Ключова риса дизайну «liquid glass» — переконлива ілюзія переломлення світла, тобто ефект, коли частину екрана видно наче через лінзу. У блозі Master.dev розповіли, як відтворити такий візуальний трюк для фонових зображень елементів на чистому CSS.

Чому фіксовані фони не підходять

Класичний спосіб отримати ефект рефракції — призначити однакове фонове зображення і елементу-підкладці, і елементу, що імітує переломлення. Обидва шари показують один і той самий фрагмент картинки, а невеликий зсув фону в одному з них і створює бажаний оптичний ефект.

#card-container {
  background: center/cover url("image.jpg");
  background-attachment: fixed;
}

#card {
  border: 2px red solid;
  background: inherit;
  background-position: -20px -20px;
  margin-left: auto;
}

Мінус цього підходу в тому, що фонове зображення прив'язується не до елемента-підкладки, а до вьюпорту — видимої області екрана. Щоб картинка коректно вписувалася у підкладку, доводиться постійно перераховувати її позицію та розмір, а для цього потрібно знати точні координати елемента на сторінці. У responsive-версті це швидко перетворюється на головний біль.

Як допомагає CSS view()

У CSS є простий спосіб слідкувати за положенням навіть довільного елемента — за певних умов. Функція view() відстежує позицію (точніше, видимість) елемента всередині контейнера з прокруткою.

Якщо використати такий контейнер прокрутки як підкладку для рефракції, фонові зображення дочірніх елементів автоматично підлаштовуватимуться під відповідні ділянки підкладки залежно від власної позиції всередині контейнера.

Фіксовані фони та складні обчислення більше не потрібні. Цікаво, що реальне прокручування сторінки для роботи техніки теж не є обов'язковим.

Розгляньмо покроково, як це реалізувати.

Базова структура

Потрібен контейнер із фоновим зображенням, який виконуватиме роль підкладки для рефракції, і дочірній елемент, що демонструватиме сам ефект переломлення.

Контейнер (підкладка рефракції)

Спочатку в CSS явно задають розмір фонового зображення контейнера, щоб зафіксувати стабільні пропорції.

#card-container {
  --w: 380px;
  --h: 420px;
  width: var(--w);
  height: var(--h);
  background: url("image.jpg") 0 0 / var(--w) var(--h) no-repeat;
}

Далі контейнер перетворюють на контейнер прокрутки за допомогою overflow:hidden. Причина цього кроку стане зрозумілою трохи згодом.

#card-container {
  overflow: hidden;
}

Картка (передній план рефракції)

Переходячи до картки — елемента, який показуватиме результат переломлення — задають успадкування фонового зображення від контейнера, щоб обидва шари використовували те саме зображення.

#card {
  background: inherit;
}

Далі до картки додають дві анімації на ключових кадрах, які мають відповідати таким умовам:

  1. Обидві анімації використовують лінійну функцію тайминга (постійна швидкість прогресу). Це виключає розбіжності під час вирівнювання фонів елементів рефракції з підкладкою.
  2. Задано діапазон анімації contain (анімація відбувається в межах видимої області). Так краї підкладки залишаються надійною точкою відліку для ефектів переломлення внутрішніх елементів.
  3. Часові шкали анімації є view-timeline — одна для осі X, інша для осі Y. Функція view() обробляє лише один напрям одночасно, тому потрібні окремі шкали для горизонталі та вертикалі.
#card {
  animation: pos-x linear, pos-y linear;
  animation-timeline: view(x), view(y);
  animation-range: contain;
}

Оскільки контейнер раніше перетворили на контейнер прокрутки, тепер можна застосувати view-timeline до картки, щоб анімація залежала від її видимості всередині цього контейнера.

Що саме анімується? Позиція фонового зображення картки.

@keyframes pos-x {
  from { background-position-x: 100%; }
}

@keyframes pos-y {
  from { background-position-y: 100%; }
}

Кадр pos-x розташовує фонове зображення по горизонталі залежно від горизонтальної позиції картки в контейнері (view(x)). Аналогічно pos-y визначає вертикальну позицію фону, орієнтуючись на вертикальне положення картки (view(y)).

У результаті картка отримує в якості власного фону саме ту частину зображення контейнера, над якою вона розташована (у прикладі картка має червону рамку для наочності).

Власне рефракція

Тепер додамо трохи ефекту переломлення, змінивши розмір фонового зображення картки.

#card {
  background: inherit;
  background-size: calc(var(--w) - 100px) calc(var(--h) - 100px);
  /.../
}

Альтернативний варіант — змістити позицію фону, отримавши інший малюнок рефракції.

@keyframes pos-x {
  from { background-position-x: 85%; }
}

@keyframes pos-y {
  from { background-position-y: 85%; }
}

Фактично тут відбувається невеликий зсув фонового зображення всередині картки.

Коли базовий ефект рефракції готовий, легко додати шари блюру, тіні, скосу та інших елементів для більш складного glassmorphism. А завдяки використанню view(), прив'язаного до прокрутки, при переміщенні картки фонове зображення оновлюватиметься автоматично.

Джерело: Master.dev Blog