Таргетинг стилей по устройству

Сайты нужно делать адаптивными, а для этого хорошо бы уметь управлять содержимым и представлением сайта в зависимости от типа устройства пользователя.

Публикация от ⁠. Крайнее обновление ⁠.

Принцип

Когда речь заходит об адаптации сайта⁠, то, «⁠То что можно сделать на сервере⁠, то нужно делать на сервере⁠». А клиенту нужно отдать то⁠, что ему может понадобиться, и не отдавать ему то⁠, что точно не понадобится⁠.

То есть⁠, совсем незачем отдавать на desktop стили с указанием @media screen and (orientation : portrait)⁠, а мобильным устройствам совсем не нужно знать о ваших «костылях» для Internet Explorer⁠.

Стремитесь к тому⁠, чтобы таргетинг был максимальным⁠, но разумным⁠. Выносите в отдельные CSS-файлы стили с чётким делением по какому-либо признаку, например, по типу устройства.

«⁠Выносить в отдельные CSS-файлы⁠» — не значит держать/хранить код в отдельном файле⁠, наоборот, стили для одной сущности нужно держать в одном месте⁠, в одном файле⁠, но в итоге⁠, при генерации⁠, у вас должны получиться файлы со стилями для определённого устройства в отдельном файле⁠.

Есть мнение⁠, что детектировать нужно не устройства⁠, а фичи⁠. А моё мнение таково⁠, зная браузер клиента, нам уже нет смысла производить 99% проверок⁠.

Будем руководствоваться основополагающим принципом: «⁠За один сеанс устройство изменить/сменить нельзя⁠, а viewport — можно».

Это означает⁠, что при ответе на запрос⁠, клиенту нужно отдать документы (разметку, стили, JS), а может и данные⁠, соответствующие именно его устройству⁠. Зачем? Я не сторонник отдавать клиенту «⁠полный фарш⁠», и после изменять его при помощи JS и CSS media query на клиенте⁠. Нет, изменять можно, конечно, но лучше это делать как можно меньше⁠.

Поклонники Modernizr скажите, на самом деле все используемые вами проверки реализуются только на клиенте⁠?

Я практикую деление устройств на следующие типы⁠:

  • desktop / стационарные ПК⁠, ноутбуки;
  • tablet / планшеты;
  • phone / смартфоны;
  • ie / Internet Explorer.

Да-да⁠, стили для IE я выношу в отдельные CSS-файлы⁠. Уж слишком много отличий у данного творения от других броузеров: Flexible Box⁠, classList, Promises, Srcset и т.д. На практике нам необходимо иметь в шаблонах и стилях переменную, например, со значением устройства.

Реализация

Все просто⁠, определяем тип устройства на сервере и прокидываем значение типа в переменную в шаблонах и стилях⁠. Таким образом мы получаем структуру, в которой весь код относящийся к конкретной сущности, находится в одном файле⁠, вне зависимости от деления на устройства⁠, для которых он предназначен⁠.

Для распарсивания UserAgent на Ruby я рекомендую использовать Device Detector for Ruby⁠.

Передать переменную из Ruby в Scss можно не обладая глубокими знаниями Ruby⁠. Создаём отдельный файл vars/dynamic.scss⁠, подключаем его к стилям⁠.

@import "vars/dynamic.scss";

И самое главное, попросите ваше Ruby-приложение записать значения необходимых переменных в ваш Scss-файл⁠.

def dynamicVars( device, section )
    path = '/vars/dynamic.scss'
    string = "$device: #{device}; $section: #{section};"

    File.write(path, string)
end

dynamicVars( $device, $section )

Для удобства использования, в Scss создан mixin⁠, который добавляет код по определённому признаку⁠.

@mixin device($collection...) {
    @each $item in $collection {
        @if $item == $device {
            @content;
            }
        }
    }

Т.е., если одно из целевых устройств (⁠для кого написан данный блок кода⁠) совпадает с самим текущим устройством клиента, то код применяем⁠. Используем просто:

.some-block {
    z-index: 1;
    position: relative;
    
    @include device(desktop, ie, tablet) {
        @include section(portfolio) {
            border: 1px solid #000;
            }
        }
    }

Классы на <html>

Помните, Paul Irish рекомендовал использовать классы на <html> для таргетинга стилей⁠?

<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7" lang="en"> <![endif]-->
<!--[if IE 7]>    <html class="no-js lt-ie9 lt-ie8" lang="en"> <![endif]-->
<!--[if IE 8]>    <html class="no-js lt-ie9" lang="en"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en"> <!--<![endif]-->

Я предлагаю использовать данную модификацию по родителю⁠, как способ более точечной кастомизации. Например, есть стили для Android⁠, но вам необходимо модифицировать стили для Android v.4.2.2⁠. В контексте описанного примера метод модификации через классы на элементе <html> очень жизнеспособен.

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

.some-block {
    width: 60vw;
    text-align: center;

    .__android-422 & {
        width: 60%;
        }
    }

Определить имя и версию операционной системы пользователя вам поможет уже упомянутая библиотека Device Detector⁠. Быстрых сайтов вам и вашим пользователям!

Update

Более корректная передача переменной между Ruby и Sass⁠. Спасибо, Макс⁠!

require 'sass'
require 'sass/plugin'

# Sass module extension
module Sass::Script::Functions
    # SASS-method - device type
    def device
        Sass::Script::Value::String.new(Sass::Plugin.options[:custom][:device])
    end

    declare :get_device []
end