HTML5 INSIGHT


Интересная заметка про использование в CSS единиц измерения, завязанных на размер Viewport. Чем больше экран, тем больше шрифт – длина строки в символах остается примерно одинаковой.

IE(9)10+ (между 9 и 10 версиями спецификация успел слегка измениться: vm => vmin), Chrome 20+

===
Замечу, что для маленьких экранов это не всегда приемлемо.



Интересное интервью с Doug Crockford о важности изучения истории CS, программистах и языках, JavaScript и jQuery.



Adobe верными шагами движется в сторону продвижения HTML, CSS и развития своих тулов для работы с веб-стандартами. Ура!



Source: Unleash the power of HTML 5 Canvas for gaming by David Catuhe

Браузеры, поддерживающие HTML5, и платформа HTML5 для Windows 8 Metro сегодня становятся серьезными кандидатами для разработки современных игр.

Благодаря canvas, у вас есть доступ к аппаратно-ускоренной поверхности, на которой вы можете отображать контент вашей игры и с помощью некоторых трюков и ухищрений вы сможете достигнуть великолепной производительности рендеринга до 60 кадров в секунду. Подобная непрерывность действительно важна в играх, так как чем плавнее игра (анимация), тем лучше чувствует себя игрок.

Цель данной статьи — дать вам несколько подсказок, как выжать максимум мощности из HTML5 Canvas. Статья состоит из двух основных частей [вы читаете первую]. David Rousset скоро опубликует вторую часть.

В статье я буду показывать ключевые примеры на одном и том же примере — это эффект 2D-туннеля, который я написал для Coding4Fun-сессии на TechDays 2012 во Франции.

На написание данного эффекта меня вдохновил мой код для Commodore AMIGA, который я написал, когда был молодым автором демо-сцен в далеких 80х :). Сегодня он использует только canvas и Javascript, хотя изначальный код базировался только на 68000 ассемблере:


Пример на JSFiddle (учтите, что текстура зашита в код).

Полный код доступен для скачивания тут: http://www.catuhe.com/msdn/canvas/tunnel.zip (копия на Я.Народ).

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

Использование скрытого canvas для чтения данных изображения

Первая вещь, о которой я хочу поговорить, это то, как вы можете использовать canvas для оптимизации чтения данных изображения. Действительно, практически в каждой игре вам нужна графика для спрайтов или фоновых изображений. В canvas есть очень удобный метод для отрисовки изображений: drawImage. Эта функция может использоваться для вывода спрайта в canvas-элемент, так как вы можете указать области источника и назначения для отрисовки.

Но иногда этого не достаточно. Например, когда вы хотите применить какие-то эффекты к исходному изображению. Или когда исходное изображение не просто картинка, а более сложный ресурс для вашей игры (к примеру, карта, из которой вам нужно считывать данные).

В таких случаях вам необходим доступ к внутренним данным изображения. Но image-тег не предоставляет способа для считывания содержимого. Вот тут-то на помощь и приходит canvas!

В сущности, каждый раз, когда вам нужно считать содержимое изображения, вы можете использовать невидимый (не выводимый на экран) canvas. Ключевая идея заключается в том, чтобы загрузить изображение, и, когда оно загружено, вам остается только отобразить его на canvas-элемент, не вставленный в DOM. Теперь у вас есть доступ к [скопированным] пикселям исходного изображения через соответствующие пиксели canvas (что очень просто).

Код для этой техники выглядит следующим образом (используется в эффекте 2D-туннеля для чтения текстуры туннеля):

var loadTexture = function (name, then) {
    var texture = new Image();
    var textureData;
    var textureWidth;
    var textureHeight;
    var result = {};

    // on load
    texture.addEventListener('load', function () {
        var textureCanvas = document.createElement('canvas'); // off-screen canvas

        // Setting the canvas to right size
        textureCanvas.width = this.width; // <-- "this" is the image
        textureCanvas.height = this.height;

        result.width = this.width;
        result.height = this.height;

        var textureContext = textureCanvas.getContext('2d');
        textureContext.drawImage(this, 0, 0);

        result.data = textureContext.getImageData(0, 0, this.width, this.height).data;

        then();
    }, false);

    // Loading
    texture.src = name;

    return result;
};

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

// Texture
var texture = loadTexture("soft.png", function () {
    // Launching the render
    QueueNewFrame();
});

Использование возможности аппаратного масштабирования

Современные браузеры и Windows 8 поддерживают аппаратное ускорение canvas. Это, в частности, означает, что вы можете использовать GPU для масштабирования контента в canvas.

В случае с эффектом 2D-туннеля алгоритм требует обработки каждого пикселя canvas. К примеру, для canvas размером 1024x768 необходимо обработать 786432 пикселей. Чтобы обеспечить непрерывность отображения, это нужно делать 60 раз в секунду, что соответствует обработке 47185920 пикселей в секунду!

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

Повторю, canvas предоставляет такое средство! Следующий код показывает, как использовать аппаратное ускорение для масштабирования внутреннего буффера canvas до внешнего размера DOM-объекта::

// Setting hardware scaling
canvas.width = 300;
canvas.style.width = window.innerWidth + 'px';
canvas.height = 200;
canvas.style.height = window.innerHeight + 'px';

Обратите внимание на разницу между размером DOM-объекта (canvas.style.width и canvas.style.height) и размером рабочего буффера canvas (canvas.width и canvas.height).

При наличии разницы между этими двумя размерами используются возможности железа для масштабирования рабочего буффера — в нашем случае это просто великолепная функция: мы можем работать в меньшем разрешении и позволить GPU смасштабировать результат, чтобы заполнить DOM-объект (с использованием прекрасного бесплатного фильтра размытия для сглаживания результата).

В данном примере рендеринг производится в области 300x200, а GPU масштабирует до размеров вашего окна.

Эта возможность широко поддерживается всеми современными браузерами, поэтому вы можете рассчитывать на нее.

Оптимизация цикла отрисовки

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

RequestAnimationFrame

Одна из интересных возможностей, пришедших с HTML5, это функция window.requestAnimationFrame. Вместо использования window.setInterval для создания таймера, вызывающего ваш цикл отрисовки каждые (1000/16) миллисекунд (чтобы достичь заветных 60fps), вы можете делегировать эту ответственность на браузер с помощью requestAnimationFrame. Вызов данного метода говорит, что вы хотите, чтобы браузер вызвал ваш код сразу же, как только будет возможно обновить графическое представление.

Браузер включит ваш запрос внутрь своего расписания отрисовки и синхронизирует вас со своим кодом отрисовки и анимации (CSS, переходы, и т.д.). Это решение также интересно в связи с тем, что ваш код не будет вызываться, когда окно не видно (свернуто, полностью перекрыто и т.д.).

Это может помочь с производительностью, так как браузер может оптимизировать параллельную отрисовку (например, если ваш цикл рендерига слишком медленный) и при этом выдавать плавные анимации.

Код довольно очевиден (обратите внимание на использование браузерных префиксов):

var intervalID = -1;
var QueueNewFrame = function () {
    if (window.requestAnimationFrame)
        window.requestAnimationFrame(renderingLoop);
    else if (window.msRequestAnimationFrame)
        window.msRequestAnimationFrame(renderingLoop);
    else if (window.webkitRequestAnimationFrame)
        window.webkitRequestAnimationFrame(renderingLoop);
    else if (window.mozRequestAnimationFrame)
        window.mozRequestAnimationFrame(renderingLoop);
    else if (window.oRequestAnimationFrame)
        window.oRequestAnimationFrame(renderingLoop);
    else {
        QueueNewFrame = function () {
        };
        intervalID = window.setInterval(renderingLoop, 16.7);
    }
};

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

var renderingLoop = function () {
    ...


    QueueNewFrame();
};

Доступ к DOM (Document Object Model)

Чтобы оптимизировать свой цикл отрисовки, вам следует использовать как минимум одно золотое правило: НЕ ОБРАЩАЙТЕСЬ К DOM. Хотя современные браузеры специально оптимизированы в этом месте, чтение свойств DOM-объектов по-прежнему слишком медленное для быстрого цикла отрисовки.

Например, в моем коде я использовал профилировщик Internet Explorer 10 (доступный в инструментах разработчика по F12) и результаты очевидны:

performance DOM

Как вы можете видеть, доступ к ширине и высоте canvas занимает очень много времени в цикле отрисовки!

Изначальный код выглядел так:

var renderingLoop = function () {


    for (var y = -canvas.height / 2; y < canvas.height / 2; y++) {
        for (var x = -canvas.width / 2; x < canvas.width / 2; x++) {

            ...

        }
    }
};

Вы можете заменить свойства canvas.width и canvas.height двумя переменными с заранее заведенными правильными значениями:

var renderingLoop = function () {

    var index = 0;
    for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) {
        for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) {
            ...
        }
    }
};

Просто, не правда ли? Возможно, что не очень просто понять, но, поверьте мне, это стоит попробовать!

Предварительные вычисления

Согласно профилировщику, функция Math.atan2 несколько медленная. На самом деле, эта операция не зашита внутрь CPU, так что среда выполнения JavaScript должна проделать некоторые операции, чтобы вычислить результат.

[пер.: Хотя, с технической точки, зрения можно предположить, что конкретная реализация JS-runtime может опираться на аппаратную инструкцию fpatan, этого априорно никто не гарантирует. Спецификация ECMAScript5 относительно математических функций говорит о том, что они (очевидно) являются аппроксимациями и рекомендует использовать алгоритмы математической библиотеки Sun Microsystems (http://www.netlib.org/fdlibm). В любом случае, какой бы ни была реализация функции atan2, это не делает ее элементарной и быстрой.]

performance atan2

В целом, если вы можете организовать предварительный расчет для некоторого долго работающего кода, это всегда хорошая идея. Здесь прежде, чем запустить мой цикл отрисовки, я рассчитываю значения для Math.atan2:

// precompute arctangent
var atans = [];

var index = 0;
for (var y = -canvasHeight / 2; y < canvasHeight / 2; y++) {
    for (var x = -canvasWidth / 2; x < canvasWidth / 2; x++) {
        atans[index++] = Math.atan2(y, x) / Math.PI;
    }
}

Массив atans далее может использовать внутри цикла отрисовки для явного повышения производительности.

Избегайте использования Math.round, Math.floor и parseInt

Пункт на счет использования parseInt имеет смысл в нашем случае:

performance parseInt

Когда вы работаете с canvas, для указания на пиксели необходимо использовать целочисленные координаты (x и y). Однако все ваши вычисления производятся с использованием чисел с плавающей точкой и рано или поздно вам нужно будет конвертировать их в целые числа.

JavaScript предоставляет функции Math.round, Math.floor или даже parseInt для конвертации чисел в целые. Но эти функции делают некоторую дополнительную работу (в частности проверяют диапазоны или проверяют, что значения действительно являются числам; parseInt вообще первым делом конвертирует свой параметр в строку!). Таким образом, внутри моего цикла отрисовки мне нужен более быстрый способ для конвертации чисел.

Вспоминая мой старый код на ассемблере, я решил применить небольшой трюк: вместо использования parseInt достаточно просто сдвинуть число вправо на 0. Среда выполнения переместит число с плавающей точкой из соответствующего регистра в целочисленный и применить аппаратное преобразование. Сдвиг числа вправо на 0 оставит число без изменений и вернет вам назад целочисленное значение.

Исходный код был таким:

u = parseInt((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u);

Новый код выглядит так:

u = ((u < 0) ? texture.width + (u % texture.width) : (u >= texture.width) ? u % texture.width : u) >> 0;

Конечно, это решение требует, чтобы вы были наверняка уверены в корректности передаваемого числа :)

Финальный результат

Применение всех описанных оптимизаций приводит к следующему отчету:

final performance

Как видите, теперь код выглядит хорошо оптимизированным с использованием только ключевых функций.

Мы начали с такого оригинального неоптимизированного туннеля:


Пример на JSFiddle

И пришли к такому результату после оптимизации:


Пример на JSFiddle

[пер.: данные скриншоты сделаны на комьютере переводчика, на котором fps не достигает заветных 60fps, но крутится довольно близко к этому — на 50fps в IE10.]

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

comparing performance

Двигаясь дальше

Помня об этих ключевых моментах, теперь вы готовы к разработке быстрых и плавных игр для современных браузеров и Windows 8!



Введение

intuition game grid

Модуль CSS3 Grid Layout — это один из самых интересных, на мой взгляд, модулей в семействе CSS3. Официальная история модуля в виде черновика спецификации насчитывает сегодня чуть менее года. О предварительном анонсе еще с названием CSS Grid Alignment на TPAC 2010 Владимир Юнев писал еще в декабре 2010. Надо также отметить, что с чуть другим названием и несколько отличным синтаксимом, но с той же сутью, он был заявлен в качестве WD еще в 2007г. Сегодня работы по доводке модуля идут полным ходом, предварительная реализация уже есть в Internet Explorer 10 и есть надежда, что поддержка новых возможностей также появится в будущих версиях других популярных браузеров.

Зачем нужен Grid Layout?

Задача, которую решает модель CSS3 Grid Layout очень проста и понятна любому веб-верстальщику (да и не только ему): предоставить удобный механизм расположения контента по виртуальной сетке.

В отличие от старых табличных подходов, основанных на использовании table, здесь не замусоривается семантика документа и представление четко отделено от содержания. В отличие от различных вариаций блочной верстки со становящимися при сколь-нибудь сложной структуре безумными и многоэтажными float'ами в сочетании с вручную просчитанными отступами, здесь описание стилей становится тривиально простым и прозрачным. В отличии от абсолютного позиционирования с привязкой к координатам, здесь сохраняется гибкость. В отличие от популярных сегодня css/js — библиотек для разметки по сетке путем указания соответствующих классов (взять хоть те же bootstrap или yui css grids), здесь не замусоривается привязка классов.

Сплошное удовольствие! Осталось только дождаться широкой поддерки :) Впрочем, если вы планируете делать приложения в стиле Metro для Windows 8 на html/js, ключевые возможности, описываемые в модуле CSS3 Grid Layout, уже работают — и можно двигаться вперед, используя удобные и практичные механизмы.

Основы

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

<section>
    <header>Title</header>
    <nav>Menu</nav>
    <article>Content</article>
    <aside>Notes</aside>
    <footer>Footer</footer>
</section>

которое необходимо разнести на три колонки примерно вот так:

smaple grid layout

Это классическая задача, поэтому я не буду останавливаться на том, как она решается в случае использования уже традиционных подходов для многоколоночной блочной верстки. Давайте посмотрим, что можно сделать с помощью Grid Layout (я пропускаю браузерные префиксы в примерах, однако, на практике из нужно добавлять — для IE это -ms-).

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

section {
    display: grid;
    grid-columns: 150px 1fr 200px;
    grid-rows: 50px 1fr 50px;
}

section header {
    grid-column: 2;
    grid-row: 1;
}

section nav {
    grid-column: 1;
    grid-row: 2;
}

section article {
    grid-column: 2;
    grid-row: 2;
}

section aside {
    grid-column: 3;
    grid-row: 2;
}
                        
section footer {
    grid-column: 2;
    grid-row: 3;
}

В самом простом варианте все, что нужно сделать, сводится к тому, чтобы указать в контейнере (section), что содержимое внутри него нужно располагать по сетке и задать виртуальную сетку, используя свойства grid-columns и grid-rows (чуть ниже я расскажу о том, какие значения они принимают). Далее для каждого элемента внутри достаточно сказать, где он должен располагаться.

using grid layout sample

И это все! Итого:

  1. Виртуальная сетка: grid-columns и grid-rows в контейнере
  2. Размещение элемента: grid-column и grid-row.

Теперь давайте разбираться в деталях и дополнительных возможностях.

Виртуальная сетка

Треки: строки и столбцы

Для создания виртуальной сетки, по которой в дальнейшем размешаются элементы, необходимо описать треки сетки (Grid Tracks) — строки и столбцы внутри сетки:

#grid {
    display: grid;
    grid-columns: 150px 1fr; /* два столбца */
    grid-rows: 50px 1fr 50px; /* три строки */
}

Линии сетки

Треки располагаются между линиями сетки (Grid Lines), которые, в свою очередь, находятся справа-слева и сверху-снизу от каждого трека:

grid lines

Чтобы задать, как именно элемент будет размещен по сетке, необходимо указать, к какой линии по вертикали и горизонтали он будет привязан:

#item {
    grid-column: 2;
    grid-row: 2;
}

grid item positioning

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

Растяжение на несколько ячеек

Чтобы растянуть элемент на несколько ячеек сетки, можно использовать свойства grid-row-span и grid-column-span:

#item {
    grid-column: 2;
    grid-column-span: 3;
    grid-row: 2;
    grid-row-span:2;
}

grid spanning

По умолчанию, оба свойства установлены в значение 1, что соответствует растяжению доступного пространства от указанной линии до следующей (+1).

Повторяющиеся треки

Часто бывает так, что предполагаемая для использования сетка содержит какой-то характерный шаблон:

#grid {
    display: grid;
    grid-columns:  24px 120px 24px 120px 24px 120px 24px 120px 24px;
    grid-rows: 1fr 24px 1fr 24px 1fr 24px;
}

grid tracks pattern

Было бы удобным описывать такие сетки в более компактном и прозрачном виде — и такая возможность есть! Пример выше можно описать так:

#grid {
    display: grid;
    grid-columns:  24px (120px 24px)[4];
    grid-rows: (1fr 24px)[3];
}

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

===

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

Единицы измерения

Как вы уже могли заметить выше, местами я использовал необычное значение при указании размеров некоторых столбцов и строчек — 1fr. Об этом и других возможных значениях при указании длины мы сейчас и поговорим.

При описании ширины колонок и высоты строк (размеров треков) можно использовать следующие единицы и значения:

  • линейные размеры — стандартные единицы указания длины, определенные в модуле CSS3 Values and Units, например, pt, px, em, vw и т. д.;
  • проценты — размер трека в процентах от размеров контенера с сеткой (хотя если высота или длина сетки зависит от контента, результат будет неопределенным);
  • доли (fraction) — неотрицательное число с последующей единицей измерения fr, размер каждой доли берется пропорциональным указанному числу (см. подробнее ниже);
  • max-content — ключевое слово для указания максимальной длины из максимальных длин элементов в треке;
  • min-content — ключевое слово для указания максимальной длины из минимальных длин элементов в треке;
  • minmax(min, max) — задает диапазон значений (принцип работы можно описать как minmax(p, q) = max(p, min(fill-available, q)) — максимум из нижнего порога и минимума доступного пространства и верхнего порога);
  • auto — ключевое слово, эквивалентное minmax(min-content, max-content).

Доли

Давайте попробуем разобраться, как работают доли (fraction value)? Сетка занимает некоторое пространство по ширине и высоте. Оно может зависеть от контента, быть жестко фиксированным или занимать все доступное пространство во внешнем контейнере. Далее, при описании треков части колонок и строк вы можете явно задать, какого размера они должны быть, для еще какой-то части вы можете указать, что из длина зависит от контента.

Теперь, если из доступной длины, отведенной под сетку по вертикали или горизонтали, вычесть сумму всех таких явных или “контентных” длин, оставшееся пространство распределяется между остальными треками пропорционально указанным в них долях (размер доли, деленный на сумму всех таких долей):

fractions sample

На примере выше это три столбца c ширинами в соотношении 2:1:1 и две строки с высотами в соотношении 5:2.

Пример

В черновике спецификации приводится такой комплексный пример, позволяющий увидеть всю эту схему с разными типами значений сразу:

#grid {
    display: grid;
    grid-columns: 100px 1fr max-content minmax(min-content, 1fr)
}

Здесь определяются следующие линии (треки, они же колонки в данном случае):

  1. Стартовая первая линия.
  2. Линия в 100px от первой.
  3. Еще одна линия на расстоянии ½ оставшегося после всех рассчетов пространства — от второй линии.
  4. И еще одна линия, чье расстояние от третьей равно максимальному из размеров контента элементов в колонке.
  5. Наконец, последняя линия, расположенная от четвертой на расстоянии равном либо минимальной длине элементов в колонке, либо ½ оставшегося пространства, смотря, что больше.

Тут есть еще небольшой нюанс относительно учета долей, которые оказались внутри функции minmax: они учитываются в общей сумме долей только если обозначены в максимальной позиции. Доли, обозначенный в минимальной позиции считаются равными 0px. Подробнее тонкости рассчета смотрите в спецификации.

===

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

Привязка элементов

Теперь, когда элементы “привязаны” к линиям сетки, возникает естественный вопрос: а как же они располагаются между линиями?

alignment to lines

Привязка элементов к границам ячейки контролируется с помощью свойств grid-column-align и grid-row-align. Для управления можно использовать следующие значения:

  • start
  • end
  • center
  • stretch

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

alignment to lines samples

Привязка в колонках (во горизонтали) соответствует направлению текста (например, в арабском языке start будет справа, а end слева). Привязка в строках (по вертикали) совпадает с направлением потока блоков (это, кстати, означает, что в некоторых восточно-азиатских языках строки и столбцы могут поменяться местами).

Управление слоями

Следующий важный момент: расположение элементов внутри сетки с наложениями. Что происходит, если, к примеру, два элемента привязаны к одним и тем же линиями, либо накладываются при расширении на несколько ячеек?

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

grid items overlaping

Чтобы управлять порядком отображения таких слоев, текущая версия спецификации расширяет возможности z-index, позволяя управлять слоями элементов внутри сетки.

Замечание: в предыдущей версии спецификации, на которую опирается текущая версия IE10 (platform preview 5), для этих целей было определено отдельное свойство grid-layer, чтобы не конфликтовать с z-index, однако, далее в процессе обсуждений в рабочей группе это решение было пересмотрено.

Пример использования:

#grid {
    display: grid;
    grid-columns: (1fr)[3];
    grid-rows: (1fr)[4];
}

#A {
    grid-column:1;
    grid-row:3;
    grid-column-span:2;
}

#B {
    grid-column:1;
    grid-row:1;
    grid-row-span:2;
    /* grid-layer: 10; */
    z-index:10;    
    margin-top:10px;
}

#C {
    grid-column:1;
    grid-row:1;
    grid-column-span:2;
    margin-left:50px;
}

#D {
    grid-column:2;
    grid-row:3;
    grid-row-span:2;
    grid-column-span:2;
    margin:10px 0 0 10px;
}

#E {
    grid-column:2;
    grid-row:2;                
    /* grid-layer: 5; */
    z-index:5; 
    margin: -20px;
}

grid layers sample

css3 grid library

Чтобы начать работать с сетками в браузерах, (еще) не поддерживающих модуль CSS Grid Layout, можно воспользоваться js-библиотекой eCSStender и расширением CSS3 Grid Alignment, обеспечивающими поддержку следующих свойств:

  • display: grid
  • grid-columns & grid-rows
  • grid-column & grid row
  • grid-column-span & grid-row-span

Пример работы можно посмотреть тут.

Немного о будущем, или какие еще возможности описаны в модуле

Наконец, давайте немного приоткроем двери в будущее и подглядим, какие еще возможности готовятся в модуле CSS3 Grid Layout.

Учтите, что на сегодня упоминаемые ниже возможности только описаны в черновике спецификации, но еще не имеют поддержки среди выпущенных браузеров (включая предварительные версии). Они также могут измениться в будущем в зависимости от реакции сообщества и рекомендаций рабочей группы CSS.

Указание конечной линии для привязки элемента

Опционально для привязки элемента к сетке можно указать не только начальную, но и конечную линию.

#item {
    grid-column: 2;
    grid-row: 2 4;
}

grid lines with end option

В отличие от механизма “span”, который говорит, на сколько ячеек элемент должен растянуться по горизонтали или вертикали, данная возможность позволяет четко указать, на какой линии элемент должен закончиться. Это также удобно использовать в сочетании с возможностью именования отдельных линий сетки.

Именованные линии сетки

Для удобства линиям можно давать названия. Это делается вставкой в соответствующих местах строковых значений при описании треков (можно давать несколько имен, если это имеет практический смысл, например, с точки зрения семантики разметки):

#grid {
    display: grid;
    grid-columns: "nav" "first" 150px "content" 1fr "last"; 
    grid-rows: "header" 50px "content" 1fr "footer" 50px; 
}

Далее при описании привязки элементов можно ссылаться на эти имена:

#menu {
    grid-column: "nav";
    grid-row: "content";
}

#header {
    grid-column: "content";
    grid-row: "header";
}

#article {
    grid-column: "content";
    grid-row: "content";
}

named grid lines

Также спецификация вводит 4 заранее именованных линии — вертикальные и горизонтальные start и end, фактически, обрамляющие всю сетку. Это полволяет, к примеру, расположить элемент “от второго столбца и до последнего”, не задумываясь об общем количестве столбцов.

#menu {
    grid-column: 1;
    grid-row: start end;
}

#footer {
    grid-column: 2 end;
    grid-row: 3;
}

start and end grid lines

Именованные ячейки и шаблоны

Еще один способ размещения элементов по сетке — заключается в использовании шаблонов, позволяющих описать виртуальную структуру блоков:

#grid {
     display: grid;
     grid-template: "ln"
                    "ma"
                    "ba"
                    "ff";

     grid-columns: auto minmax(min-content, 1fr); 
     grid-rows: auto minmax(min-content, 1fr) auto auto;
}

grid template sample

При этом для размещения элемента с прявязкой к той или иной виртуальной ячейке, достаточно, сослаться на нее соответствующим правилом:

#article {
     grid-cell: "a";
}

Такой подход оказывается особенно удобным, если в зависимости от различных условий, например, разрешения экрана, вам нужно менять расположение элементов и даже переделать саму сетку. В подобной ситуации Grid Layout хорошо сочетается с Media Queries:

@media (orientation: portrait) {
    #grid {
        display: grid;
        grid-template: "ln"
                       "ma"
                       "ba"
                       "ff";

        grid-columns: auto minmax(min-content, 1fr); 
        grid-rows: auto minmax(min-content, 1fr) auto auto;
    }
}

@media (orientation: landscape) {
    #grid {
        display: grid;
        grid-template: "ln"
                       "ma"
                       "mb"
                       "sf";

        grid-columns: auto minmax(min-content, 1fr); 
        grid-rows: auto minmax(min-content, 1fr) auto auto;
    }
}

#article {
     grid-cell: "a";
}

grid template for landscape

Обратите внимание, что привязка самой статьи к именованной ячейке при этом не меняется. (Ручки уже чешутся начать использовать, однако, ждем, когда это все реализуют в браузерах.)

Заключение

Я постарался дать в статье обзор ключевых возможностей модуля CSS3 Grid Layout. Надеюсь, они вдохновляют вас не менее, чем меня. :)

Напомню, что модуль продолжает развиваться, редакторы собирают отзывы от внешнего сообщества и внутри рабочей группы CSS. На действующую реализацию основного функционала можно посмотреть в Internet Explorer 10. Несколько интересных примеров можно найти на ietestdrive.com: The Grid System и Hands On: CSS3 Grid Layout:

Hands On: CSS3 Grid Layout

Также еще раз отмечу, что возможности Grid Layout, уже поддерживаемые движком IE10, также можно использовать при разработке приложений в стиле Metro на HTML/JS для Windows 8.



ie10 metro

Как вы, наверняка, уже знаете, 29 февраля вышла Windows 8 Consumer Preview, а вместе с ней и 5-я platform preview версия Internet Explorer 10. В этой статье я расскажу о том, как подготовиться к новой версии IE. И хотя повествование будет вестись преимущественно вокруг IE10, многие из этих советов применимы и к другим браузерам, в том числе на других платформах.

1. Используете <!doctype html>

Первое, самое-самое-самое первое, с чего надо начать, — это убедиться, что все страницы вы отдаете с правильным doctype. В мире html5 это одна небольшая и очень простая строчка:

<!doctype html>

Это должна быть самая первая строчка, которую вы напишите. Если шаблон вашей страницы генерируется автоматически инструментами разработки и там нет этой строчки, поправьте шаблон. Если ваша CMS отдает страницы без doctype, обновите используемые шаблоны.

Повторю, это очень и очень простой шаг, однако, чрезвычайно важный. Правильный doctype говорит браузеру, как ему обрабатывать контент страницы. IE9 и IE10 при отсутствии doctype переключаются в quirks mode.

В IE9 в режиме совместимости не работают многие новые возможности, включая canvas, audio, video и возможности CSS3. В IE10 в режиме совместимости используется механизм, описанный в спецификации HTML5: это тот же стандартный режим, но с наложенными исправлениями совместимости, описанными в спецификации, что также соответствует поведению других браузеров.

(Напомню, что спецификация HTML5, помимо всего прочего, также содержит правила обработки страниц браузерами, в том числе, в режиме совместимости. Примеры: чувствительность к регистру, формы, изображения расположение контента.)

См. также презентацию Вадима Макеева из Opera Software: Доктайп. Точка (видео).

В каком режиме работает моя страница?

Посмотреть, в каком режиме отображается ваш сайт, можно через DevTools (F12):

document mode

Режимы Standards и Quirks соответствуют спецификации HTML5.

IE10 также поддерживает quirks mode из IE5 для сайтов без doctype, работающих в режиме совместимости (compatibility view), и сайтов, явно указавших необходимость такого режима через мета-теги с X-UA-Compatible:

<meta http-equiv="X-UA-Compatible" content="IE=5">

2. Решите проблемы совместимости

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

  • Сайт указан в списке совместимости, используемом IE. Российских сайтов на сегодня в этом списке около 7. Как правило, это популярные сайты, для которых разработчиками выставлен режим совместимости, либо пользователи часто делают это вручную для улучшения отображения. Процедура исключения из списка для владельцев ресурсов описана на MSDN.
  • Используется неправильный doctype.
  • Тот или иной режим прописан на странице через мета-тег X-UA-Compatible.

И даже, если с doctype и режимом совместимости все в порядке, это не означает, что сайт будет полностью корректно работать в IE10. Например, сайт может использовать так называемый browser sniffing, определяя браузер по строке user agent или другим признакам и отдавая разным браузерам разный код.

Или, скажем, сайт может использовать устаревшую библиотеку, которая использовала баг или специальных хак в старых версиях IE, но перестала корректно работать в новой версии с исправленным ошибками.

Таких причин может быть множество, и, чтобы определить возможные источники ошибок было проще, у нас есть специальный инструмент — инспектор совместимости (compat inspector).

Инспектор совместимости

Инспектор представляет собой библиотеку на JavaScript, которую можно легко добавить на свою страницу:

<script src="http://ie.microsoft.com/testdrive/HTML5/CompatInspector/inspector.js"></script>

После этого загружаемый скрипт анализует страницу на предмет возможных проблем и выдает по результатам сводку в виде вот такого счетчика:

compat inspector on habr

Нажав на счетчик, можно увидеть конкретные обнаруженные проблемы и рекомендации по их исправлению:

compat inspector results on habr

Например, в профиле пользователя на Хабре инспектор совместимости говорит, что, в соответствии со стандартом DOM Level 3 Core, метод createElement не поддерживает текстовые аргументы с угловыми скобками. Такая возможность использовалась в старых версиях MooTools, о чем также подсказывает инспектор и рекомендует обновиться до свежей версии. Простая проверка показывает, что на хабре действительно используется старая версия MooTools.

Или вот, например, многие пользователи Windows 8 Consumer Preview жалуются, что Яндекс.Карты не работают в IE10. Инспектор подсказывает, что, вероятно, сайт использует проверку user agent и предлагает это проверить, отдав сайту другой user agent, прикинувшись тем самым другим браузером (это делается установкой одной галочки в checkbox Verify). После этого карты начинают работать.

Как “добавить” инспектор совместимости на чужой сайт?

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

Для этого вам нужно установить Fiddler и добавить в него сниппет (внести правки в правила обработки через Rules -> Customize Rules…). После этого Fiddler сможет вставлять код инспектора в тело страниц локально на вашем компьютере.

Еще раз ссылки: инспектор совместимости и инструкция.

3. Определяйте возможности, а не браузер

Эта тема обсуждается много и тем не менее будет не лишним напомнить о ней еще раз. Чем более стандартным становится поведение браузеров, чем больше новых возможностей появляется и чем быстрее и автоматичнее обновляются браузеры, тем более важным становится, чтобы веб-разработчики, делая свои сайты и веб-приложения, опирались на возможности браузеров и их наличие, а не на конкретные браузеры и их версии.

feature detection vs. browser detection

Да:

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

Нет:

  • Проверка конкретных браузеров. Не используйте идентификаторы браузера (например, строку user agent) для подстройки поведения.
  • Неподкрепленные предположения. Если вы проверили какую-то одну возможность, это еще не означает, что будут работать другие.

Подстройка под конкретный браузер может казаться разумным решением лишь в краткосрочной перспективе, однако, в реальных решениях для конечных пользователей это сильно ограничивает адаптивные возможности страниц, начиная с того, что сайт можете перестать работать с выходом новой версии браузера (новые возможности, исправления ошибок, изменения в стандартах и т.п.), и заканчивая тем, что используемые при этом обходные пути могут оказаться излишними и неэффективными.

Использование modernizr — хорошая отправная точка.

См. также статьи Same Markup: Writing Cross-Browser Code и Browser and Feature Detection.

Условные комментарии

Многие из вас, наверняка, знакомы с условными комментариями для IE, позволяющими вставлять тот или иной код для разных версий IE. Думаю, также многие сталкивались с ситуациями, когда условные комментарии были вставлены некорректно, в результате чего, например, сайт знал только о IE7 и 8, всем остальным версиям IE (включая новые) отдавая разметку для IE6.

Знакомо? У меня для вас новость:

<!--[if IE]> 
Такой контент игнорируется в IE10 и других браузерах.
В старых версиях IE он отображается как часть страницы.
<![endif]-->

Все верно: IE10 игнорирует условные комментарии. Вы можете продолжать использовать их для предыдущих версиях IE, однако, для более современных браузеров следует использовать определение возможностей браузера (feature detection).

4. Предусмотрите режим без плагинов

Если для вас это новость, то добро пожаловать в суровую реальность. Важно, чтобы ваш сайт или ваше веб-приложение оставались функциональными и решали поставленные перед ними задачи даже при отключенных плагинах. Это касается Flash, Silverlight и любых других расширений (например, основанных на ActiveX).

Если вы разрабатывали сайты для iOS-устройств, такая ситуация должна быть вам знакома. В Windows 8 IE10 представлен в двух режимах: для рабочего стола со всеми установленными пользователем расширениями и в metro-режиме с отключенными расширениями.

ie10 metro youtube

Из metro-режима пользователь может легко переключиться в десктопный режим, однако, бессмысленно выводить ему сообщения вроде “установите плагин Flash Player” — ему это не поможет.

ie10 metro rutube

Что рекоммендуется сделать:

  • Предусмотреть альтернативное решение для реализации функционала, требующего сегодня плагинов. Например, для проигрывания Audio/Video и графики можно использовать возможности HTML5; для рекламных баннеров предоставить альтернативу в виде изображения.
  • Пересмотреть работу сайта, если она сильно завязана на использование плагинов: двигаться в сторону современных веб-стандартов, либо переключиться на создание альтернативы в виде приложения в стиле Metro для Windows Store.
  • При необходимости использования расширений выдать корректное сообщение, предложив пользователю переключиться в десктопный режим.

См. также статьи Browsing Without Plug-ins и Moving to Standards-based Web Graphics in IE10.

5. Добавьте префиксы, где надо, и внимательно следите за стандартами

Я не буду обсуждать, хорошо ли, плохо ли использовать префиксы, однако, на сегодня это данность. Браузерные префиксы есть и они позволяют разным производителям реализовывать поддержку развивающихся и не очень стабильных возможностей до окончательного принития той или иной спецификации (или хотя бы достижения статуса Candidate Recommendation) и получать отзывы на реализацию от сообщества.

Если вы хотите использовать новые возможности в IE10, не забудьте обновить свой код, добавив свойства и методы с нужными префиксами для всех основных движков (например, -ms- для css-свойств в IE) и безпрефиксный вариант на будущее для более-менее стабильных возможностей.

Напомню, что очень много новых возможностей было добавлено уже в IE9, и еще больше из в IE10:

ie10 features

О том, как разрабатывать с использованием префиксов смотрите, например, статью A Best Practice for Programming with Vendor Prefixes и библиотеку –prefix-free от Lea Verou.

6. Проектируйте под разные экраны

В своей статье “На парусах HTML5. Как новые технологии меняют современный веб” я уже рассказывал о том, что мечта многих и многих веб-разработчиков о том, что размеры и разрешения экранов будут только расти рассыпалась в пух и прах, столкнувшись с мобильными и планшетными сценариями, популярность которых, уже заставила пересмотреть многие подходы в разработке веб-сайтов и веб-приложений.

Responsive design — это два заветных слова, по которым вам нужно искать, чтобы узнать, как добиться гибкости по отношению к разным разрешениями и размерам экрана.

win8 cp

Применительно к Windows 8, нужно быть готовым к двум основным моментами:

  1. Большому разнообразию устройств совершенно разного форм-фактора: от относительно небольших планшетов до больших экранов мониторов и панелей.
  2. Snapped-режиму работы приложений в стиле metro, включая IE10.

Разнообразие экранов

win8 screens

Рекомендуемый минимальный размер экрана для Windows 8 — 1366x768, в этом случае будет работать функция Snap, о которой рассказывается ниже. Для приложений в стиле Metro минимальное требование — 1024x768. Сайт в IE10, раборающем в Metro-режиме, занимает весь экран целиком.

Однако, учитывая, что десктопный браузер может работать и при меньших разрешениях (например, 800x600 или 1024x600), необходимость учета небольших разрешений по-прежнему остается.

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

Аналогичная история и с размером графики и необходимостью ее масштабирования на больших экранах (используя разные наборы растровой графики, либо используя SVG).

Responsive design. CSS3 Media Queries. Bing.

См. также статьи Уголок CSS: Медиа-запросы CSS3 и Ответ на разные устройства с медиа-запросами CSS3.

Snapping (“парковка”)

Эта история в техническом плане в чем-то похожа на задачу разработки общего решения для десктопных и мобильных сценариев. Функция Snap в Windows 8 позволяет закрепить сбоку (справа или слева) приложение в стиле Metro, чтобы оно было постоянно на экране:

ie10 metro snapped

В таком состоянии ширина простанства, отводимого под сайт, равняется 320px, что похоже на мобильные сценарии и также доступно для подстройки отображения с помощью CSS3 Media Queries.

См. также статью Device adaptation.

7. Проектируйте под пальцы, мышь и клавиатуру

Наконец, не забывайте о том, как с вашим сайтом будет взаимодействовать пользователь (кстати, независимо от того, использует он десктопный или metro-режим Internet Explorer). Он может управлять мышкой и клавиатурой, а может и пальцами.

touch input

Последнее означает, что самое время задуматься, удобно ли работать с вашим сайтом или веб-приложением, используя естественный ввод?

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

С технической точки зрения, рекомендую обратить внимание на эти две статьи: Touch Input for IE10 and Metro style Apps и Handling Multi-touch and Mouse Input in All Browsers.

Резюме

Повторю еще раз необходимые пунктики, чтобы быть во всеоружии в нужный момент:

  1. Используйте правильный doctype
  2. Проверьте режим рендеринга и возможные проблемы с помощью инспектора совместисти
  3. Переключитесь на подход “feature detection”
  4. Продумайте, что увидит пользователь при отсутствии плагинов
  5. Добавьте префиксы для экспериментальных возможностей
  6. Подготовьтесь к разным размерам экрана и режимам отображения
  7. Позвольте пользователю удобно работать пальцами с вашим сайтом или веб-приложением.


Две статьи, которые стоит почитать:

The Vendor Prefix Predicament: ALA’s Eric Meyer Interviews Tantek Çelik by Eric Meyer, Tantek Çelik

I think it is a fair question to ask: what went wrong here, how did we end up with a WebKit mobile web monoculture despite at least some standards evangelism to the contrary?

…Third, we’ve seen strong evangelism of -webkit- features by Apple and Google, most notably as part of “HTML5” presentations and demo sites.

Every Time You Call a Proprietary Feature “CSS3,” a Kitten Dies by Lea Verou

Any -webkit- feature that doesn’t exist in a specification (not even an Editor’s draft) is not CSS3. Yes, they are commonly evangelized as such, but they are not part of CSS at all.

И не то, чтобы я против вебкитов, эпплов или гуглов, скорее даже за, но… это реально удручающее зрелище, когда авторы статей и их переводчики у нас (например, на хабре), зачастную совершенно не заботятся о том, чтобы проверить работает ли нужное свойство в других браузерах с их префиксами или вообще без префиксов.

Печально это все :(



Mike Downey on the HTML5 Player Framework.



Мы уже ранее затрагивали возможности модуля CSS3 Backgrounds and Borders, рассматривая работу с тенями (box-shadow). Сегодня мы немного поговорим о еще одной интересной возможности — использовании нескольких изображений в фоне.

Композиция фонов

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

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

Могут быть и другие разумные причины :)

Классический подход

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

<div class="sample1">
    <div class="sea">
        <div class="mermaid"><div class="fishing"></div></div>                
        <div class="fish"></div>
    </div>
</div>

“fishing” внутри “mermaid” исключительно для демонстрационных целей.

Теперь немного стилей:

.sample1 .sea, .sample1 .mermaid, .sample1 .fishing {
    height:300px;
    width:480px;
    position: relative;
}
            
.sample1 .sea {
    background: url(media/sea.png) repeat-x top left;          
}
            
.sample1 .mermaid {
    background: url(media/mermaid.svg) repeat-x bottom left;
}
        
.sample1 .fish {
    background: url(media/fish.svg) no-repeat;
    height:70px;
    width:100px;
    left: 30px;
    top: 90px;
    position: absolute;
}
            
.sample1 .fishing {
    background: url(media/fishing.svg) no-repeat top right 10px;
}

Результат:

Sample 1. Classic nested divs

В данном примере три вложенных фона и один блок с рыбками, расположенный рядом с “фоновыми” блоками. В теории, рыбок можно перемещать, например, с помощью JavaScript или CSS3 Transitions/Animations.

Кстати, в этом примере для “.fishing” используется новый синтаксис для позиционирования фона, также определенный в CSS3 (выделил желтым). На текущий момент он поддерживается в IE9+ и Opera 11+, но не поддерживается в Firefox 10 и Chrome 16. Так что пользователи последних двух браузов поймать рыбку пока не смогут.

Поехали дальше. Как упростить эту конструкцию?

Множественные фоны

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

<div class="sample2">
    <div class="sea">                     
        <div class="fish"></div>
    </div>
</div>

И соответствующие стили:

.sample2 .sea {
    height:300px;
    width:480px;
    position: relative;
    background-image:  url("media/fishing.svg"), url("media/mermaid.svg"), url("media/sea.png");  
    background-position: top right 10px, bottom left, top left;
    background-repeat: no-repeat, repeat-x, repeat-x ;
}
            
.sample2 .fish {
    background: url("media/fish.svg") no-repeat;
    height:70px;
    width:100px;
    left: 30px;
    top: 90px;
    position: absolute;
}

Для определения множественных изображений необходимо использовать правило background-image, перечисляя отдельные изображения через запятую. Дополнительными правилами, также списком, можно задать позиционирование, повторы и другие параметры для каждого из изображений. Обратите внимание на порядок перечисления изображений: слои перечисляются слева направо от самого верхнего к самом нижнему.

Результат полностью совпадает:

Sample 2. Using multiple backgrounds

Одним правилом

Если рыбок не нужно выделять в отдельный блок для последующих манипуляций, всю картинку можно переписать одним простым правилом:

<div class="sample3">
    <div class="sea"></div>
</div>

Стили:

.sample3 .sea {
    height:300px;
    width:480px;
    position: relative;
    background-image:  url("media/fishing.svg"), url("media/mermaid.svg"), url("media/fish.svg"), url("media/sea.png");  
    background-position: top right 10px, bottom left,  30px 90px, top left;
    background-repeat: no-repeat, repeat-x ;
}

Картинку с результатом приводить не буду — поверьте, она совпадает с двумя картинками выше. А вот на стили обратите внимание еще раз, особенно на “background-repeat” — согласно спецификации, если часть списка в конце пропущена, то браузер должен повторить указанный список нужное число раз, чтобы соответствовать количеству изображений в списке.

В данном случае, это эквивалентно такому описанию:

background-repeat: no-repeat, repeat-x, no-repeat, repeat-x;

Еще короче

Если вы помните CSS 2.1, в нем определена возможность описывать фоновые изображения в краткой форме. Как на счет множественных изображений? Это также возможно:

.sample4 .sea {
    height:300px;
    width:480px;
    position: relative;
    background: url("media/fishing.svg") top right 10px no-repeat, 
                url("media/mermaid.svg") bottom left repeat-x,
                url("media/fish.svg") 30px 90px no-repeat,
                url("media/sea.png") repeat-x;                
            }

Но обратите внимание, что теперь пропускать просто так значения нельзя (если только они не совпадают со значением по умолчанию). Кстати, если вы хотите задать цвет фонового изображения, это надо делать в самом последнем слое.

Динамичные изображения

Если композиция статична или динамична не более, чем в зависимости от размеров контейнера, тогда множественные фоны очевидно упрощают конструкцию страницы. А что делать, если с отдельными элементами композиции нужно работать независимо из javascript (перемещать, прокручивать и т.п.)?

Кстати, вот пример из жизни — тема с одуванчиком в Яндексе:

Yandex

Если вы залезете в код, вы увидите там примерно следующее:

<div class=b-skin-bg sizcache="272" sizset="0">
	<div class=b-fluff-bg sizcache="272" sizset="0">
		<div class=b-fluff__sky sizcache="272" sizset="0">
			<div style="background-position: 3244px 0px" class=b-fluff__cloud></div>
			<div style="width: 1200px" class=b-max-width sizcache="214" sizset="0">
				<div class=b-fluff__placeholder sizcache="302" sizset="0">
					<div style="bottom: 105px; display: none; left: 940px" class="b-fluff__item b-fluff_item_3" jQuery1328289994769="30"></div>
					<div style="bottom: 50px; display: none; left: 879px" class="b-fluff__item b-fluff_item_3" jQuery1328289994769="31"></div>
					<div style="bottom: 105px; display: none; left: 940px" class="b-fluff__item b-fluff_item_3" jQuery1328289994769="32"></div>
					...
				</div>
			</div>
		</div>
	</div>
</div>

Блоки с классами “b-fluff-bg”, “b-fluff__cloud” и “b-fluff__item” содержат фоновые изображения, накладывающиеся друг на друга. Причем фон с облаками постоянно прокручивается, а одуванчики летают по экрану.

Можно ли это переписать с использованием множественных фонов? В принципе, да, но при условии 1) поддержки этой возможности в целевых браузерах и… 2) читайте дальше ;)

Как добавить динамики множественным фонам? В такой ситуации оказывается удобным, что во внутреннем представлении браузер раскидывает отдельные параметры фоновых изображения по соответствующим свойствам. Например, для позиционирования есть “background-position”, и для сдвигов достаточно изменять только его. Однако имеется и плата за использование множественных изображений — в этом правиле (и любом аналогичном) необходимо перечислять позицию для всех фонов, заданных для вашего блока, и нельзя сделать это выборочно.

Чтобы добавить нашему фону с рыбками анимации, можно использовать такой код:

$(document).ready(function() {
    var sea = $(".sample5 .sea")[0];
    var fishesX = 30;
    var fishesY = 90;
    var fishX = 0;
    var fishY = 0;
    var mermaidX = 0;
    var t = 0;

    function animationLoop() {
        fishesY = 90 + Math.floor(30 * Math.sin(t++ / 180.0));
        if(--fishesX < 0) fishesX = 480;
        mermaidX += 0.5;
        if(mermaidX > 480) mermaidX = 0;
        fishY = -10 + (10 * Math.cos(t * 0.091));
        fishX = 10 + (5 * Math.sin(t * 0.07));

        sea.style.backgroundPosition = "top " + fishY + "px right " + fishX + "px, " + mermaidX + "px bottom," + fishesX + "px " + fishesY + "px, top left";


        window.requestAnimFrame(animationLoop);
    }
    animationLoop();
});

где

window.requestAnimFrame = (function() {
    return 
        window.requestAnimationFrame || 
        window.msRequestAnimationFrame ||
        window.mozRequestAnimationFrame || 
        window.oRequestAnimationFrame || 
        window.webkitRequestAnimationFrame || 
        (function(callback) { window.setTimeout(callback, 1000 / 60); });
})();

Результат (видео):

Sample 5. Animating

И, кстати, анимации также можно делать через CSS3 Transitions/Animations, но это тема для отдельного обсуждения.

Паралакс и интерактив

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

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

sea.style.backgroundPosition = "top " + fishY + "px right " + fishX + "px, " + mermaidX + "px bottom," + fishesX + "px " + fishesY + "px, top left";

Уверен, что это можно обернуть в удобный код на javascript, который возьмет на себя виртуализацию взаимоотношений с отдельными слоями, оставляя при этом html-код страницы максимально чистым.

Что там с совместимостью?

Все современные версии популярных браузеров, включая IE9+, поддерживают множественные изображения (можно сверяться, например, с сaniuse).

Вы также можете использовать Modernizr, чтобы предоставлять браузерам, не поддерживающим множественные фоны, альтернативные решения. Как написал Chris Coyier в заметке о порядке слоев при использовании множественных фонов, делайте примерно так:

.multiplebgs body {
   /* Awesome multiple BG declarations that transcend reality and impress chicks */
}
.no-multiplebgs body {
  /* laaaaaame fallback */
}

Если вас смущает использование JS для предоставления обратной совместимости, вы можете просто дважды объявить background, правда, это тоже имеет свои минусы в виде возможной двойной загрузки ресурсов (это зависит от реализации обработки css в конкретном браузере):

/* multiple bg fallback */
background: #000 url(...) ...;
/* Awesome multiple BG declarations that transcend reality and impress chicks */
background url(...), url(...), url(...), #000 url(...);

p.s. В тему: не могу не вспомнить феноменальную статью про принцип цикады.