Пользователь
0,0
рейтинг
18 августа 2014 в 14:11

Разработка → Малоиспользуемые, но от этого не менее прекрасные возможности LESS из песочницы

Данный пост навеян коментарием уважаемого хабраюзера SerafimArts о том, что LESS много чего не умеет. Хочется развеять эти крамольные заявления и заодно показать, каким прекрасным может быть LESS, если правильно его готовить.

Примечание: некоторые примеры «из жизни» в данной статье предоставлены для тех людей, кто по каким-то причинам (вплоть до религиозных) не использует Autoprefixer.

Примечание 2: для всего, что написано ниже используется последняя версия LESS, потому что нет вообще ни одной причины её не использовать.



Слияния


Они же объединения, они же мерджи (Merge). Используются, если вам нужно что-нибудь присоединить через пробел или через запятую. Транзишны, трасформы, множественные бэкграунды, тени (простите за русское слово: бокс-шадоуы звучит как-то неласково) ликуют. Лучше всего за меня скажут примеры.


Объединяем через пробел прямо в миксине
.transform (@value) {
    transform+_:@value;
}

.transformed {
    .transform(rotateX(10deg));
    .transform(scale(1.2,1.1))
}

Скомпилируется в
.transformed {
     transform: rotateX(10deg) scale(1.2, 1.1);
}


А теперь через запятую
.transition (@property: all,@duration: 0s,@delay: 0s,@ease: ease-in-out){
    transition+: @arguments;
}

.transitioned {
    .transition(width,1s);
    .transition(opacity,0.5s,0.2s,linear);
}

Превратится в
.transitioned {
     transition: width 1s 0s ease-in-out, opacity 0.5s 0.2s linear;
}


То что нужно. Никаких сложностей с использованием: просто вызовите нужный миксин столько раз, сколько вам надо. Это чем-то даже удобнее способа с неопределенным количеством параметров: жёсткий порядок аргументов не даст вам возможности потеряться. Счастье, да и только.

Условия


Вместо привычных if-ов в LESS существуют так называемые гарды (охранники, guards). Суть их в том, чтобы вызывать разные объявления одного миксина в зависимости от различных условий (есть в них что-то от дженериков). Пример из жизни приведен во всё том же комментарии, который меня вдохновил: транзишны для трансформов хотелось бы иметь с префиксами. Да будет так (обратите внимание на ключевое слово when после объявления миксина):

//Специальный вариант для трансформа
.transition (@property: all,@duration: 0s,@delay: 0s,@ease: ease-in-out) when (@property=transform) {
    -webkit-transition+: -webkit-transform @duration @delay @ease;
       -moz-transition+:    -moz-transform @duration @delay @ease;
         -o-transition+:      -o-transform @duration @delay @ease;
            transition+:     -ms-transform @duration @delay @ease, 
                                 transform @duration @delay @ease;  //Кажется, -ms-transition официально не существует
}

//И всё остальное.
.transition (@property: all,@duration: 0s,@delay: 0s,@ease: ease-in-out) when not (@property=transform) {
    -webkit-transition+: @arguments;
       -moz-transition+: @arguments;
         -o-transition+: @arguments;
            transition+: @arguments;
}

.transitioned {
    .transition(width,1s);
    .transition(transform,0.5s,0.2s,linear);
}


На выходе получаем (форматирование по столбцам моё, остальное сделал препроцессор)
.transitioned {
    -webkit-transition: width 1s 0s ease-in-out, -webkit-transform 0.5s 0.2s linear;
       -moz-transition: width 1s 0s ease-in-out,    -moz-transform 0.5s 0.2s linear;
         -o-transition: width 1s 0s ease-in-out,      -o-transform 0.5s 0.2s linear;
            transition: width 1s 0s ease-in-out,     -ms-transform 0.5s 0.2s linear, 
                                                         transform 0.5s 0.2s linear;
}


Ну разве это не прекрасно? Гарды поддерживают отношения =, >, <, >=, <=, логические операторы and, or, not, и ещё немного функций для проверки на тип переменной (iscolor, isnumber и т. п.), что позволяет варьировать выбор миксина в значительных пределах. Маленький, но важный нюанс: LESS возьмёт и выполнит все миксины с одним именем, которые пройдут через охрану. Это надо не забывать и дописывать все нужные условия для «остальных» случаев. И я думаю, что это хорошо. А почему это хорошо, будет видно ниже.

Циклы


Заявление, что в LESS нету циклов абсолютно верно. Но зато есть рекурсия! Собственно, её и предлагают использовать разработчики. В качестве жизненного примера создадим генератор спрайтовых классов для картинки (не пропускайте код, там много комментариев)

// Это базовый миксин для бэкграунда. Он простой, как пять копеек, но это всего лишь пример
.spriter (@x: 0,@y: 0) {
    background: #cccccc url("my-sprite-img.png") @x*20px @y*30px;
}

//Здесь непосредственно генерируется класс
.generate_sprite(@x,@y) {
    .sprite-@{x}-@{y}{
        .spriter(@x,@y);
    }
}

//Следующие две функции управляющие - задают порядок действий при обходе двумерного массива рекурсией. 
//Удобно то, что их можно называть так же, как и основную функцию-генератор. 
//LESS будет вызывать все миксины, которые удовлетворят гардам.
.generate_sprite(@x,@y) when (@y=0) and (@x>0) {
    .generate_sprite(@x - 1,@spritey);
}

.generate_sprite(@x,@y) when (@y>0){
    .generate_sprite(@x,@y - 1);
}

//Числа, которые на один меньше количества столбцов и строк иконок в спрайте
@spritex: 1;
@spritey: 2;

//Запускаем генератор
.generate_sprite(@spritex,@spritey);



На выходе получим шесть классов для иконок
.sprite-1-2 {
  background: #cccccc url("my-sprite-img.png") 20px 60px;
}
.sprite-1-1 {
  background: #cccccc url("my-sprite-img.png") 20px 30px;
}
.sprite-1-0 {
  background: #cccccc url("my-sprite-img.png") 20px 0px;
}
.sprite-0-2 {
  background: #cccccc url("my-sprite-img.png") 0px 60px;
}
.sprite-0-1 {
  background: #cccccc url("my-sprite-img.png") 0px 30px;
}
.sprite-0-0 {
  background: #cccccc url("my-sprite-img.png") 0px 0px;
}


Именно здесь мы использовали то, что вызовутся все подходящие миксины. Ведь благодаря этому нам не пришлось дублировать вызов генератора класса в управляющих структурах. Отлично же.
Наверное, вы скажете, что это извращение. Думаю, вы будете правы. Всё же отсутствие циклов в языке несколько увеличивает сложность в построении таких конструкций. Благо, нужны они не так часто.

Внесение кода как параметр


В качестве параметров можно передавать полноценные куски less-кода, которые будут себя вести вполне логично. Примеры — наше всё: показывать проще чем объяснять
.placeholder(@content) {
    &::-webkit-input-placeholder{
        @content();
    }
    &:-moz-placeholder {
        @content();
    }
    //Пожалуй, для демонстрации хватит
}

.withplaceholder{
    .placeholder({
         //Вау, мой редактор кода даже подсветит это правильно
         color:blue;
         font-style: italic;
   })
}


Лёгким движением руки превращается в
.withplaceholder::-webkit-input-placeholder {
  color: blue;
  font-style: italic;
}
.withplaceholder:-moz-placeholder {
  color: blue;
  font-style: italic;
}


Более того, вы можете во встраиваемый код вставлять вызов других миксинов и всё сработает:
.topleft(@position){
    top:@position;
    left:@position/2;
}

.keyframes(@name,@rules) {
    //Можно я обойдусь без вендорных префиксов?
    @keyframes @name {
        @rules();
    };
}

.keyframes(down,{
    0% {
        .topleft(0);
    };
    100% {
        .topleft(80);
    };
});


И на выходе
@keyframes down {
  0% {
    top: 0;
    left: 0;
  }
  100% {
    top: 80;
    left: 40;
  }
}

Создали весь пак кейфреймов (точнее создали бы, если бы я для краткости и читабельности не пропустил вендорные префиксы) одной единственной командой — идеально.

И ещё одна вкусность напоследок в этом разделе: не любите писать длинные media query? Я вас понимаю: каждый раз одно и тоже. Давайте сократим. Вот такой код
@width_mobile: 320px;
@width_tablet: 640px;

.media_mobile(@rules) {
    @media screen and (max-width: @width_tablet) and (min-width: @width_mobile) {
        @rules();
    };
};

.class_parent{
    width:300px;
    .media_mobile({
        width:200px;
        .class_child{
            width:100px;
        }
    });
}


Станет таким
.class_parent {
    width: 300px;
}
@media screen and (max-width: 640px) and (min-width: 320px) {
    .class_parent {
        width: 200px;
    }
    .class_parent .class_child {
        width: 100px;
    }
}

Не писать каждый раз длинный паровоз media query — это ли не мечта адаптивного верстальщика?

Что можно сказать в итоге. LESS очень активно развивается. Некоторых из этих фичей не было буквально до версии 1.7, которая вышла буквально полгода назад. Лично мне это доставляло некоторые неудобства: часто приходится работать с различными видами анимаций на фронтенде. Однако теперь я практически готов расцеловать разработчиков за этот милый и добрый препроцессор, который прост в освоении (не было никаких проблем в том, чтобы перевести на него даже самых неопытных разработчиков), достаточно мощен для всех (или почти всех) жизненных ситуаций и, что немаловажно, имеет удобный читабельный синтаксис.

Надеюсь, данный пост хоть немного подымет LESS в глазах хабрасообщества. Всем спасибо за внимание.

P.S. Если мне кто-нибудь может подсказать, как сделать подсветку LESS на хабре, буду очень благодарен.
Артём Старченко @Starche
карма
14,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

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

  • +1
    А фишки и правда новые, переходя по ссылке я ожидал прочитать про то, что уже и сам знаю, ан нет.
    Ну и про Autoprefixer — если вы его еще не используете — делаете себе же хуже. По началу я было сомневался, так как и не надеялся сохранить корректный source map, но Autoprefixer так крут, что после тривиального колдовства с конфигом в веб-инспекторе будут отображаться корректные пути к исходным файлам.
  • 0
    Примеры использования которые вы показали, полезны, когда под рукой нет ничего кроме заготовочек less.
    В работе же, задачи слияния классов и свойств, генерацию безошибочных спрайтов, префиксов, выполняют Grunt/Gulp.
    Комфортнее писать CSS3 без лишних символов, зависящих от препроцессора.
    • +1
      Не очень понял, как Grunt (или Gulp) помогут в той части, которая касается слияний. Из моего опыта: довольно часто происходит так, что на элемент наложено 3-4 транзишна и он имеет несколько состояний. При этом отличия в тразишнах для разных состояний только в одном из этих четырех, а переписывать приходится полностью. Тут отлично поможет миксин на неизменяемую часть и объединение через запятую.
      Если Grunt как-то здесь может помочь, то не могли бы вы направить в какую сторону смотреть?
      В остальном несомненно, вы правы. Однако не только этими примерами ограничивается применение указанных возможностей: циклы (через рекурсию) можно применить при генерации сетки (так делает bootstrap), передача кода как параметр поможет заменить длинные media query (пожалуй, добавлю это в статью), условия вообще полезная штука.
      Ну а использовать или не использовать препроцессоры — это настолько холиварная тема, что даже касаться не хочется
      • 0
        По поводу слияний, я подразумевал минификаторы (мне нравится: clean-css, для Grunt это contrib-cssmin). Специально посмотрел, clean-css не делает слияния такого типа (разные транситионы), но, какой смысл писать одно свойство в классе двумя строчками? WebStorm сразу подсветит ошибку, пишу через запятую. Возможно другие минификаторы так могут, мне хватает одного. Не забываем про @extend + работа clean-css по объединению, хороша.
        Сам пользую scss естественно, первый комментарий в контексте «плюсов» этой статьи.
        • +2
          какой смысл писать одно свойство в классе двумя строчками?

          Смысл в том, что LESS сам склеит их в одну строку и Webstorm как ошибку вам их не подсветит.
          А ещё более глобальный смысл покажу на примере.
          Пусть у вас три транзишна на элементе: top, opacity и color. При этом top и opacity всегда есть. А вот если у элемента :hover, то color у него нету, то бишь проявляться должен цвет мгновенно, а пропадать плавно (http://jsbin.com/qocol/1/)
          Вам приходится писать оба неменяющихся транзишна дважды. А если в дело вступают другие состояния (через классы, которые навешиваются в JS), то и ещё больше чем дважды. И если надо поправить тайминг, то делать это приходится везде.
          С помощью возможности объединения свойств, используя миксин
          .transition (@property: all,@duration: 0s,@delay: 0s,@ease: ease-in-out){
              transition+: @arguments;
          }
          

          Вы сможете написать что-то вроде
          .button_transitions(){
              .transition(height,1s);
              .transition(opacity,1s);
          }
          
          .button{
              .button_transitions;
              .transition(color,1s);
              &:hover{
                  .button_transitions;
              }
          }
          

          И получите то, что нужно
          .button {
            transition: height 1s 0s ease-in-out, opacity 1s 0s ease-in-out, color 1s 0s ease-in-out;
          }
          .button:hover {
            transition: height 1s 0s ease-in-out, opacity 1s 0s ease-in-out;
          }
          

          плюс возможность менять общую часть транзишнов в одном месте.
          • –1
            Догадываюсь, что вы хотите описать, но, вот без микшина и нестандартных записей (хотя можно и микшин написать в scss, получается не плюс less, а рядовая возможность препроцессоров):
            .button_transitions {
                transition: height 1s 0s ease-in-out, opacity 1s 0s ease-in-out;
            }
            .button {
              @extend .button_transitions;
              transition: color 1s 0s ease-in-out;
              &:hover {
                @extend .button_transitions;
              }
            }
            

            И получите, меньше кода (на 23 символа):
            .button_transitions, .button, .button:hover {
              transition: height 1s 0s ease-in-out, opacity 1s 0s ease-in-out;
            }
            .button {
              transition: color 1s 0s ease-in-out;
            }
            

            Пишется и выглядит, на мой взгляд, лаконичнее.
            WebStorm, в данном случае подсветит в scss ошибку, если в рамках класса дублируется свойство transition (не в моём примере, а если бы писал в две строки).
            • +3
              Ваш транзишн отработает неправильно. Потому что в итоговом CSS для класса .button второе объявление убъёт первое, а не дополнит его
              • –1
                Я проверил, работу в браузере, произойдёт слияние свойств. :)
                • +3
                  Пока не покажете, не поверю. Только что проверил в последнем стабильном Хроме, свойство перезаписывается: сравните
                  jsbin.com/qocol/1 — при mouseout плавное движение обратно
                  jsbin.com/qocol/2 — рывок
                  • +1
                    Согласен, поспешил с первым вариантом примера.
                    Скрытый текст
                    $button_transitions: height 1s ease-in-out, opacity 1s ease-in-out;
                    button {
                      transition: $button_transitions, color 1s ease-in-out;
                      &:hover {
                        transition: $button_transitions;
                      }
                    }
                    

                    Получится:
                    button {
                      transition: height 1s ease-in-out, opacity 1s ease-in-out, color 1s ease-in-out;
                    }
                    button:hover {
                      transition: height 1s ease-in-out, opacity 1s ease-in-out;
                    }
                    
      • 0
        Ваш пример с сокращением media-queries криво работает в WebStorm-е (ломается форматирование и корректная подсветка иногда).
        Я использую другой способ:

        // определяем все медиа-типы в переменные
        @desktopView: ~'(min-width: @{minWidth})';
        @mobileView: ~'(max-width: @{maxWidth})';
        
        // и используем дальше, как и раньше с @media 
        @media @desktopView {
         .someRules { ... }
        }
        
        • 0
          Релизы Jetbrains не поспевают за развитием LESS. Вполне может быть, что в EAP-билдах уже работает верно. Надо будет проверить и, если нет, то зарепортить им в багтреккер.
  • 0
    Спасибо большое за статью.

    Последняя дата, когда я притрагивался к Less (более того — именно с него я начинал) — была примерно в районе 2011-2012 годов, после этого сложилось вполне стереотипное мнение о неполноценности языка. Сейчас же я вижу, что все мои вопросы и проблемы тех лет уже решились, благодаря развитию языка.
  • 0
    Появилось больше уважения к LESS, но Sass по-прежнему в миох глазах стоит выше. Возможность функций и настоящих условий хотя бы мне очень милы. Хотя вот слияние свойств — довольно вкусная штука, которой в Sass как раз-таки нет.

    Чем действительно статья помогла — так это тем, что я ещё раз сходил в репозиторий Автопрефиксера и обнуражил, что уже 2 месяца мог бы им пользоваться (2 месяца назад был релиз Web Essentials со встроенным автопрефиксером — чем я ченжлоги вообще читал?). Лучше поздно, чем никогда, конечно, но обидно. =)
    • 0
      Удивительное рядом. Буквально только что из статьи habrahabr.ru/post/181746/ узнал, что LESS умеет выполнять javascript. Это я к чему: функции и обычные условия нужны не так часто и, обычно, если нужны, то нужны вместе (для обычных случаев вполне хватает гардов вместо условий). Тут как раз и придёт на помощь злой, но полезный eval. Так что могу предложить вам задуматься ещё раз =)
      • 0
        Ох ты ж. Какие костыли. Возможностей это, конечно, добавляет кучу, но как-то не по нутру такое вот смешивание языков. Но спасибо, когда-нибудь и это пригодится.)
        Я, вообще, знакомство с препроцессорами как раз с LESS и начинал. Гарды и «циклы» немного прочувствовал. В каких-то ситуациях было неудобно — может, в силу нехватки опыта. Потом ушёл к Sass.
        В ближайший раз, когда придётся выбирать, может и задумаюсь.
  • 0
    Спасибо за статью. Теперь будет урл, которым можно бросаться в less-скептиков )
    Кстати, кто какой grunt-плагин использует в добавку к less для генерации спрайтов?
  • +6
    интересно, сколько еще людей после прочтения названия решило что речь про юниксовый less(1)
  • 0
    Насчет примечания 2 о том, что нет ни одной причины не использовать последний less — вы заблуждаетесь. LESS 1.7 компилирует примерно в два раза медленнее версии 1.3, при этом не добавляя серьезных удобств и возможностей. На текущем проекте имеется около 15000 строчек less, что приводило к 30-40 секундной компиляции на каждое изменение в стилях, и это на достаточно быстрой машине (последний iMac). Рефакторинг не особенно помогал в этой ситуации, но даунгрейд LESS до версии 1.3 позволил сократить это время до 15-20 секунд, причем как показала практика, обойтись без новых возможностей LESS можно вообще без проблем.
    Лучше бы ребята инкрементальную компиляцию имплементировали и занялись серьезно вопросом оптимизации, нежели напичкиванием функционала.
    • +1
      А в каких ситуациях less-файл может быть столь огромным? Я конечно понимаю что в продакшене можно все склеивать и минифицировать, но вот при разработке? Мне кажется что стоит разделять такие большие файлы стилей на логические части. Так и разрабатывать удобнее и компилить быстрее (только измененный файл). А в продакшене уже сливать всё в один минифицированный файл. Правда все равно не уверен что это будет хорошая идея, т.к. по моим прикидкам 15000 строк кода less будут занимать ~300 кб, что в css будет еще больше. И пока такой «жирный» файл стилей не подгрузится, вся страница будет выглядеть ужасно, что может быть критично для медленного канала
      • +1
        Для начала возьмем less-hat (1000 строк). Потом декларацию переменных и декораторов (1500 строк). Потом декларацию шрифтов, иконок. Рисователь png в base64 (еще 1000 строк). Базовая разметка, сетка. Возможно еще что всплывет, уже не помню. Это все — минимальный остаток, после ревизий, рефакторингов и сжатий, самое необходимое (ведь можно было бы использовать фреймворк). Это все — накладные расходы, т. е. зависимости, которые придется подключать всегда, к каждому отдельно компилируемому входному куску less. Уже такая конструкция сама по себе компилится секунд 10-12 на less 1.7. На 1.3 — сносно, в районе 5-7 секунд.
        Насчет размера выходного файла в less — он может быть даже нулевым, но компиляция будет долгой, — дело не только в рендеринге, но и парсинге. lesshat, к примеру, вообще не генерит css, как и всяческие декораторы.
        Хотя выходной css на проекте действительно довольно большой — 50Кб после сжатия.
    • 0
      Да, тоже был нериятно удивлен производительностью 1.7

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