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

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

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

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

Принцип

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

То есть, совсем незачем отдавать на 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. Пользователи PHP могут обратить внимание на Device Detector for PHP.

Передать переменную из 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