CSS-функция element()
Перевод статьи CSS element() function с сайта iamvdo.me, с разрешения автора — Венсана де Оливейры.
В июле я написал про продвинутые приемы работы с CSS-фильтрами, в частности, backdrop-filter и filter()
. Сегодня я хочу поделиться намного более потрясающей возможностью CSS. Но прежде чем начать, хочу предупредить: она работает только в Firefox, другие браузеры пока не торопятся ее поддерживать. Может быть, скоро это изменится. Очень на это рассчитываю. Так что прочитайте и передайте другим.
Если вы читаете эту статью не в Firefox, наверное, вам стоит переключиться на него, чтобы увидеть примеры в действии. На всякий случай я добавил видео.
element()
CSS-модуль изображений в качестве значений и замещаемого контента 4-го уровня вводит функцию element()
. Эта функция раньше была определена в модуле 3-го уровня, так что Firefox поддерживает ее еще с 4-й версии (май 2011 г.!). Попросту говоря, эта функция отображает часть сайта как картинку в реальном времени. Картинку. В. Реальном. Времени! Едва DOM-элемент отобразится в браузере, у вас появится его изображение. Стоит элементу измениться, и это изображение тотчас изменится следом — даже если вы всего лишь выделите текст.
Когда я впервые открыл это свойство в 2011-м, я не поверил глазам. Как же это круто? Как такое вообще возможно?
Что ж, это работает, и синтаксис очень прост. Надо просто сослаться на элемент, который надо отобразить, по его атрибуту id
. Например, вот текст и картинка в div#css-source
. «Живое» изображение этого элемента можно использовать как фон для div#css-result
.
<div id="css-source"> <p>Lorem ipsum</p> <img src="" alt=""> </div> <div id="css-result"></div>
#css-result { background: element(#css-source); background-size: 50% 50%; }
Раз element()
создает картинку, то ей можно управлять с помощью любых привычных вам CSS-свойств, вроде background
, background-repeat
, background-size
и т.п.
Вот живой пример того, о чем я говорю
See the Pen Basic demo element() by Ilya Streltsyn (@SelenIT) on CodePen.
Работающий результат в Firefox
Имейте в виду, что так обратиться можно к любой части страницы, даже к целой странице, если нужно. Но всё же будьте внимательны, чтобы случайно не сделать элемент фоном для его собственного потомка, а то элементы отобразятся по нескольку раз. К счастью, Firefox нормально справляется с рекурсивными ссылками. (Прим. перев. 06.07.2021: похоже, в какой-то момент эту проверку циклических зависимостей сделали строже, отчего часть оригинальных примеров, где элемент был фоном собственных псевдоэлементов, перестала работать. Сейчас примеры в переводе исправлены с учетом этого.)
Функция element()
с легкостью переводит CSS-разработку на новый уровень. Вот несколько идей, что приходят мне на ум (некоторые из них я уже использовал за последние 4 года):
- когда для продвинутых эффектов бывает нужен дубликат контента
- «живые превью» предыдущего/следующего слайдов в слайдшоу
- «лупа» над картинкой, например, на странице товара в интернет-магазине
- анимированный фон, с помощью CSS-анимаций или со ссылкой на видео, canvas или SVG
- имитация
backdrop-filter
илиfilter()
- «водяной знак» с различными фонами (на основе идеи Лии Веру)
- и всё, о чем вы сейчас подумали сами;)
Кое-что нам на заметку:
- в Firefox пока нужен префикс:
-moz-element()
- влияние на скорость отрисовки при нескольких ссылках. Не так плохо, как у CSS-фильтров, но всё-таки об этом стоит задумываться
- на CanIUse есть страничка о поддержке этой функции
- баг в Chromium
- баг в WebKit
- в планах на реализацию в IE пока не упоминается
Отражения
Все мы знаем, что мода на отражения в веб-дизайне прошла (привет, веб 2.0!), но это отличное упражнение, чтобы лучше понять element()
. Следующий пример состоит из картинки и ее подписи в <figcaption>
, обернутых в тег <figure>
. Функция element()
используется для фона псевдоэлемента ::after
, чтобы получить актуальное изображение всей <figure>
, которое тут же переворачивается по оси Y и затеняется с помощью SVG-маски. Всё это делается внутри директивы @supports
, чтобы соответствовать подходу прогрессивного улучшения:
<figure class="reflection" id="css-element"> <img src="image.jpg" alt=""> <figcaption>San Francisco, CA</figcaption> </figure>
@supports (background: element(#css-element)) { .reflection::after { background: element(#css-element); transform: scaleY(-1); mask: url('#mask'); opacity: .3; } }
Живой пример работает в Firefox, а резервным вариантом для браузеров на основе WebKit служит старое, нестандартное свойство -webkit-box-reflect
(IE/Edge не поддерживается).
See the Pen Reflections with element() by Ilya Streltsyn (@SelenIT) on CodePen.
Да, я знаю, вы уже вдоволь насмотрелись на этот эффект. Зайдем поглубже.
Объемный эффект сложенной бумаги
Во многих продвинутых эффектах вам порой приходится возиться с дублированным контентом, и единственным разумным вариантом для этого оказывается JavaScript. Это не так уж сложно со статичным контентом (картинки, тексты и т.п.), но с динамическим приходится изрядно помучиться. А с element()
это почти что элементарно.
Например, вы можете запросто сложить эту форму логина для Твиттера пополам (наведите на нее в Firefox)
See the Pen Fold login form by Ilya Streltsyn (@SelenIT) on CodePen.
Живой пример в Firefox
Смотрите, что здесь происходит:
- создаем и размещаем в нужном месте HTML-форму логина
- затем кладем поверх нее слой-маску, так что форма становится невидимой
- добавляем к форме два псевдоэлемента (
::before
и::after
) и кладем их поверх слоя-маски - каждый псевдоэлемент ставится точно в то же место, что форма логина, и ссылается на нее с помощью
element()
- затем к этим псевдоэлементам применяются CSS-трансформации, анимации и фильтры
- кроме того, при помощи
pointer-events:none
события перенаправляются на нижележащий слой, так что форма оказывается полностью работоспособной - и всё это происходит только если
element()
поддерживается, внутри@supports
Развивая эту идею, мы можем сложить что угодно на странице, например, интерактивную карту:
See the Pen Fold interactive map by Ilya Streltsyn (@SelenIT) on CodePen.
Живой пример в Firefox
Анимированный фон
Можно создать и другой простой эффект — анимированный фон. Да, вы могли припомнить старинную GIF-анимацию для фона, но element()
дает новые возможности вроде использования тегов <video>
, <canvas>
или <svg>
. Соедините <video>
, <canvas>
и дублирующийся контент — и сможете создать такой обалденный эффект загнутого листа из 30 с лишним кусочков (прим. перев. 06.07.2021: здесь пришлось уменьшить их число, потому что вложенные 3d-трансформации в моем Firefox слишком тормозили), на котором можно рисовать прямо во время анимации. Весьма занятно!
See the Pen Crazy fold video + live canvas drawing by Ilya Streltsyn (@SelenIT) on CodePen.
Живой пример в Firefox
Вы можете заметить, что этот пример работает и в браузерах на основе WebKit. Вот как это получилось:
- Я заменил тег
<video>
анимированным GIF-рисунком, который может работать в качестве CSS-фона. Минус здесь в том, что размер GIF-файла огромен по сравнению с видео: если MP4 «весит» около 400КБ, WEBM — около 600КБ, то GIF — почти 4МБ. Так что в этом случае мне пришлось уменьшить количество кадров. - Я воспользовался
–webkit-canvas()
, который похож наelement()
, но может работать только с, ну да, элементом<canvas>
. Здесь такое решение, что называется, «сойдет», поскольку я ссылаюсь на canvas. Но всё же будьте внимательны, эта функция нестандартная и считается устаревшей.
Имитация backdrop-filter
C функцией element()
можно создать довольно простую альтернативу для backdrop-filter
, и тем самым охватить больше браузеров. Для этого нужно лишь задать в качестве фона элемента актуальное изображение элемента, лежащего под ним. Легко, правда?
Можете взглянуть на один из моих примеров из прошлой статьи, который теперь поддерживает Firefox благодаря element()
:
See the Pen Faking backdrop-filter with element() by Максим (@psywalker) on CodePen.
Плюс еще один с динамическим контентом:
See the Pen iOS 7 background blur with CSS by Максим (@psywalker) on CodePen.
Код сам себя поясняет:
h1 { … }
@supports ( backdrop-filter: blur(1px) ) { h1 { backdrop-filter: grayscale(1) contrast(3) blur(1px); } } @supports (not (backdrop-filter: blur(1px))) and (background: element(#back)) { h1::before { content: ''; position: absolute; z-index: -1; top: 0; left: 0; bottom: 0; right: 0; background: element(#back) fixed; filter: grayscale(1) contrast(3) blur(1px); } }
С помощью @supports
можно проверить:
- если поддерживается
backdrop-filter
, применим его к<h1>
- если
backdrop-filter
не поддерживается, но вместо него естьelement()
, создадим псевдоэлемент, подложим его под заголовок, зададим ему в качестве фона изображение нижележащей страницы и применим фильтр.
Еще стоит отметить, что backdrop-filter
можно имитировать SVG-фильтрами. Что-то наподобие такого (см. вкладку HTML):
See the Pen Faking backdrop-filter using SVG filter by Ilya Streltsyn (@SelenIT) on CodePen.
Таким способом можно добиться намного лучшей поддержки, но есть и много минусов. SVG-фильтры не динамические, пусть даже теоретически это и возможно. На деле ни один браузер не поддерживает backgroundImage
в качестве входных данных для примитивов фильтра. В IE/Edge фильтры могут «достучаться» до backgroundImage
с помощью устаревшего свойства enable-background
, но лишь для SVG-контента.
Как скрывать исходные элементы
Для многих эффектов мне пришлось создавать слой-маску, чтобы скрыть части страницы. Дело в том, что элемент, используемый в качестве «живого» фона, нельзя скрыть с помощью display:none
. Такой элемент вообще перестает отображаться в качестве «живой» картинки.
Я пробовал оборачивать исходный элемент в <div>
c height: 0
и overflow: hidden
. В этом случае элемент остается на странице (и может использоваться для «живой» фоновой картинки), но его самого не видно, так что слой-маска не нужен. Проблема в том, что некоторые браузеры «экономят» на отрисовке невидимых элементов (CSS-анимациях, приостанавливают GIF-анимацию и т.п.), и в нашем конкретном случае это может стать помехой.
Так что я остановился на способе с маской. Может, вы знаете еще какое-нибудь решение?
Итого
Надеюсь, я убедил вас, что element()
в CSS — это так круто, несмотря на слабую поддержку браузерами и оставляя в стороне вопрос о скорости отрисовки. Не бойтесь экспериментировать с ним сами и не забывайте делиться своими потрясающими примерами. Надо показать, что нам нужна эта функция. Тогда и браузеры, без сомнения, обратят на нее внимание (не только Firefox).
P.S. Это тоже может быть интересно:
Очень круто. Вот оно будущее вэба. Ой простите, уже настоящее, жаль только что не для всех.
Учитывая информацию с caniuse, всё-таки будущее
Примеры очень захватывающие!
Привет из будущего: нигде кроме мозилы до сих пор не актуально, так что в топку)
Ещё один привет из будущего. Теперь и в FF примеры нерабочие. :(
Спасибо за багрепорт! Нерабочие примеры пофикшены. Пример с картой не работал из-за устаревшей версии API, а вот отражение и «складная» форма логина, судя по всему, пострадали из-за ужесточения проверки на циклические зависимости (больше нельзя сделать элемент фоном для собственного псевдоэлемента, но для соседнего элемента — без проблем).