Приветствую!

Примерно месяц назад я написал статью, в которой выдвинул идею об оптимизации @media screen. Идея заключается в том, чтобы иметь возможность писать значения для всех экранов в одну строку. Более подробно можете почитать по ссылке. Большинство комментариев — это критика относительно реализации, к сожалению идей никто не подкинул. Но если посмотреть с другой стороны, из каждой критики можно вытащить идею, поэтому опираясь на мнение читателей, я поставил себе цель написать миксин, который:

  • легко читается (максимально повторяющий синтаксис sass/scss/css);
  • легко поддерживать (чтобы через год ты понимал, что там написано);
  • гибкий (поддержка максимального количества описаний @media);

Давайте посмотрим, что у меня вышло (репозиторий Github)!

Синтаксис

.class{
    @include media($properties, $orientation);
}

Миксин media поддерживает два параметра $properties и $orientation.
$properties — массив css правил.
$orientation — ориентация экрана (необязательный).

$properties


.class{
    @include media((
        width: 100%;
        height: (lg: 800px, md: 600px, sm: 300px),
        transform: (all: translateX(100px) translateY(100px), sm: translateX(50px) translateY(50px)),
        color: (sm-md: $white, md-lg: $gray),
        font-size: (320: 12px, min-480: 18px, 480-md: 24px, print: 14pt)
    ));
}

Давайте разберем код подробней…

Параметр $properties является массивом, поэтому все свойства берутся в ().

width: 100%;

Обычное правило для всех экранов. компилируется в

.class{
    width: 100%;
  }

Интересное дальше:

height: (lg: 800px, md: 600px, sm: 300px)

Здесь мы описываем высоту для экранов с максимальной шириной lg, md и sm (задаются разработчиком, об этом позже).

Результат компиляции:

@media only screen and (max-width: 1024px) {
    .class{
        height: 800px;
   }
}
@media only screen and (max-width: 768px) {
    .class{
        height: 600px;
   }
}
@media only screen and (max-width: 640px) {
    .class{
        height: 300px;
   }
}

Так же обратите внимание на код ниже:

transform: (all: translateX(100px) translateY(100px), sm: translateX(50px) translateY(50px))

В данном примере присутствует экран all, я думаю вы догадались, что это все экраны. Есть принципиальная разница между all и обычным правилом, как width: 100%. Но об этом так же немного позже.

color: (sm-md: $white, md-lg: $gray)

Здесь я постарался максимально гибко соединить диапазоны экранов между min-width и max-width. Т.е. код, представленный выше скомпилирует минимальную ширину sm (640) — максимальную ширину md (768), и для экранов в этом диапазоне задаст белый цвет текста, или серый для md (768) — lg (1024).

Скомпилированный вариант:

.class{
    @media only screen and (min-width: 768px)  and (max-width: 1024px) {
        color: gray;
    }
}
.class{
    @media only screen and (min-width: 640px)  and (max-width: 768px) {
        color: white;
    }
}

Так же бывают случаи, когда мы не можем опираться только на заданные экраны. Поэтому есть возможность задавать кастомную ширину динамически. Так же если вам внезапно понадобится указать вместо максимальной ширины минимальную, или наоборот, такая возможность так же присутствует:

font-size: (880: 12px, min-480: 18px, 480-md: 24px, print: 14pt)

Первый экран max-width: 880px. По умолчанию ширина проставляется именно максимальная (так же легко поменять). Для этого экрана получим font-size: 12px;
Второй экран min-480 указывает на то, что будем отталкиваться от минимальной ширины экрана 480px (приставка min-), и в итоге получим font-size: 18px для всех экранов шире 480px.

480-md — создаст кастомный минимальный размер 480px и максимальный md (768). Т.е. это вариант диапазона экранов, как в предыдущем примере, только с произвольным значением.

Обратите внимание, что если написать 480-768 мы получим экран -288px, т.е. знак "-" отработает как минус. Поэтому такой пример стоит взять в кавычки «480-768», если конечно вы намерено не писали операцию с "-" (кстати +, *, / так же будет работать).

print: 14pt

print — является неизменным параметром и предназначен для печати:

@media print {
  .class{
    font-size: 14pt;
  }
}

Надеюсь на данном этапе все понятно… Если нет, с радостью разъясню в комментариях.

Давайте перейдем к параметру $orientation


Тут все намного проще. Есть два варианта ориентации экрана landscape и portrait. Собственно, в случае необходимости, вторым параметром прописываем именно эти значения.

.class{
    @include media((
        width: 100%;
        height: (all: 100%, md: 50%)
    ), portrait);
}

В итоге мы получим:

.class{
     width: 100%;
}
@media only screen and (orientation:portrait){
     .class{
          height: 100%;
     }
}
@media only screen and (max-width: 768px) and (orientation:portrait){
     .class{
          height: 50%;
     }
}

На этом примере можно четко увидеть разницу между all и стандартным правилом. В случае width: 100% правило при любых обстоятельствах подпадает под все экраны. В случае all — все экраны с ориентацией portrait/landscape.

Настройки


Две стандартные переменные, которые нужно отредактировать — это $breakpoints и $media-direction. Вот как они выглядят по умолчанию:

$breakpoints: (
        lg: 1024,
        md: 768,
        sm: 640
) !default; //- размеры экранов
$media-direction: max !default; - направление по умолчанию (max/min)

Т.е. для того чтобы создать себе нужное количество экранов с нужными названиями, размерами и первоначальным направлением (min/max) необходимо создать новые переменные в удобном для вас месте:

$breakpoints: (
        desktop: 1280,
        ipad: 1024,
        tablet: 768,
        mobile: 640
);
$media-direction: min;

Этого достаточно чтобы начать работу.

Как еще можно использовать данный миксин?

Если параметр $properties поместить в переменную, тогда можно получить отличную функцию. Например:

$title-style: (
     line-height: (lg: 24px, md: 20px, sm: 16px),
     font-size: (lg: 20px, md: 16px, sm: 12px;),
     text-align: (all: left, sm: center)
     ...
) 
.block{
     h2{
          color: gray;
          @include media($title-style);
     }
     h3{
          color: black;
          @include media($title-style, landscape);
     }
}

Получаем своего рода extend класс. Согласитесь, это может быть очень удобно.

Подведем итоги


Плюсы


  • легко читаемый — код максимально повторяет синтаксис CSS;
  • легко поддерживаемый — экраны прописаны достаточно понятно, и разобраться в коде не будет проблем;
  • гибкий — мы получили возможность описывать @media screen для всех экранов, диапазоны экранов, минимальную/максимальную ширину динамически, в любой момент есть возможность прописать кастомную ширину, а так же не отходя от кода настраивать правила для печати.

Минусы


  • Отсутствует описание экрана по высоте. К этой части я пока не добрался, и если данный миксин будет вам интересен, я обязательно допишу и такую возможность. Так что не забывайте писать комментарии, чтобы я мог понимать насколько для вас это актуально и полезно.
  • Отсутствует возможности прописывать @include mixin для разных экранов. На данный момент такую возможность реализовать в приведенном выше миксине посредством Sass в принципе невозможно. Насколько это критично, я оставлю вам на размышление, решайте сами, стоит ли жертвовать подобной функцией или нет. Лично для меня это вообще не критично, так как я не могу припомнить, чтобы я вообще когда-нибудь прописывал @include mixin внутри @media screen.

Вывод


Думаю, цель достигнута. Миксин получился достаточно удобным и лаконичным.

Если вы со мной не согласны, пишите комментарии. В любом случае с удовольствием выслушаю ваш совет по улучшению данного миксина, или попробую извлечь что-то полезное из вашей критики.

P.S. Скачать миксин вы можете в репозитории Github по ссылке. _mixin.scss — тот файл, который вам нужен. Так же файл package.json содержит в себе плагины для Gulp, которые было бы неплохо подключить:

  • gulp-autoprefixer добавит префиксы браузеров, там где это необходимо;
  • gulp-group-css-media-queries отлично группирует @media screen;
  • gulp-minify-css оптимизирует css, можно сказать, что данный плагин необходим.

Уверен, что для webpack или для любого другого сборщика, вы без труда найдете аналоги.

Спасибо за внимание!

Комментарии (15)


  1. anttoshka
    03.04.2018 10:03

    Намного лучше относительно прошлой реализации. Жаль нельзя внутри миксины использовать. на реальном проекте была такая задача — на десктопе одни иконки, на мобильном другие. А испльзуются они через миксин.
    Но честно говоря я у себя применять такой миксин поостерегся бы — после меня кто-то другой будет поддерживать этот код и столкнется с чем-то неизвестным. Так же я так понял нельзя использовать css свойства, которые записаны через дефис, или это не так?


    1. HTML-coder Автор
      03.04.2018 10:14

      можно использовать абсолютно любые свойства все те что и в css, даже с использованием переменных. Т.е. можно написать font-weight: (sm: $font-weight-bold). Дефис работает как минус только в случае описания экрана вроде 480-320 (т.е. два числа)… Если написать sm-880, тогда получим строку которая разобьется на два экрана sm и 880px


      1. anttoshka
        03.04.2018 10:16

        А почему не поддерживается свойство max-height?


        1. HTML-coder Автор
          03.04.2018 10:21

          я пока не дошел до этого… нужно подумать как реализовать, чтобы не выбиваться из общего концепта. Надеюсь доработать в будущем


        1. HTML-coder Автор
          03.04.2018 13:08

          Я немного не правильно написал, чем завел в заблуждение и Вас и себя… max-height — я имел ввиду описание экрана по высоте, а не как свойство. Прошу прощения. В статье отредактировал этот пункт


          1. anttoshka
            03.04.2018 14:37

            Теперь понял =)


    1. HTML-coder Автор
      03.04.2018 10:20

      По поводу миксинов внутри… я честно потратил пару дней на поиски возможности реализовать, но столкнулся с тем что в sass невозможно динамически вызывать миксины. Т.е.

      @include $mixin-name()
      — не прокатит ни в каком виде


  1. Qumbeez
    03.04.2018 12:08

    А чего Bootstrap 4 миксины не использовать бы? Там всё хорошо работает. github.com/twbs/bootstrap/blob/v4-dev/scss/mixins/_breakpoints.scss


    1. HTML-coder Автор
      03.04.2018 12:18

      Спасибо за ссылку, раньше не натыкался на него. Бегло пробежался, и если я правильно понял, bootstrap миксин не избавляет от дублирования свойств, по сути у нас похожи, но все же разные подходы. Но как альтернатива, то конечно…


      1. Qumbeez
        03.04.2018 16:10

        Про дублирование свойств это просто экономия на спичках. Тот код, который вы показывали в прошлой статье, якобы ваш миксин экономит место, я бы вам посоветовал изучить подход вёрстки mobile first, тогда большинство дублирующего кода просто отпадёт.


        1. HTML-coder Автор
          03.04.2018 16:46

          У mobile first есть свои плюсы и недостатки, но я макет для мобильных экранов получаю максимум в 20% случаев, а вот десктопные постоянно. Еще часто бывает, что по дизайну необходимо экранов достаточно много. Если 5 экранов, 5 раз дублировать код. всегда проще поменять в одном месте, согласитесь. Так что с «экономия на спичках», не согласен абсолютно. Вы можете со мной соглашаться или нет, дело ваше, но я опробовал вариант из первой статьи на не большом сайте, с большим количеством экранов, и реально править было легко и быстро. Да, код слабо читаемый, но для этого и был написан миксин который представлен в данной статье.


    1. anttoshka
      03.04.2018 14:38

      Если я не ошибаюсь, это тот же миксин respond-to.


      1. HTML-coder Автор
        03.04.2018 16:06

        вот и мне так кажется, но немного расширенный… там есть возможность задавать минимальный и максимальный размер экрана. И кажется еще что-то с вариациями экранов


  1. AlexBukh
    03.04.2018 21:24

    А мы у себя заюзали миксин к конкретному правилу (кстати можно подправить что бы как у вас передавался масив свойств)
    ну собственно gist-link
    миксин зовется response-rule
    =) критиру и предложения в студию
    (по поводу трудности сопровождения думаю что решается прямыми руками и знанием grep-sed-awk)


    1. HTML-coder Автор
      04.04.2018 12:58

      Ваш миксин, по сути похож на мой первый вариант https://m.habrahabr.ru/post/350466/, только с названиями экранов. Можете почитать отзывы, и сделать выводы