Отблеск, перспектива и поворот - причудливые CSS 3D эффекты для изображений

alexei13/09/2023 - 09:26
Отблеск, перспектива и поворот - CSS 3D-эффекты для изображений

CSS, используя определенные приемы, позволяет превращать изображения в аккуратные интерактивные элементы. Эта статья представляет собой сборник необычных 3D-эффектов для изображений, демонстрирующих эти возможности CSS. Мы расскажем о том, как добавить изображениям перспективу, глубину, поворот и даже гладкий блеск при помощи CSS. И все это вы сможете использовать в своем следующем проекте.

Все мы согласны с тем, что 3D-эффекты - это круто. Особенно когда они сочетаются с изящной анимацией. В этой статье мы рассмотрим несколько приемов CSS для создания потрясающих 3D-эффектов!

"Зачем нам нужна еще одна статья о CSS 3D-эффектах... разве их уже написано не миллионы?" Да, но эта статья немного отличается, потому что мы будем работать с минимальным количеством HTML-кода. Фактически, единственная разметка, которую мы будем использовать для создания некоторых довольно удивительных CSS-эффектов, будет такой:


<img src="" alt="">

И все! Нам нужен только HTML тег <img>. Остальное будет делать CSS.

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

CSS 3D отблеск

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

See the Pen 3D Shine effect on hover by Alexei Goloviznin.

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

Первое, что мы здесь сделали, это в CSS для изображения добавили поворот:


img {
  transform: perspective(400px) rotate3d(1, -1, 0, 8deg);
}

img:hover {
  transform: perspective(400px) rotate3d(1, -1, 0, -8deg);
}

CSS функция rotate3d позволяет нам определить ось, вокруг которой изображение будет вращаться. Не будем сейчас вдаваться в математические подробности, но чтобы получить вращение по диагонали, мы задаем для оси z значение 0, а для осей x и y используем значение 1 или -1.

Затем мы используем CSS функцию perspective, чтобы добавить изображению небольшой дисбаланс. За использованием значения 400px или за поворотом на 8deg нет особой логики, но можно заметить, что небольшой угол в сочетании с большой перспективой дает хороший результат. Не стесняйтесь использовать свои значения. Возможно, для своего проекта вы найдете что-то получше.

Мы можем немного упростить наш код! Чтобы не повторяться в селекторе :hover, мы можем использовать CSS переменную для указания отрицательного угла поворота изображения при наведении курсора мыши. Таким образом, у нас отпадет необходимость переписывать всю декларацию просто для того, чтобы изменить 8deg на -8deg.


img {
  transform: perspective(400px) rotate3d(1, -1, 0, calc(var(--i, 1) * 8deg));
}

img:hover {
  --i: -1;
}

Обратите внимание, как используется CSS функция calc(). Умножая значение градуса на 1 (определяется CSS переменной--i), мы получаем значение по умолчанию 8deg. Затем в селекторе :hover мы меняем 1 на -1, и все пересчитываем.

Здорово! Но все становится еще интереснее, когда мы начинаем работать над отблеском. Интуиция подсказывает, что для создания сияния нам нужно использовать наложение, которое следует разместить поверх изображения. Но помните, мы работаем только с одним элементом <img>, а наложение потребует дополнительной разметки.

Может тогда использовать псевдоэлемент? Но, к сожалению, здесь это не сработает, потому что псевдоэлементы не будут работать с тегом <img>.

В действительности, мы собираемся все это сымитировать при помощи CSS-маски и анимированного градиента. Мы говорим "сымитировать", потому что в действительности изображение, которое вы видите, частично прозрачное. При наведении курсора, чтобы создать эффект отблеска, прозрачность изменяется.

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

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

See the Pen Opacity transition by Alexei Goloviznin on CodePen.

Это базовая идея. Теперь вместо свойства opacity мы будем использовать свойство mask с функцией linear-gradient():


/* Диагональный градиент, который темный
 * в центре и полупрозрачный по обоим краям 
*/
mask: linear-gradient(135deg, #000c 40%, #000, #000c 60%);

При использовании маски в CSS цвет не имеет значения, учитывая значение по умолчанию mask-mode. Важен только альфа-канал, который будет определять уровень прозрачности. В нашем случае диагональная часть непрозрачна, а боковые части частично прозрачны. #000c эквивалентно rgb(0 0 0 / 80%).

See the Pen Adding the gradient mask by Alexei Goloviznin on CodePen.

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

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


img {
  mask:
    linear-gradient(135deg, #000c 40%, #000, #000c 60%)
    100% 100%/ /* начальная позиция, внизу справа */
    240% 240%; /* ширина и высота */
}

img:hover {
    mask-position: 0 0; /* При наведении сдвигаем влево и вверх */
}

Посмотрите! При наведении курсора мыши на изображение у нас есть красивый отблеск!

See the Pen Adding the hover effect by Alexei Goloviznin on CodePen.

Здорово, правда? Теперь, чтобы создать весь эффект целиком, давайте объединим отблеск с 3D поворотом.


img {
  transform: perspective(400px) rotate3d(1,-1,0,calc(var(--i,1)*8deg));
  mask:
    linear-gradient(135deg,#000c 40%,#000,#000c 60%)
    100% 100%/240% 240%;
  transition: .4s;
  cursor: pointer;
}

img:hover {
  --i: -1;
  mask-position: 0 0;
}

Один HTML элемент и несколько строк CSS - это все, что нам было нужно, чтобы эффект получился. Вот рисунок, иллюстрирующий различные значения, используемые внутри маски:

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

Зеленое поле иллюстрирует градиент, в котором синие линии определяют цветовые границы, которые мы использовали. Изначально он расположен на 100% 100%, и при наведении курсора мы перемещаем его на 0 0. Создается эффект скольжения, который переместит диагональную (непрозрачную) часть градиента вдоль изображения и получится эффект отблеска.

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

See the Pen 3D Shine effect on hover by Alexei Goloviznin.

CSS 3D параллакс

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

See the Pen 3D parallax effect on hover by Alexei Goloviznin on CodePen.

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

Вы можете подумать, что нам нужно объединить две версии одного и того же изображения, чтобы добиться этого, но это совсем не так! Эффект создается с помощью одного изображения и нескольких строк CSS для "имитации" эффекта параллакса. Да, мы называем это "имитацией", потому что на самом деле это не реализация параллакса, а комбинация движений, которые заставляют ваш мозг думать, что это на самом деле так! Если вы хотите увидеть "настоящий" параллакс в действии, то посмотрите пример на CodePen от автора Sladjan.

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

Изображение поворачивается и меняет ракурс при наведении курсора мыши, точно так же, как эффект отблеска из предыдущей части. Однако на этот раз мы поворачиваем только по оси y (rotateY()) вместо всех трех осей (rotate3d()).


img {
  transform: perspective(400px) rotateY(8deg);
}

img:hover {
  transform: perspective(400px) rotateY(-8deg);
}

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

See the Pen Moving the image inside a box by Temani Afif on CodePen.

Здесь изображение, часть которого обведена зеленой рамкой. Зеленая рамка представляет обрезаемую область. Обрезаемая область имеет квадратную форму, и полное изображение немного выходит за правый край. При наведении курсора мыши мы сдвигаем изображение влево (при помощи свойства transform: translateX()), в то время как обрезанная область остается на месте.

Если мы скроем часть изображения, которая выходит за пределы обрезанной области (overflow: hidden), и добавим поворот, который мы сделали ранее, мы получим желаемый эффект "имитации" параллакса:

See the Pen Adding the rotation + the overflow by Temani Afif on CodePen.

Однако в этом примере для решения этой задачи используется дополнительный элемент <div>. Наша же цель - сделать то же самое без дополнительных элементов. Вот тут нам и поможет CSS свойство clip-path:


img {
  --f: .1; /* коэффициент параллакса (чем меньше, тем лучше) */

  --_f: calc(100 * var(--f) / (1 + var(--f)));
  width: 250px; /* размер изображения */
  aspect-ratio: calc(1 + var(--f));
  object-fit: cover;
  clip-path: inset(0 var(--_f) 0 0);
  transition: .5s;
}

img:hover {
  clip-path: inset(0 0 0 var(--_f));
  transform: translateX(calc(-1 * var(--_f)))
}

Переменная --f управляет эффектом, определяя, как сильно должно сдвигаться изображение. Мы используем эту переменную для вычисления значения aspect-ratio, которое немного больше 1, чтобы создать неквадратное изображение, которое мы позже обрезаем, чтобы получить квадратное изображение. Переменная --_f определяет ту часть изображения, которую нужно вырезать, чтобы получить соотношение сторон 1:1.

Демонстрация того, как значения clip-path изменяется, когда на изображение наводится курсор мыши
Демонстрация того, как значения clip-path изменяется, когда на изображение наводится курсор мыши

Свойство clip-path определяет обрезанную область, и нам нужно, чтобы эта область оставалась фиксированной. Вот почему мы добавили трансляцию при наведении курсора мыши, чтобы сдвинуть изображение в противоположном направлении от clip-path.

See the Pen Clip-path + translation by Alexei Goloviznin on CodePen.

Добавляем поворот и получаем идеальный эффект:


img {
  --f: .1; /* коэффициент параллакса (чем меньше, тем лучше) */
  --r: 10px; /* радиус */

  --_f: calc(100%*var(--f)/(1 + var(--f)));
  --_a: calc(90deg*var(--f));

  width: 250px; /* размер изображения */
  aspect-ratio: calc(1 + var(--f));
  object-fit: cover;
  clip-path: inset(0 var(--_f) 0 0 round var(--r));
  transform: perspective(400px) translateX(0px) rotateY(var(--_a));
  transition: .5s;
}

img:hover {
  clip-path: inset(0 0 0 var(--_f) round var(--r));
  transform: perspective(400px) translateX(calc(-1*var(--_f))) rotateY(calc(-1*var(--_a)));
}

Мы слегка закруглили углы у обрезанной области, чтобы сделать эффект немного более причудливым. Если вам интересно, почему мы не использовали свойство border-radius то, это потому, что это свойство плохо работает с обрезанными областями. К счастью, clip-path принимает значение round, которое задает такие же закругленные углы.

Вот и все! Мы и закончили делать этот элегантный эффект наведения курсора мыши на изображение.

See the Pen 3D parallax effect on hover by Alexei Goloviznin on CodePen.

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

CSS 3D поворот

В этой последней демонстрации мы добавим изображению глубины и преобразуем его в 3D блок.

See the Pen 3D images with hover effect by Alexei Goloviznin on CodePen.

В этот раз мы пропустим часть, касающуюся повороту, потому что она ничем не отличается от того, что мы уже делали в предыдущих частях этой статьи. Вместо этого давайте сосредоточимся на 3D части и использовании свойств outline и clip-path. На следующем рисунке показано, как они работают вместе, формируя 3D блок.

Как работают outline и clip-path

Свойство outline обрамляет изображение (1), затем смещается, закрывая изображение (2), чтобы его можно было обрезать в форме прямоугольника (3).

Вот как это работает. Сначала мы добавим отступы в верхнюю и нижнюю части изображения и применяем outline, который имеет полупрозрачный черный цвет.

Затем, мы применяем отрицательное значение для свойства outline-offset, чтобы контур от свойства outline закрывал изображение с левой и правой сторон, но не затрагивал его верхнюю и нижнюю части:


img {
  --d: 18px;  /* глубина */

  padding-block: var(--d);
  outline: var(--d) solid #0008;
  outline-offset: calc(-1 * var(--d));
}

Обратите внимание, что мы определили переменную --d, которая управляет толщиной контура outline. Это то, что придает изображению глубину.

Последний шаг - добавить clip-path. Для этого нам нужен многоугольник с восемью точками.

Изображение с восемью точками обрезанной многоугольной формы
Восемь точек обрезанной формы многоугольника

Красные точки зафиксированы на одном месте, а зеленые - это те, которые мы будем анимировать, чтобы показать глубину. Конечно, это далеко не 3D блок, но следующий этап, где мы добавляем поворот, дает лучший результат.

Поведение обработанного изображения при наведении курсора мыши
Поведение обработанного изображения при наведении курсора мыши

Изображение начинается с глубины, направленной в одном направлении (слева), затем при наведении курсора мыши поворачивается и скрывает глубину, становясь плоским (в середине). Мы также можем изменить направление глубины (справа).

Изначально изображение поворачивается с некоторой перспективой. Зеленые точки справа совмещаются с красными. Таким образом, мы скрываем контур с этой стороны, чтобы он был виден только с левой стороны. Слева у нас есть наш 3D блок с глубиной.

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

Затем мы продолжаем поворот, и зеленые точки справа смещаются, в то время как левые остаются на месте. Мы получаем тот же 3D эффект, но с глубиной с правой стороны.

Теперь наберитесь терпения, потому что следующая часть кода поначалу будет выглядеть довольно запутанной. Это связано с несколькими новыми переменными и восьмиточечным многоугольником, который мы рисуем в свойстве clip-path.


@property --_l {
  syntax: "";
  initial-value: 0px;
  inherits: true;
}
@property --_r {
  syntax: "";
  initial-value: 0px;
  inherits: true;
}

img {
  --d: 18px;  /* глубина */
  --a: 20deg; /* угол */
  --x: 10px;

  --_d: calc(100% - var(--d));
  --_l: 0px;
  --_r: 0px;

  clip-path: polygon(
    /* Две зеленые точки слева */
    var(--_l) calc(var(--_d) - var(--x)),
    var(--_l) calc(var(--d)  + var(--x)),

    /* Две красные точки наверху */
    var(--d) var(--d),var(--_d) var(--d),

    /* Две зеленые точки справа */
    calc(var(--_d) + var(--_r)) calc(var(--d)  + var(--x)),
    calc(var(--_d) + var(--_r)) calc(var(--_d) - var(--x)),

    /* Две красные точки внизу */
    var(--_d) var(--_d),var(--d) var(--_d)

    );
  transition: transform .3s, --_r .15s, --_l .15s .15s;
}

/* Изменяем точки многоугольника при наведении курсора */
img:hover{
  --_l: var(--d);
  --_r: var(--d);
  --_i: -1;
  transition-delay: 0s, .15s, 0s;
}

Обратите внимание, что для определения положения зеленых точек мы используем переменные --_l и --_r. Мы анимируем эти переменные от 0 значения глубины (--d). Декларация @property позволяют нам анимировать переменные, указывая тип их значений (<length>).

Примечание: В настоящее время декларация @property поддерживается не всеми браузерами. Поэтому в самой демонстрации мы добавили запасной вариант с немного другой анимацией.

После того, как многоугольник нарисован в свойстве clip-path, следующее, что делает код, - это задает свойство transition, которое обрабатывает поворот. Полный поворот длится .3s, поэтому зеленые точки должны переходить в два раза медленнее (.15s). При наведении курсора мыши левые точки многоугольника перемещаются немедленно (0s), в то время как правые точки перемещаются в два раза медленнее (благодаря задержке .15s). Когда мы перестаем наводить курсор мыши, мы используем разные задержки, потому что нам нужно, чтобы правые точки перемещались немедленно (0s), в то время как левые точки перемещались в два раза медленнее.

А что там за переменная --x? Если вы посмотрите на первое изображение, которое иллюстрирует точки clip-path, то вы заметите, что зеленые точки немного смещены от верхнего и нижнего краев, что логично для имитации 3D эффекта. Переменная --x определяет, как велико это смещение, но математика, лежащая в ее основе, немного сложна, и ее нелегко выразить в CSS. Таким образом, мы задаем ее значение вручную в зависимости от каждого случая, пока не получим нужный результат.

Вот у нас есть окончательный вариант эффекта!

See the Pen 3D images with hover effect by Alexei Goloviznin on CodePen.

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

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

И все это мы сделали таким образом, что потребовалась всего одна строка HTML. Никаких элементов div. Никаких классов или идентификаторов. Никаких псевдоэлементов. Все, что нам было нужно, - это всего лишь один тег <img>. Да, вполне возможно, что увеличение HTML разметки, сделало CSS код немного проще, но тот факт, что он опирается на один простой HTML элемент, означает, что CSS можно использовать более широко. Ведь CSS достаточно мощный, чтобы сделать все что угодно всего с одним HTML элементом!