Pull to refresh

Firefox 4: отрисовка произвольных элементов в качестве фонов посредством -moz-element

Reading time 8 min
Views 1.8K
Original author: Markus Stange
Поль Руже:  Это блогозапись нашего гостя, Маркуса Стэнджа. Маркус обыкновенно работает над реализацией темы оформления Firefox для Маков, но на сей раз он совершил небольшую окольную прогулку по движку макетирования в Gecko, реализуя -moz-element.

В четвёртой бета-версии нового Файерфокса мы представляем вам новое расширение CSS-свойства background-image: возможность отрисовывать произвольные элементы в качестве фонов, используя -moz-element(#elementID).
<p id="myBackground1" style="background: darkorange; color: white; width: 300px; height: 40px;">
    This element will be used as a background.
    <!-- Этот элемент послужит фоном. -->
</p>
<p style="background: -moz-element(#myBackground1); padding: 20px 10px; font-weight: bold;">
    This box uses #myBackground1 as its background!
    <!-- Этот прямоугольник использует #myBackground1 в качестве фона! -->
</p>
[показать пример]

Изображение -moz-element() срабатывает совершенно так же, как и привычное изображение url(). Это значит, что оно управляется всеми привычными свойствами фонов: background-position, background-repeat, и даже background-size.

Используя background-size, вы можете создать миниатюру (thumbnail) того элемента, который используется в качестве фона — вот пример:
<ul id="thumbnails">
    <li style="background-image: -moz-element(#slide-0)"></li>
    <li style="background-image: -moz-element(#slide-1)"></li>
    <li style="background-image: -moz-element(#slide-2)"></li>
    <li style="background-image: -moz-element(#slide-3)"></li>
</ul>
#thumbnails li {
    width: 160px;
    height: 120px;
    background-repeat: no-repeat;
    background-size: contain;
}
[показать пример]

Имейте в виду три обстоятельства насчёт -moz-element:
  1. Фон — живой: что бы ни происходило в том элементе, который указан в свойстве -moz-element, фон будет отражать его изменения. Он также будет отображать выделение текста курсором, моргание текстового курсора, и так далее.
  2. Фон — просто видимость. На нём нельзя жмякнуть мышою и попасть к элементу-первоисточнику изображения. Так он устроен.
  3. Фоном можно сделать какой угодно элемент HTML. Даже <iframe>:

    [показать пример]

    Даже <video>:

    [показать пример]

    И даже холст <canvas>:

    [показать пример]
Использование холста в качестве фона может прийтись кстати во многих приложениях. Например, если вы перекрашиваете в сепию изображения CSS-фонов во браузере, то теперь не понадобится дополнительно перекодировать изображение, обработанное на холсте, получая URI «data:». Вместо этого можно станет сделать фоновым изображением сам холст.

Использование холста в качестве фонового изображения также поддерживает и Webkit, посредством -webkit-canvas().

Зацикливание отрисовки


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

Сокрытие элемента-первоисточника


Подчас вы можете не захотеть того, чтобы первоначальный элемент был видимым — а оставить на виду только фон -moz-element. И что же тогда придётся сделать вам? Просто задать «display: none» или «visibility: hidden» элементу-первоисточнику не получится, так как тогда нечего будет рисовать и на фоне -moz-element, и оттого он сделается прозрачным.

Вместо этого вам нужно предотвратить отрисовку элемента на экране, не пряча его впрямую. Один из возможных способов — поместить этот элемент внутрь другого, и задать этому другому «height: 0; overflow: hidden;» в CSS.

Три типа элементов являются исключением из этого правила: изображения, холсты и видеозаписи. Этим видам элементов дозволяется иметь свойство «display: none» и всё равно использоваться в -moz-element. Более того: они могут даже не содержаться в DOM основного документа.

Новый DOM API:
document.mozSetImageElement


К объекту document мы добавили новый метод: document.mozSetImageElement(ID_элемента, элемент).

Рассмотрим следующие две строки кода:
var slide5 = document.getElementById("slide-5");
document.mozSetImageElement("current-slide", slide5);
Теперь все те элементы, которые наделены свойством background-image: -moz-element(#current-slide), будут отображать элемент, имеющий ID slide-5 даже если существует элемент, имеющий ID current-slide!

Вызов document.mozSetImageElement("current-slide", null) отменяет действующее переопределение.

Такой API может пригождаться в разнообразных практических ситуациях. Одну из них я косвенно упомянул в предыдущем подразделе: посредством mozSetImageElement можно пользоваться холстами и изображениями, не являющимися частью дерева DOM:
var img = new Image();
img.src = "my_image.png";
document.mozSetImageElement("image", img);

var canvas = document.createElement("canvas");
canvas.width = canvas.height = 100;
var ctx = canvas.getContext("2d");
//… далее рисуем через ctx …
document.mozSetImageElement("canvas", canvas);
(показать пример)

Другая ситуация, в которой вызов mozSetImageElement оказывается полезен — создание библиотеки джаваскриптовых функций. Например, у вас может получиться вот такая функция:
var runningNumber = 0;
function addReflectionToElement(reflectedElement) {
    var referenceID = "reflected-element-" + runningNumber++;
    var reflection = document.createElement("div");
    reflection.className = "reflection";
    reflection.style.backgroundImage =
        "-moz-element(#" + referenceID + ")";
    document.mozSetImageElement(referenceID, reflectedElement);
    //… и теперь вставляем reflection в DOM …
}
Следуя таким путём, вы можете уменьшить побочный эффект от применения библиотечной функции: ей не придётся манипулировать ID того элемента, который ей передан.

И наконец, mozSetImageElement также позволяет ссылаться на элементы других документов (например, внутри <iframe>); при этом, разумеется, действуют обычные требования ко взаимодействию документов (единство их происхождения).

-moz-element для серверов отрисовки SVG: узоры и градиенты


Если вам доводилося писать SVG вручную, то вам наверняка знакомо понятие серверов отрисовки (paint servers): это те элементы, узоры и (или) градиенты, которые приходится указывать в атрибутах fill или stroke в тех случаях, когда однотонная заливка не годится. Теперь их можно использовать также и в фонах элементов HTML, используя -moz-element:
<p style="background: -moz-element(#pattern),
            -moz-element(#gradient);
        padding: 10px; color: white">
    This element has both types of SVG paint servers
    in its background: a pattern and a gradient.
    <!-- Фон этого элемента использует оба типа
    серверов отрисовки SVG: и узор, и градиент. -->
</p>

<svg height="0">
    <linearGradient id="gradient" x2="0" y2="1">
        <stop stop-color="black" offset="0%"/>
        <stop stop-color="red" offset="100%"/>
    </linearGradient>
    <pattern id="pattern" patternUnits="userSpaceOnUse"
                width="60" height="60">
        <circle fill="black" fill-opacity="0.5"
                cx="30" cy="30" r="10"/>
    </pattern>
</svg>
[показать пример]

Обратите внимание, что благодаря нашему новому парсеру HTML5 нам не обязательно использовать XHTML для того, чтобы заработал внедрённый код SVG.

Вышеописанная возможность дублирует ужé существующие возможности градиентов CSS и иллюстраций SVG, однако и она бывает необычайно полезною в некоторых ситуациях — например, в анимациях. Предположим, вам надобно создать индикатор процесса, снабжённый анимированным градиентом, наподобие вот этого:

[индикатор процесса]

Вы могли бы достичь желаемого, используя градиент CSS и некоторый джаваскрипт, периодически обновляющий свойство background-position. Однако также вы могли бы использовать градиент SVG, анимированный посредством SMIL, и оттого не требующий вообще никакого джаваскрипта:
<div
        style="background: -moz-element(#animated-gradient);">
</div>

<svg height="0">

    <linearGradient id="animated-gradient" spreadMethod="reflect"
            gradientUnits="userSpaceOnUse"
            x1="16" x2="24" y2="0">
        <animate attributeName="x1" values="16; 0" dur="350ms"
                repeatCount="indefinite"/>
        <animate attributeName="x2" values="24; 8" dur="350ms"
                repeatCount="indefinite"/>

        <stop stop-color="#0F0" offset="0"/>
        <stop stop-color="#0D0" offset="100%"/>
    </linearGradient>

</svg>
(показать пример)

Того же можно достигнуть и CSS-анимациями, однако, пока они не реализованы в Gecko, вы можете следовать вышеописанным путём.

Поддержка изображений SVG в качестве фонов CSS (баг 276431) также вскоре будет обеспечена.

И нате ещё пример: Pacman на CSS и SVG.

Приложения


У меня есть ещё пара предложений насчёт использования -moz-element:

Отражения


Что такое отражение?
#reflection {
    /* Отражение — это копия первоначального элемента… */
    background: -moz-element(#reflected-element)
                bottom left no-repeat;

    /* … отражённая сверху вниз … */
    -moz-transform: scaleY(-1);

    /* … и с постепенным затемнением от верха к низу. */
    mask: url(#reflection-mask);
}
[показать пример]

Так как к отражению мы можем применять какие угодно стили, то можно порождать эффекты наподобие анимированных кругов на воде.

Шикарные переходы между слайдами


В этом демонстрационном примере мне хочется достигнуть такого зрелищного перехода между соседними слайдами, который выглядит так, как если бы верхнюю половину предыдущего слайда завёртывали книзу, открывая под ним следующий слайд:
На этом месте во блоге Mozilla Hacks стоит видеозапись, подключённая элементом <video>, так что показать её на Хабрахабре не получится.
Как бы вы реализовали эту задумку? Несомненно, понадобилось бы какое-нибудь преобразование, однако преобразование какого именно элемента? Верхней половине слайда требуется другое преобразование, чем нижней, так что просто наложить преобразование на весь слайд не получится.

В итоге я создал четыре новых элемента (#previousUpper, #previousLower, #nextUpper и #nextLower) и поместил их в отдельный контейнер (под названием #transition), который становится видимым только во время перехода от слайда к слайду. Тогда я присваиваю им правильные размеры и налагаю соответствующую часть изображения от предыдущего или последующего слайда, используя background-image: -moz-element(#previous/nextSlide) и правильное значение свойства background-position. А затем я налаю на эти вспомогательные элементы желаемое преобразование.

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

Ещё?


У меня сейчас закончились замыслы примеров -moz-element, хотя, конечно, с этим свойством ещё многое можно проделать. Теперь ваш черёд!

Благодарности


Бóльшую часть благодарности по этому поводу заслуживает Robert O’Callahan, который сготовил первоначальную реализацию ещё в 2008 году. После своих первоначальных экспериментов, правда, ему пришлось плотно работать над более важными делами, так что патчи его пролежали в бездействии почти год, покуда (в апреле 2009 года) он не начал обсуждение в группе новостей о том, каким должен стать подходящий API. Вскоре после этого Рё Кавагучи возродил труды Роберта и все последние недели своей стажировки в Мозилле посвятил им. Ещё через год я подготовил этот патч на рецензирование (review) и провёл его через итоговые стадии, вплоть до включения в код Файерфокса.

Напоследок изложу такое же предупреждение, которое mozRequestAnimationFrame касалось: и -moz-element, и document.mozSetImageElement являются экспериментальными API. Мы не гарантируем их бесконечную поддержку, и мы не пропагандируем их использование. Мы реализовали их для того, чтобы людям можно было экспериментировать с ними, а к нам поступали бы отзывы. Мы предложим эти API в качестве стандарта (но без префикса «moz», естественно), так что отзывы о нашей реализации помогут нам улучшить будущий стандарт.
Tags:
Hubs:
+62
Comments 36
Comments Comments 36

Articles