Необычные украшения изображений - Магия с одним элементом

alexei29/10/2023 - 09:22
Необычные украшения изображений

Как следует из названия, мы собираемся украшать изображения! На эту тему уже написано множество статей, но то, что будет рассмотрено здесь, немного отличается, потому что это скорее вызов. Вызов? Да, задача украсить изображение, используя только тег <img> и ничего больше.

Все верно. Никакой дополнительной разметки, никаких дополнительных элеменов div и никаких псевдоэлементов. Только один тег.

Звучит сложно, не так ли? Но к концу этой статьи вы убедитесь, что CSS достаточно мощный инструмент, который позволяет добиваться по-настоящему ошеломляющих результатов, несмотря на ограничения при работе с одним элементом. Итак,

Начнем с нашего первого примера

See the Pen Необычная рамка by Alexei Goloviznin on CodePen.

Прежде чем углубляться в код, давайте перечислим возможности оформления элемента <img> без каких-либо дополнительных элементов или псевдоэлементов. Мы можем использовать рамку border, тень box-shadow, контур outline и, конечно же, фон background. Может показаться странным добавлять фон к изображению, ведь мы не увидим его, так как он будет находиться за изображением. Но хитрость заключается в том, чтобы создать пространство вокруг изображения с помощью padding и/или border, а затем нарисовать наш фон внутри этого пространства.

Думаем, вы уже догадались, что последует дальше. Да, градиенты! Все украшения, которые мы собираемся создавать, основаны на большом количестве градиентов.

Давайте же вернемся к нашему первому примеру:


img {
  --s: 10px; /* контролирует размер */
  padding: var(--s);
  border: calc(2 * var(--s)) solid #0000;
  outline: 1px solid #000;
  outline-offset: calc(-1 * var(--s));
  background: conic-gradient(from 90deg at 1px 1px, #0000 25%, #000 0);
}

Мы задаем поле padding и прозрачную рамку border, используя переменную --s, чтобы создать пространство вокруг нашего изображения, которое будет в три раза больше значения этой переменной.

Почему мы используем и padding и border, а не что-то одно? Похожий результат можно получить, используя только одно из этих свойств, но нам нужна такая комбинация для градиента, потому что по умолчанию начальное значение background-clip равно border-box, а background-origin равно padding-box.

Вот пошаговая иллюстрация сказанного, которая поможет понять логику происходящего:

See the Pen Отображение градиента by Alexei Goloviznin on CodePen.

Изначально у изображения нет никакой рамки, поэтому наш градиент создает два сегмента толщиной 1px. (В этой конкретной демонстрации мы задали толщину в 3px, чтобы было виднее.) Мы добавляем цветную рамку, и градиент по-прежнему дает нам тот же результат внутри области, заданной padding (из-за background-origin), но он повторяется за рамкой. Если мы сделаем рамку прозрачной, мы можем использовать повторение и тогда получим нужную рамку.

В демонстрации outline имеет отрицательное смещение. Это создает квадратную форму в верхней части градиента. Вот и все! Мы добавили красивое оформление к нашему изображению, используя один градиент и outline. Мы могли бы использовать больше градиентов!

Вот решение, основанное только на градиенте, в котором для определения пространства используется только padding. Результат тот же, но с более сложным синтаксисом.

See the Pen Необычная рамка (альтернативная) by Alexei Goloviznin on CodePen.

Давайте рассмотрим другой вариант рамки:

See the Pen Необычная рамка 2 by Alexei Goloviznin on CodePen.

Мы взяли предыдущий пример, удалили outline и применили clip-path, чтобы обрезать градиент с каждой стороны. Значение у clip-path немного запутано и может сбивать с толку, но вот иллюстрация, чтобы лучше понять его суть:

Изображение с clip-path и без
Изображение с clip-path и без

Думаем, вы уловили основную идею. Мы собираемся комбинировать фоны, контуры, обрезку и маску для получения различных видов оформления. А в качестве дополнительного бонуса мы рассмотрим несколько классных анимаций при наведении курсора мыши! То, что мы разбирали до сих пор - это всего лишь небольшой обзор того, что будет дальше!

Рамка по углам изображения

See the Pen Рамка по углам изображения by Alexei Goloviznin on CodePen.

Для реализации задуманного нам потребуется четыре градиента. Каждый градиент закрывает один угол, и при наведении курсора он расширяется, создавая полную рамку вокруг изображения. Давайте проанализируем код для одного из градиентов:


--b: 5px; /* толщина рамки */
background: conic-gradient(from 90deg at top var(--b) left var(--b), #0000 90deg, darkblue 0) 0 0;
background-size: 50px 50px; 
background-repeat: no-repeat;

Мы собираемся нарисовать градиент размером 50px 50px, и поместить его в верхний левый угол (0 0). Вот пошаговая демонстрация настройки градиента, показывающая, как мы достигли нужного результата.

See the Pen Демонстрация углового градиента by Alexei Goloviznin on CodePen.

Принято думать, что градиенты хорошо подходят только для перехода между двумя цветами. Но на самом деле с их помощью мы можем сделать гораздо больше! Они особенно полезны, когда дело доходит до создания различных фигур. Хитрость в том, чтобы обеспечить жесткий, а не плавный переход между цветами, как в примере выше:


#0000 25%, darkblue 0

По сути, здесь говорится: "сначала 25% области заполните прозрачным цветом, затем оставшуюся область заполните темно-синим цветом".

Возможно, вы подумаете, а что это за значение 0? Это небольшая хитрость для упрощения синтаксиса. На самом деле, мы должны использовать это, чтобы сделать жесткую остановку между цветами:


#0000 25%, darkblue 25%

Так логичнее! Прозрачный цвет заканчивается на 25% и темно-синий начинается именно там, где заканчивается прозрачность, делая жесткую остановку. Если мы заменим второе значение на 0, браузер сделает эту работу за нас, так что это немного более эффективный способ добиться цели.

Где-то в спецификации сказано:

если позиция цветового ограничителя или оттенка перехода меньше указанной позиции любого цветового ограничителя или оттенка перехода перед ним в списке, установите ее позицию равной наибольшей указанной позиции любого цветового ограничителя или оттенка перехода перед ним.

0 всегда меньше любого другого значения, поэтому браузер всегда преобразует его в наибольшее значение, которое стоит перед ним в декларации. В нашем случае это число равно 25%.

Теперь мы применяем все сказанное ко всем углам и получаем следующий код:


img {
  --b: 5px; /* толщина рамки */
  --c: #0000 90deg, darkblue 0; /* здесь задаем цвет */
  padding: 10px;
  background:
    conic-gradient(from 90deg  at top    var(--b) left  var(--b), var(--c)) 0 0,
    conic-gradient(from 180deg at top    var(--b) right var(--b), var(--c)) 100% 0,
    conic-gradient(from 0deg   at bottom var(--b) left  var(--b), var(--c)) 0 100%,
    conic-gradient(from -90deg at bottom var(--b) right var(--b), var(--c)) 100% 100%;
  background-size: 50px 50px; /* здесь задаем длину рамки */
  background-repeat: no-repeat;
}

Мы ввели переменные CSS, чтобы избежать некоторой избыточности, поскольку все градиенты используют одни и те же настройки цветов.

Для создания анимированного эффекта при наведении курсора мыши все, что мы делаем, это увеличиваем размер градиентов, чтобы создать полноразмерную рамку:


img:hover {
  background-size: 51% 51%;
}

Мы задаем 51% вместо 50%, чтобы было небольшое перекрытие, это позволит избежать возможные разрывы.

Давайте попробуем реализовать другую идею, используя ту же технику:

На этот раз мы используем только два градиента, но с более сложной анимацией. Сначала мы обновляем положение каждого градиента, затем увеличиваем их размеры, чтобы нарисовать рамку на всю длину. Мы также ввели больше переменных для лучшего контроля над цветом, размером, толщиной и даже промежутком между изображением и рамкой.


img {
  --b: 8px;  /* толщина рамки */
  --s: 60px; /* размер угла */
  --g: 14px; /* промежуток */
  --c: #EDC951; 

  padding: calc(var(--b) + var(--g));
  background-image:
    conic-gradient(from  90deg at top    var(--b) left  var(--b), #0000 25%, var(--c) 0),
    conic-gradient(from -90deg at bottom var(--b) right var(--b), #0000 25%, var(--c) 0);
  background-position:
    var(--_p, 0%) var(--_p, 0%),
    calc(100% - var(--_p, 0%)) calc(100% - var(--_p, 0%));
  background-size: var(--s) var(--s);
  background-repeat: no-repeat;
  transition: 
    background-position .3s var(--_i,.3s), 
    background-size .3s calc(.3s - var(--_i, .3s));
}

img:hover {
  background-size: calc(100% - var(--g)) calc(100% - var(--g));
  --_p: calc(var(--g) / 2);
  --_i: 0s;
}

Почему в названии переменных --_i и --_p есть символ подчеркивания? Символы подчеркивания являются частью соглашения об именовании для учета "внутренних" переменных, используемых для оптимизации кода. В них нет ничего особенного, но нам хотелось указать на разницу между переменными, которые мы используем для управления рамкой (например, --b, --c и т.д.), и теми, которые используются, чтобы сделать код короче.

Код может показаться весьма запутанным и сложным для понимания. Вот иллюстрация, позволяющая лучше понять различные используемые значения:

Иллюстрация CSS переменных, используемых в коде
Иллюстрация CSS переменных, используемых в коде

Раскрывающаяся рамка

Давайте попробуем другой тип анимации, где при наведении курсора мыши рамка раскрывается на всю длину:

See the Pen Эффект с рамкой при наведении by Alexei Goloviznin on CodePen.

Круто, правда? И, если присмотреться, то можно заметить, что линии исчезают в противоположном направлении при нажатии мыши, что делает эффект еще более причудливым!

Эффект аналогичен известному эффекту для текстового элемента:

See the Pen Фон при наведении 1 by Alexei Goloviznin on CodePen.

Но на этот раз, вместо того, чтобы охватывать весь элемент, мы охватываем только небольшую его часть, определяя высоту, чтобы получить что-то вроде этого:

Это верхняя граница нашей рамки. Мы повторяем то же самое с каждой стороны изображения, и у нас получается нужный нам эффект:


img {
  --b: 10px; /* толщина рамки */
  --g: 5px; /* промежуток при наведении */
  --c: #8A9B0F; 

  padding: calc(var(--g) + var(--b));
  --_g: no-repeat linear-gradient(var(--c) 0 0);
  background: 
    var(--_g) var(--_i, 0%) 0,
    var(--_g) 100% var(--_i, 0%),
    var(--_g) calc(100% - var(--_i, 0%)) 100%,
    var(--_g) 0 calc(100% - var(--_i, 0%));
  background-size: var(--_i, 0%) var(--b),var(--b) var(--_i, 0%);
  transition: .4s, background-position 0s;
  cursor: pointer;
}

img:hover {
  --_i: 100%;
}

Как можно видеть, мы применяем один и тот же градиент четыре раза, и у каждого градиента свое положение, чтобы покрывалась только одна сторона за раз.

А вот еще один вариант:

See the Pen Необычная рамка при наведении by Alexei Goloviznin on CodePen.

Этот вариант выглядит немного более сложным, и действительно требуется некоторое воображение, чтобы понять, как два конических градиента создают подобный эффект. Вот демонстрация, иллюстрирующая один из градиентов:

See the Pen Демонстрация градиента by Alexei Goloviznin on CodePen.

Псевдоэлемент имитирует градиент. Изначально он находится вне поля зрения, и при наведении курсора мы сначала меняем его положение, чтобы получить верхний край рамки. Затем мы увеличиваем высоту, чтобы получить правый край. Форма градиента похожа на те, которые мы использовали в предыдущем разделе: два сегмента, закрывающие две стороны.

Но зачем делать ширину градиента 200%? Кажется 100% было бы вполне достаточно.

Конечно, ширины в 100% было бы достаточно, но в этом случае мы не сможем перемещать градиент так, как нам надо. Это еще одна маленькая особенность, связанная с тем, как работает свойство background-position.

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


img {
  --c: #8A9B0F; /* цвет рамки */
  --b: 10px; /* толщина рамки */
  --g: 5px;  /* промежуток */

  padding: calc(var(--g) + var(--b));
  --_g: #0000 25%, var(--c) 0;
  background: 
    conic-gradient(from 180deg at top    var(--b) right var(--b), var(--_g))
     var(--_i, 200%) 0 / 200% var(--_i, var(--b))  no-repeat,
    conic-gradient(            at bottom var(--b) left  var(--b), var(--_g))
     0 var(--_i, 200%) / var(--_i, var(--b)) 200%  no-repeat;
  transition: .3s, background-position .3s .3s;
  cursor: pointer;
}

img:hover {
  --_i: 100%;
  transition: .3s, background-size .3s .3s;
} 

Как видите, оба градиента почти идентичны. Просто поменялись местами значения размера и положения.

Поворот рамки

На этот раз мы не будем рисовать рамку вокруг нашего изображения, а скорее скорректируем внешний вид уже существующей.

See the Pen Эффект поворот рамки by Alexei Goloviznin on CodePen.

Вы, наверное, задаетесь вопросом, как вообще можно преобразовать прямую линию в линию с углом. И вы правы, хитрость здесь в другом. Это всего лишь иллюзия, которая получается после объединения простых анимаций для четырех градиентов.

Давайте посмотрим, как создается анимация для верхнего градиента:

See the Pen Анимация верхнего градиента by Alexei Goloviznin on CodePen.

Здесь мы просто обновляем положение повторяющегося градиента. Пока ничего необычного! Давайте сделаем то же самое для правой стороны:

Улавливаете в чем заключается фокус? Оба градиента пересекаются в углу, создавая иллюзию залома прямой линии. Чтобы лучше все видеть, давайте удалим контур и скроем перекрывание:

Теперь добавляем еще два градиента, чтобы покрыть оставшиеся края, и готово:


img {
  --g: 4px; /* промежуток */
  --b: 12px; /* толщина рамки */
  --c: #669706; /* цвет */

  padding: calc(var(--g) + var(--b));
  --_c: #0000 0 25%, var(--c) 0 50%;
  --_g1: repeating-linear-gradient(90deg ,var(--_c)) repeat-x;
  --_g2: repeating-linear-gradient(180deg,var(--_c)) repeat-y;
  background:
    var(--_g1) var(--_p, 25%) 0, 
    var(--_g2) 0 var(--_p, 125%),
    var(--_g1) var(--_p, 125%) 100%, 
    var(--_g2) 100% var(--_p, 25%);
  background-size: 200% var(--b), var(--b) 200%;
  transition: .3s;
}

img:hover {
  --_p: 75%;
}

Если мы возьмем этот код и слегка подкорректируем его, то сможем получить еще одну классную анимацию:

Поняли ли вы логику в этом примере? Пусть это будет вашим домашним заданием! Код может выглядеть устрашающе, но он использует ту же логику, что и рассмотренные ранее примеры. Попробуйте выделить каждый градиент и представьте, как он анимируется.

Подводим итоги

Слишком много градиентов в одной статье!

Это точно, но мы вас предупреждали! Тем не менее, если задача состоит в том, чтобы украсить изображение без дополнительных элементов и псевдоэлементов, у нас есть всего несколько возможностей, и градиенты - самый мощный вариант.