Давайте поиграем с изображениями и поэкспериментируем с CSS-масками. Идея довольно проста: берем один тег и используем возможности CSS для выполнения сложных переходов при наведении. На примере различных демонстрационных примеров вы увидите, как CSS-маски в сочетании с градиентами позволяют создавать причудливые эффекты - с эффективным, многократно используемым кодом.
В предыдущей статье мы рассмотрели причудливые эффекты наведения для изображений, включающие блеск, вращение и 3D-перспективы. Мы продолжим играть с изображениями, но на этот раз мы создадим анимацию, раскрывающую изображения при наведении. В частности, мы узнаем о масках CSS и о том, как их можно использовать для закрытия частей изображения, которые раскрываются при наведении курсора на изображение.
Вот HTML, который мы будем использовать в нашей работе:
<img src="" alt="">
Да, именно так, только один тег image. Задача, которую я люблю решать в каждом новом CSS-проекте, такова: Пусть CSS сделает всю работу без лишней разметки.
В процессе работы вы можете заметить небольшие различия между кодом, который я привожу в статье, и тем, что используется в демонстрационных версиях. Код, приведенный в статье, отражает спецификацию CSS. Но поскольку поддержка браузерами некоторых используемых нами функций не совпадает, я включаю свойства с префиксом для более широкой поддержки.
Пример 1: Раскрытие окружности
В первом случае изображение помещается в квадратный контейнер, который при наведении стирается, открывая изображение.
Посмотрите анимацию раскрытия Pen Hover с использованием маски, созданную Темани Афифом.
Первым шагом для нас является создание контейнера изображения и градиентной границы вокруг него. Контейнер фактически представляет собой повторяющийся линейный градиентный фон, установленный в теге . Если мы добавим небольшую подложку к изображению, это позволит градиентному фону проявиться.
img {
padding: 10px;
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
}
Два квадратных изображения с полосатыми градиентными границами. (Большой предварительный просмотр)
Итак, у нас есть два изображения, каждое с градиентным фоном, который раскрывается с помощью наложения. Мы могли бы добавить в разметку
Итак, у нас есть два изображения, каждое с градиентным фоном, который раскрывается с помощью наложения. Мы могли бы добавить в разметку div или, возможно, даже figure для создания настоящего контейнера, но это противоречит задаче позволить CSS выполнять всю работу.
Хотя нам удалось обойтись без дополнительной разметки, теперь мы должны задать себе вопрос: Как скрыть изображение, не затрагивая градиентный фон? Нам нужно скрыть изображение, но продолжать показывать область вокруг него. На помощь приходят маски CSS.
Применить маску к элементу несложно, но в данном контексте это немного сложнее. Хитрость” заключается в том, чтобы связать два слоя-маски вместе и более четко определить, где применяются маски:
img {
/* etc. */
mask:
linear-gradient(#000 0 0) padding-box,
linear-gradient(#000 0 0) content-box;
}
Теперь у нас есть две маски-”источника”:
- content-box: ограничивающая содержимое изображения,
- padding-box: охватывающая всю область изображения, включая область с накладками.
Нам нужны два слоя, потому что тогда мы сможем объединить их с помощью свойства CSS mask-composite. Существуют различные способы объединения слоев-масок с помощью mask-composite, один из которых заключается в ”исключении” области, где две маски накладываются друг на друга.
img {
/* etc. */
mask:
linear-gradient(#000 0 0) padding-box,
linear-gradient(#000 0 0) content-box;
mask-composite: exclude;
}
В результате будет виден только градиент (область с накладками), как показано ниже.
См. обзор пера для исключения композиции Темани Афиф.
Обратите внимание, что мы можем убрать из кода padding-box, поскольку по умолчанию градиент покрывает всю область, а это то, что нам нужно.
Есть ли другие способы сделать это без маски-композита? Существует множество способов скрыть контент-бокс и показать только область с накладкой. Вот один из них, использующий в качестве маски конический градиент:
mask:
conic-gradient(from 90deg at 10px 10px, #0000 25%, #000 0)
0 0 / calc(100% - 10px) calc(100% - 10px);
/* 10px is the value of padding */
See the Pen Border-only using conic-gradient by Temani Afif.
Есть, конечно, и другие, но, думаю, вы поняли суть. Выбор подхода зависит только от вас. Лично я считаю, что использование маски-композита наиболее оптимально, поскольку не требует заранее знать значение padding или изменять его в нескольких местах. Кроме того, это хорошая возможность попрактиковаться в использовании маски-композиции.
Теперь заменим второй градиент (тот, что покрывает только область содержимого) на радиальный. Ведь для перехода при наведении мы хотим получить свайп по окружности.
img {
mask:
linear-gradient(#000 0 0),
radial-gradient(#000 70%,#0000 71%) content-box;
mask-composite: exclude;
}
See the Pen Adding the radial-gradient by Temani Afif.
Видите? Композит исключающей маски создает отверстие в изображении. Давайте поиграем с размером и положением этого выреза и посмотрим, что происходит. В частности, я уменьшу размер в два раза и расположу круг в центре изображения:
mask:
linear-gradient(#000 0 0),
radial-gradient(#000 70%,#0000 71%) content-box
center / 50% 50% no-repeat;
mask-composite: exclude;
See the Pen Updating the radial-gradient size and position by Temani Afif.
См. статью Обновление размера и положения радиального градиента от Temani Afif.
Наверняка вы уже поняли, к чему все идет. Мы регулируем размер радиального градиента, чтобы либо скрыть изображение (увеличить), либо раскрыть его (уменьшить). Чтобы полностью скрыть изображение, нам нужно увеличить масштаб маски настолько, чтобы круг закрывал изображение. Это означает, что нам нужно что-то большее, чем 100%. Я провел скучные математические вычисления и пришел к выводу, что 141% - это точная величина, но при желании можно воспользоваться и круглым числом.
Таким образом, мы получаем окончательный вариант CSS для эффекта:
img {
padding: 10px; /* control the thickness of the gradient "border" */
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
mask:
linear-gradient(#000 0 0),
radial-gradient(#000 70%, #0000 71%) content-box
50% / var(--_s, 150% 150%) no-repeat;
mask-composite: exclude;
transition: .5s;
}
img:hover {
--_s: 0% 0%;
}
Несколько мелких деталей:
Мы начинаем с размера, равного 150% 150%, чтобы первоначально скрыть изображение. Я делаю дополнительный шаг, применяя размер в качестве CSS-переменной (—_s) с полным размером (150% 150%) в качестве запасного значения. Таким образом, все, что нам нужно сделать, это обновить переменную при наведении.Добавьте состояние наведения, которое уменьшает размер до нуля, чтобы изображение было полностью раскрыто.Примените небольшой переход на .5 с, чтобы сгладить эффект наведения.
Вот еще раз финальная демонстрация:
See the Pen Hover reveal animation using mask by Temani Afif.
Посмотрите анимацию раскрытия Pen Hover с использованием маски, созданную Темани Афифом.
Мы только что создали красивую анимацию раскрытия с помощью всего нескольких строк CSS - и никакой дополнительной разметки! Нам даже не пришлось прибегать к псевдоэлементам. И это всего лишь один из способов настройки. Например, мы можем изменить положение маски, чтобы создать изящную вариацию этого эффекта:
See the Pen Another variation of the circular reveal animation by Temani Afif.
Еще одна вариация анимации кругового раскрытия, созданная Темани Афифом.
Я большой поклонник того, чтобы выложить идею и продвигать ее дальше с помощью экспериментов. Запустите демо-версию и сообщите мне, какие интересные вещи вы можете из нее сделать!
Пример 2: Раскрытие диагонали
Давайте усложним задачу и попробуем создать эффект наведения, для которого потребуется не два, а три градиента.
See the Pen Hover reveal animation using mask II by Temani Afif.
Анимация раскрытия при наведении с использованием маски II
Не стоит пока рассматривать код. Давайте попробуем создать его шаг за шагом, начав с более простого эффекта, который повторяет ту же схему, что мы создали в первом примере. Разница заключается в том, что мы заменяем радиальный градиент на линейный:
img {
padding: 10px; /* control the thickness of the gradient "border" */
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
mask:
linear-gradient(#000 0 0),
linear-gradient(135deg, #000 50%, #0000 0) content-box
0% 0% / 200% 200% no-repeat;
mask-composite: exclude;
transition: .5s;
}
img:hover {
mask-position: 100% 100%;
}
Вы заметите, что еще одно небольшое отличие этого CSS от первого примера заключается в том, что размер маски равен 200% 200%. Кроме того, на этот раз при наведении обновляется положение маски, а не ее размер: от 0% 0% (слева вверху) до 100% 100% (справа внизу), чтобы создать эффект пролистывания.
See the Pen Diagonal reveal animation using mask by Temani Afif.
Анимация раскрытия диагонали с использованием маски
Мы можем изменить направление пролистывания, просто изменив угол линейного градиента со 135 на -45 градусов, а затем обновив позицию на 0% 0% при наведении вместо 100% 100%:
img {
padding: 10px; /* control the thickness of the gradient "border" */
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
mask:
linear-gradient(#000 0 0),
linear-gradient(-45deg, #000 50%, #0000 0) content-box
100% 100% / 200% 200% no-repeat;
mask-composite: exclude;
transition: .5s;
}
img:hover {
mask-position: 0% 0%;
}
See the Pen Diagonal reveal animation using mask by Temani Afif.
Анимация диагонального раскрытия с использованием маски
Еще один момент: я задал только одно значение позиции маски при наведении, но у нас два градиента. Если вам интересно, как это работает, то положение маски применяется к первому градиенту, но поскольку градиент занимает всю область, к которой он применяется, его нельзя перемещать с помощью процентных значений. Это означает, что мы можем спокойно задать только одно значение для обоих градиентов, и оно будет влиять только на второй градиент. Более подробно я объясняю эту идею в ответе на Stack Overflow. В ответе рассматривается положение фона, но та же логика применима и к положению маски.
Далее я хотел бы попробовать объединить два последних созданных нами эффекта. Посмотрите демонстрационный пример, приведенный ниже, чтобы понять, как я хочу, чтобы эта комбинация работала:
See the Pen Combination of two diagonal reveal by Temani Afif.
Комбинация двух диагональных раскрытий
На этот раз оба градиента начинаются в центре (50% 50%). Первый градиент скрывает левую верхнюю часть изображения, а второй - правую нижнюю. При наведении курсора оба градиента сдвигаются в противоположном направлении, открывая полное изображение.
Если вы похожи на меня, то наверняка подумали: Сложить все градиенты вместе, и готово. Да, это наиболее интуитивное решение, и выглядеть оно будет следующим образом:
img {
padding: 10px; /* control the thickness of the gradient "border" */
background: repeating-linear-gradient(45deg, #FF6B6B 0 10px, #4ECDC4 0 20px);
mask:
linear-gradient(#000 0 0),
linear-gradient(135deg, #000 50%, #0000 0) content-box
50% 50% / 200% 200% no-repeat,
linear-gradient(-45deg, #000 50%, #0000 0) content-box
50% 50 / 200% 200% no-repeat;
mask-composite: exclude;
transition: .5s;
cursor: pointer;
}
img:hover {
mask-position: 0% 0%, 100% 100%;
}
See the Pen Combining both effects by Temani Afif.
Сочетание обоих эффектов
Этот подход вроде бы работает, но у нас есть небольшой визуальный глюк. Обратите внимание, что из-за природы градиентов и проблем со сглаживанием видна странная диагональная линия. Мы можем попытаться исправить это, немного увеличив процентное соотношение до 50,5% вместо 50%:
See the Pen Trying to fix the anti-aliasing issue by Temani Afif.
Попытка исправить проблему сглаживания
Это еще хуже. Вероятно, вы задаетесь вопросом, не следует ли мне уменьшить процент, а не увеличить его. Попробуйте, и произойдет то же самое.
Исправление заключается в обновлении свойства mask-composite. Если вы помните, мы уже используем значение exclude. Вместо того чтобы объявлять только значение exclude, нам нужно также применить значение add, чтобы нижние слои (сметающие градиенты) не исключались друг из друга, а складывались:
img {
mask:
/* 1st layer */
linear-gradient(#000 0 0),
/* 2nd layer */
linear-gradient(135deg, #000 50.5%, #0000 0) content-box
50% 50% / 200% 200% no-repeat,
/* 3rd layer */
linear-gradient(-45deg, #000 50.5%, #0000 0) content-box
50% 50% / 200% 200% no-repeat;
mask-composite: exclude, add;
}
Теперь во втором и третьем слоях с помощью композиции add будет создан промежуточный слой, который будет исключен из первого. Другими словами, мы должны исключить все слои из первого.
Я знаю, что маска-композиция - это сложная концепция. Я настоятельно рекомендую вам прочитать краткий курс Аны Тюдор по композиции масок, чтобы получить более глубокое и подробное объяснение того, как свойство ”маска-композиция” работает с несколькими слоями.
Это исправляет проблему с линией в нашем эффекте наведения:
See the Pen Diagonal reveal animation using mask by Temani Afif.
Анимация раскрытия диагонали с использованием маски
Еще одна небольшая деталь, которую вы могли заметить: в коде мы определили три градиента, но только два значения mask-position для состояния hover:
img:hover {
mask-position: 0% 0%, 100% 100%;
}
Первое значение (0% 0%) применяется к первому градиентному слою; он не будет перемещаться, как раньше. Второе значение (100% 100%) применяется ко второму градиентному слою. При этом третий градиентный слой использует первое значение! Если в параметре mask-position объявлено меньше значений, чем количество слоев маски, то серия значений, разделенных запятыми, повторяется до тех пор, пока не будут учтены все слои маски.
В данном случае серия повторяется по кругу до первого значения (0% 0%), чтобы третий слой маски принял значение. Таким образом, в действительности приведенный выше код является более кратким эквивалентом написания этого:
img:hover {
mask-position: 0% 0%, 100% 100%, 0% 0%;
}
Вот окончательный вариант демонстрации с обоими вариантами. Видно, что во втором примере используется тот же код с небольшими изменениями.
See the Pen Hover reveal animation using mask II by Temani Afif.
See the Pen Анимация раскрытия при наведении с использованием маски II by Temani Afif.
Пример 3: Зигзагообразное раскрытие
У меня есть еще один пример, на этот раз раскрывающий изображение с зигзагообразными краями, раздвигающимися, как бы вгрызающимися в изображение зубами.
See the Pen Hover reveal animation using mask III by Temani Afif.
Анимация раскрытия при наведении с использованием маски III
Хотя этот эффект может показаться более сложным, чем два предыдущих, в нем используется все тот же базовый CSS-шаблон, который мы применяли все это время. На самом деле, я даже не буду углубляться в код, поскольку хочу, чтобы вы сами проанализировали его, используя то, что вы теперь знаете об использовании градиентов CSS в качестве масок и комбинировании слоев-масок с помощью mask-composite.
Я не дам вам ответа на этот вопрос, но поделюсь статьей, в которой показано, как я создал зигзагообразную форму. И поскольку я чувствую себя щедрым, то в качестве еще одного ресурса приведу ссылку на этот онлайновый генератор границ.
Подведение итогов
Надеюсь, вам понравился этот небольшой эксперимент с CSS-масками и градиентами! Градиенты могут быть запутанными, но смешение их с масками - это не что иное, как сложность. Однако, потратив время на рассмотрение трех примеров по частям, мы можем ясно увидеть, как градиенты могут использоваться в качестве масок, а также как их можно комбинировать для ”рисования” видимых областей.
Как только мы получили представление о том, как это работает, поражает тот факт, что для получения эффекта нам достаточно обновить положение или размер маски в зависимости от состояния наведения элемента. И тот факт, что все это можно сделать с помощью одного HTML-элемента, показывает, насколько мощным средством является CSS.
Мы увидели, как один и тот же общий CSS-шаблон может быть настроен для создания бесчисленных вариаций одного и того же эффекта. В завершение статьи я решил привести еще несколько примеров, с которыми вы можете поиграть.
See the Pen Hover reveal animation using mask IV by Temani Afif.
Анимация раскрытия при наведении с использованием маски IV
See the Pen Hover reveal animation using mask V by Temani Afif.
Анимация раскрытия при наведении с использованием маски V
See the Pen Hover reveal animation using mask VI by Temani Afif.
Анимация раскрытия при наведении с использованием маски VI