Анимации Hover и эффекты Blur: Полный гид по созданию динамических карточек

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

76111a79e4f16851042b94de46ee516a.gif

Основные трудности, которые нам предстоит решить:

  1. При движении мыши отображается граница и световой эффект текущего края карты.

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

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

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

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

Создание статического эффекта

Сначала мы создаем статический эффект, который виден, когда мышь не находится над карточкой. Вот пример:

4fb76b3d8bc88670d9e8722e6d60cf70.png

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

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

Для решения этой задачи можно использовать CSS-фильтр filter: blur(). Код для этого достаточно простой.

: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;
    }
}

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

73f202c42cefab3dc7fe3acd39a72bec.png

Это позволяет адаптировать размытое изображение к любому фону.

f68f4eb7d91d6dac8f0213d1616edc81.png

Реализация градиентной границы

Следующий шаг — создание градиентной границы.

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

div {
    width: 350px;
    height: 500px;
    border-radius: 30px;
    background: conic-gradient(#03a9f4, #e91e63, #9c27b0, #ff5722, #03a9f4);
}

Получаем такой градиент.

a0426c384ec6bdd4fac58354a3ac06b3.png

Мы накладываем этот элемент на предыдущий эффект, немного увеличив его размер.

53577d07d15a0c45f0c3b6e2611ccbf3.png

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

87ad30581269af084dfc09e7a30022ff.png

Исследование прозрачности с filter: blur ()

Эффект прозрачности возникает из-за того, что элемент с фильтром filter: blur() создаёт плавное затухание прозрачности от краёв к центру.

Проведем простой эксперимент

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, где сам элемент имеет угловой градиентный фон.

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

78fb513f7602ebe6433cea76f98f3fba.png

На данный момент два элемента не отличаются друг от друга. Но далее мы добавляем фильтр: blur() эффект «Размытие по Гауссу» к псевдоэлементу второго элемента:

div:nth-child(2) {
    &::before {
        filter: blur(20px);
    }
}

В этот момент снова посмотрите на эффекты:

4f01ff059d1b537cecb71041952f121e.png

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

Конечно, поскольку размытие по Гауссу распространяется и наружу, приведенный выше DEMO выглядит не очень четко, поэтому мы можем предотвратить распространение гауссова размытия наружу, применив дополнительный слой контейнеров с overflow: hidden.

Давайте еще немного подправим макет:

.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() будет иметь прозрачный распад от краев к центру, и эффект будет очень очевиден:

33abfa0240206f2a75f195cfed31bfcb.png

Добавление событий мыши и масок для достижения эффекта

Итак, к этому моменту нам удалось добиться такого эффекта:

016d77bf4c06de2bccf9191837aa0547.png

Исходя из вышеописанного эффекта, в итоге мы добиваемся такого эффекта:

8a59b61856bb6a5d3887696b95ca1045.gif

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

В реализации эффекта используются несколько слоев. Конкретно, граница и внутреннее свечение — это отдельные слои, которые формируют фоновый эффект.

a50d622273ba8294fe4124deb39bf794.png

Что нам нужно сделать:

  1. Создать маску с радиальным градиентом с помощью radial-gradient().

  2. Отслеживать событие перемещения мыши и перемещать центр маски в зависимости от положения курсора.

  3. Добавить дополнительный слой, чтобы градиентный элемент фона появлялся только при наведении мыши (Hover), и исчезал, когда курсор покидает область элемента.

Примерный код выглядит следующим образом:

: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';
});

Перемещение мыши по графику дает такой эффект:

82b9a69c2290a32e6a522f9d1ef4f4ac.gif

Объедините два первых слоя, чтобы в итоге получился идеальный эффект:

14be1313cb383ef619db2caaa694273d.gif

© Habrahabr.ru