В этой статье мы подробно рассмотрим, как реализовать анимацию с эффектом Hover для карточек, как показано ниже.
![](https://habrastorage.org/getpro/habr/upload_files/761/11a/79e/76111a79e4f16851042b94de46ee516a.gif)
Основные трудности, которые нам предстоит решить:
При движении мыши отображается граница и световой эффект текущего края карты.
Эффект только рядом с курсором: Существует несколько подходов для реализации этого эффекта. Можно рассчитать зону вокруг курсора и применять эффект только в этой области, но это может быть слишком ресурсоемко.
Альтернативный подход — использовать маску. Можно заранее создать общий эффект с градиентной границей и внутренним свечением, а затем сделать так, чтобы маска следовала за курсором.
Чтобы создать такой эффект, потребуется адаптировать его к движению мыши и переключать эффекты в зависимости от положения курсора. На данный момент чистый CSS не справляется с этим, поэтому понадобится немного JavaScript.
В следующем разделе мы разберем, как пошагово реализовать этот эффект, учитывая упомянутые трудности.
Создание статического эффекта
Сначала мы создаем статический эффект, который виден, когда мышь не находится над карточкой. Вот пример:
![](https://habrastorage.org/getpro/habr/upload_files/4fb/76b/3d8/4fb76b3d8bc88670d9e8722e6d60cf70.png)
Каждая карточка имеет размытое фоновое изображение, которое должно быть индивидуальным для каждой картинки, поэтому одну универсальную фоновую картинку использовать нельзя.
Кроме того, размытое фоновое изображение должно примерно соответствовать цвету настоящего изображения.
Для решения этой задачи можно использовать CSS-фильтр filter: blur().
Код для этого достаточно простой.
<div></div>
:root {
--pic: url("https://oss.aiyuzhou8.com/2023/05/08-.jpg");
}
div {
position: relative;
margin: auto;
width: 350px;
height: 500px;
border-radius: 30px;
overflow: hidden;
&::before,
&::after {
content: "";
position: absolute;
background: var(--pic);
background-size: cover;
background-position: center;
border-radius: 30px;
}
&::before {
inset: 0;
filter: blur(20px);
}
&::after {
inset: 50px;
}
}
Мы используем псевдоэлементы: один для исходного изображения, другой для размытого фона.
![](https://habrastorage.org/getpro/habr/upload_files/73f/202/c42/73f202c42cefab3dc7fe3acd39a72bec.png)
Это позволяет адаптировать размытое изображение к любому фону.
![](https://habrastorage.org/getpro/habr/upload_files/f68/f4e/b7d/f68f4eb7d91d6dac8f0213d1616edc81.png)
Реализация градиентной границы
Следующий шаг – создание градиентной границы.
Для этого используется conic-gradient
. Потребуется дополнительный элемент div
, чтобы создать нужный эффект. Мы накладываем этот элемент на предыдущий эффект, немного увеличив его размер.
<div></div>
div {
width: 350px;
height: 500px;
border-radius: 30px;
background: conic-gradient(#03a9f4, #e91e63, #9c27b0, #ff5722, #03a9f4);
}
Получаем такой градиент.
![](https://habrastorage.org/getpro/habr/upload_files/a04/26c/384/a0426c384ec6bdd4fac58354a3ac06b3.png)
Мы накладываем этот элемент на предыдущий эффект, немного увеличив его размер.
![](https://habrastorage.org/getpro/habr/upload_files/535/77d/07d/53577d07d15a0c45f0c3b6e2611ccbf3.png)
При внимательном рассмотрении можно заметить, что помимо градиентной границы, текущий эффект также имеет внутреннее свечение. Он очень кривой, это не совсем то, чего мы хотим добиться:
![](https://habrastorage.org/getpro/habr/upload_files/87a/d30/581/87ad30581269af084dfc09e7a30022ff.png)
Исследование прозрачности с filter: blur()
Эффект прозрачности возникает из-за того, что элемент с фильтром filter: blur()
создаёт плавное затухание прозрачности от краёв к центру.
Проведем простой эксперимент
<div></div>
<div></div>
div {
position: relative;
width: 200px;
height: 300px;
border-radius: 10px;
border: 1px solid #000;
background: conic-gradient(#03a9f4, #e91e63, #9c27b0, #ff5722, #03a9f4);
&::before {
content: "";
position: absolute;
inset: 10px;
border-radius: 10px;
background: #fff;
border: 1px solid #000;
}
}
Мы создаем два одинаковых div,
где сам элемент имеет угловой градиентный фон.
Затем, используя его псевдоэлемент, мы устанавливаем белый фон в середине элемента, на расстоянии 10px от границы. Эффект выглядит следующим образом:
![](https://habrastorage.org/getpro/habr/upload_files/78f/b51/3f7/78fb513f7602ebe6433cea76f98f3fba.png)
На данный момент два элемента не отличаются друг от друга. Но далее мы добавляем фильтр: blur()
эффект "Размытие по Гауссу" к псевдоэлементу второго элемента:
div:nth-child(2) {
&::before {
filter: blur(20px);
}
}
В этот момент снова посмотрите на эффекты:
![](https://habrastorage.org/getpro/habr/upload_files/4f0/1ff/059/4f01ff059d1b537cecb71041952f121e.png)
На краях белого элемента действительно наблюдается эффект уменьшения прозрачности в направлении внутрь.
Конечно, поскольку размытие по Гауссу распространяется и наружу, приведенный выше DEMO выглядит не очень четко, поэтому мы можем предотвратить распространение гауссова размытия наружу, применив дополнительный слой контейнеров с overflow: hidden
.
Давайте еще немного подправим макет:
<div class="g-father">
<div class="g-child"></div>
</div>
<div class="g-father">
<div class="g-child"></div>
</div>
.g-father {
position: relative;
width: 200px;
height: 300px;
border-radius: 10px;
border: 1px solid #000;
background: conic-gradient(#03a9f4, #e91e63, #9c27b0, #ff5722, #03a9f4);
.g-child {
position: absolute;
inset: 10px;
border-radius: 10px;
border: 1px solid #000;
overflow: hidden;
&::before {
content: "";
position: absolute;
inset: 0;
background: #fff;
border-radius: 10px;
}
}
}
.g-father:nth-child(2) {
.g-child::before {
filter: blur(20px);
}
}
Сейчас если мы посмотрим на весь эффект, элемент с установленным фильтром: blur()
будет иметь прозрачный распад от краев к центру, и эффект будет очень очевиден:
![](https://habrastorage.org/getpro/habr/upload_files/33a/bfa/024/33abfa0240206f2a75f195cfed31bfcb.png)
Добавление событий мыши и масок для достижения эффекта
Итак, к этому моменту нам удалось добиться такого эффекта:
![](https://habrastorage.org/getpro/habr/upload_files/016/d77/bf4/016d77bf4c06de2bccf9191837aa0547.png)
Исходя из вышеописанного эффекта, в итоге мы добиваемся такого эффекта:
![](https://habrastorage.org/getpro/habr/upload_files/697/4ec/d59/6974ecd5942487ee4b01a11d190a726f.gif)
Мы будем использовать обработчик события mousemove
для отслеживания перемещения курсора и комбинировать его с маской для создания желаемого эффекта.
В реализации эффекта используются несколько слоев. Конкретно, граница и внутреннее свечение — это отдельные слои, которые формируют фоновый эффект.
![](https://habrastorage.org/getpro/habr/upload_files/a50/d62/227/a50d622273ba8294fe4124deb39bf794.png)
Что нам нужно сделать:
Создать маску с радиальным градиентом с помощью
radial-gradient()
.Отслеживать событие перемещения мыши и перемещать центр маски в зависимости от положения курсора.
Добавить дополнительный слой, чтобы градиентный элемент фона появлялся только при наведении мыши (Hover), и исчезал, когда курсор покидает область элемента.
Примерный код выглядит следующим образом:
<div id="g-container">
<div id="g-img"></div>
</div>
:root {
--x: 0;
--y: 0;
}
#g-container {
position: relative;
width: 350px;
height: 500px;
border-radius: 30px;
}
#g-img {
position: absolute;
inset: 0px;
border-radius: 30px;
background: conic-gradient(#03a9f4, #e91e63, #9c27b0, #ff5722, #03a9f4);
mask: radial-gradient(
circle at var(--x) var(--y),
#000,
#000,
transparent,
transparent,
transparent
);
}
const container = document.getElementById("g-container");
const img = document.getElementById("g-img");
container.addEventListener("mousemove", (event) => {
img.style.visibility = 'visible';
const target = event.target;
const rect = target.getBoundingClientRect();
var offsetX = event.clientX - rect.left;
var offsetY = event.clientY - rect.top;
var percentX = (Math.min(Math.max(offsetX / rect.width, 0), 1) * 100).toFixed(2);
var percentY = (Math.min(Math.max(offsetY / rect.height, 0), 1) * 100).toFixed(2);;
console.log('X: ' + percentX + '%');
console.log('Y: ' + percentY + '%');
container.setAttribute('style', `--x: ${percentX}%;--y: ${percentY}%;`);
});
container.addEventListener("mouseout", (event) => {
img.style.visibility = 'hidden';
});
Перемещение мыши по графику дает такой эффект:
![](https://habrastorage.org/getpro/habr/upload_files/82b/9a6/9c2/82b9a69c2290a32e6a522f9d1ef4f4ac.gif)
Объедините два первых слоя, чтобы в итоге получился идеальный эффект:
![](https://habrastorage.org/getpro/habr/upload_files/14b/e13/13c/14be1313cb383ef619db2caaa694273d.gif)
Комментарии (4)
monochromer
15.08.2024 13:32Нужно отметить, что статья является переводом https://www.cnblogs.com/coco1s/p/18358267.
Finesse
В вашей градиентной рамке середина не прозрачная, а белая. Каким образом вы делаете её прозрачной, чтобы наложить на фоновый слой?
Тут либо картинка не та, либо пропущено много шагов.
Ну и ссылочку бы на CodePen, чтобы посмотреть код целиком.
AlexeyRybakov Автор
https://codepen.io/Chokcoco/pen/NWmQLvZ
Finesse
Понятно, фоновый слой находится над слоем конусного градиента, а не под ним. Получается, размытие фонового слоя убивает двух зайцев: собственно размытие и мягкое свечение рамки. Теперь картинка после «Мы накладываем этот элемент на предыдущий эффект» выглядит логично. Было бы намного понятнее, если бы вы показали схему слоёв как в начале статьи.
(оригинальная ссылка от автора)