CSS-live.ru

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 года):

Кое-что нам на заметку:

Отражения

Все мы знаем, что мода на отражения в веб-дизайне прошла (привет, веб 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. Это тоже может быть интересно:

6 комментариев

  1. Очень круто. Вот оно будущее вэба. Ой простите, уже настоящее, жаль только что не для всех.

  2. Привет из будущего: нигде кроме мозилы до сих пор не актуально, так что в топку)

    1. Спасибо за багрепорт! Нерабочие примеры пофикшены. Пример с картой не работал из-за устаревшей версии API, а вот отражение и «складная» форма логина, судя по всему, пострадали из-за ужесточения проверки на циклические зависимости (больше нельзя сделать элемент фоном для собственного псевдоэлемента, но для соседнего элемента — без проблем).

Оставить комментарий

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

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.