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

Поль Руже: Это блогозапись нашего гостя, Маркуса Стэнджа. Маркус обыкновенно работает над реализацией темы оформления 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», естественно), так что отзывы о нашей реализации помогут нам улучшить будущий стандарт.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *