Крутые эффекты наведения, основанные на свойстве background

alexei15/12/2023 - 09:37
Крутые эффекты наведения, основанные на свойстве background

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

В следующих примерах вы видите результат работы свойства background, а также CSS функции calc(). Мы собираемся разобраться, как скомпоновать все это в хорошо оптимизированный код!

See the Pen hover effect by Temani Afif on CodePen.

Итак, приступим!

Эффект №1

See the Pen Background Hover 1 by Geoff Graham on CodePen.

Давайте рассмотрим первый эффект. У него следующий код:


.hover-1 {
  background: linear-gradient(#1095c1 0 0) var(--p, 0) / var(--p, 0) no-repeat;
  transition: .4s, background-position 0s;
}

.hover-1:hover {
  --p: 100%;
  color: #fff;
}

Если мы опустим переход по цвету (который необязателен), то для реализации этого эффекта нам понадобятся всего три CSS декларации. Вы, наверное, удивлены, насколько здесь мало кода, но давайте посмотрим, как мы этого добились.

Во-первых, давайте начнем с простого перехода по background-size:

See the Pen Background-size animation by Temani Afif on CodePen.

Мы анимируем размер линейного градиента от 0 100% до 100% 100%. Это означает, что ширина изменяется от 0 до 100%, при этом высота остается на 100%. Пока ничего сложного.

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


background-image: linear-gradient(#1095c1 0 0);

Синтаксис может показаться немного странным, но в данном случае мы сообщаем браузеру, что он должен применить один цвет к двум цветовым точками ограничения, и в CSS этого достаточно для определения градиента. У нас обе цветовые точки ограничения заданы значением 0, поэтому браузер автоматически последнюю точку ограничения сделает 100% и зальет наш градиент тем же цветом. Вот вам и краткая запись!

Со свойством background-size мы можем не указывать высоту, потому что по умолчанию градиенты всегда отображаются на всю высоту элемента. Мы можем просто задать переход от background-size: 0 к background-size: 100%.


.hover-1 {
  background-image: linear-gradient(#1095c1 0 0);
  background-size: 0;
  background-repeat: no-repeat;
  transition: .4s;
}

.hover-1:hover {
  background-size: 100%;
}

Чтобы избежать дублирования свойства background-size, давайте введем пользовательскую CSS переменную:


.hover-1 {
  background-image: linear-gradient(#1095c1 0 0);
  background-size: var(--p, 0%);
  background-repeat: no-repeat;
  transition: .4s;
}

.hover-1:hover {
  --p: 100%;
} 

Мы не задаем начальное значение для переменной --p, поэтому будет использовано браузерное значение по умолчанию (в нашем случае 0%). При наведении курсора мыши мы определяем значение, которое будет подменять начальное значение (в данном случае на 100%).

Теперь, давайте все свойства, определяющие фон, соберем в одно короткое свойство background:


.hover-1 {
  background: linear-gradient(#1095c1 0 0) left / var(--p, 0%) no-repeat;
  transition: .4s;
}

.hover-1:hover {
  --p: 100%;
} 

Вот мы практически и закончили! Обратите внимание, что мы добавили значение left (для background-position), которое является обязательным при определении размера в сокращенном свойстве background. Ну и помимо этого, для создания нашего эффекта оно все равно нужно.

Также, нам нужно обновить положение при наведении курсора мыши. Мы можем сделать это в два шага:

  1. Увеличиваем размер вправо при наведении курсора мыши.
  2. Уменьшаем размер влево при выводе курсора мыши.

Для этого при наведении курсора нам также нужно обновить background-position:

See the Pen Adding background-position by Temani Afif on CodePen.

Мы добавили в наш код две вещи:

  1. Значение right свойству background-position в селектор, отвечающий за стили при наведении курсора мыши
  2. Значение 0s для background-position в свойство transition-duration

Это означает, что при наведении курсора мы мгновенно меняем значение background-position с left (вот для этого нам и нужно было это значение!) на right, таким образом ширина фона будет плавно увеличиваться вправо. Затем, когда курсор мыши покидает ссылку, переход воспроизводится в обратном порядке, от right к left, создавая впечатление, что мы уменьшаем ширину фона влево. Наш эффект готов!

Но вы говорили, что нужно только три декларации, а их четыре!

Точно подмечено! Значения left и right можно заменить на 0 0 и 100% 0 соответственно; и поскольку наш градиент по умолчанию уже установлен на всю высоту, мы можем получить нужный результат с помощью значений 0 и 100%.


.hover-1 {
  background: linear-gradient(#1095c1 0 0) 0 / var(--p, 0%) no-repeat;
  transition: .4s, background-position 0s;
}

.hover-1:hover {
  --p: 100%;
  background-position: 100%;
}

Видите, в background-position и --p используется одно и то же значение? Вот здесь мы и можем сократить код до трех деклараций:


.hover-1 {
  background: linear-gradient(#1095c1 0 0) var(--p, 0%) / var(--p,0%) no-repeat;
  transition: .4s, background-position 0s;
}

.hover-1:hover {
  --p: 100%;
} 

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

А как нам сделать обратное преобразование, чтобы эффект начинался с левой стороны и заканчивался справа?

Мы все еще можем использовать одну переменную. По сути все, что нам требуется это, чтобы градиент изменялся от 100% к 0%, а не от 0% к 100%. У нас есть разница от 100%, которую мы можем выразить при помощи функции calc():


.hover-1 {
  background: linear-gradient(#1095c1 0 0) calc(100% - var(--p,0%)) / var(--p,0%) no-repeat;
  transition: .4s, background-position 0s;
}

.hover-1:hover {
  --p: 100%;
} 

Переменная --p будет меняться от 0% до 100%, но позиция фона, благодаря calc(), изменится от 100% до 0%.

У нас все еще три декларации и одна CSS переменная, но другой эффект.

See the Pen The opposite hover effect by Temani Afif on CodePen.

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

Эффект №2

See the Pen Background Hover 2 by Geoff Graham on CodePen.

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

Диаграмма, показывающая анимацию эффекта из трех частей
Диаграмма, показывающая анимацию эффекта из трех частей

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

Таким образом, мы создаем переход по background-position, за которым следует переход по background-size. Давайте переведем это в код:


.hover-2 {
  background-image: linear-gradient(#1095c1 0 0);
  background-size: 100% .08em; /* .08em - фиксированная высота; если нужно, измените */
  background-position: /* ??? */;
  background-repeat: no-repeat;
  transition: background-size .3s, background-position .3s .3s;
}

.hover-2:hover {
  transition: background-size .3s .3s, background-position .3s;
  background-size: 100% 100%;
  background-position: /* ??? */;
}

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

Теперь вопрос заключается в следующем: какие значения мы используем для background-position? В этом примере мы оставили их пустыми. Значения для background-size вполне тривиальны, а для background-position это не так. И если мы оставим фактическую конфигурацию, мы не сможем переместить наш градиент.

Наш градиент имеет ширину, равную 100%, поэтому мы не можем использовать процентные значения у background-position для его перемещения.

Процентные значения, используемые с background-position, всегда вызывают затруднения, особенно когда вы используете их впервые. Их поведение неинтуитивно, но четко определено и легко понимается, если уловить логику, стоящую за этим.

Хитрость заключается в том, чтобы изменить ширину на что-то другое, чем 100%. Давайте использовать 200%. Не стоит беспокоиться о том, что фон превышает элемент, ведь переполнение все равно скрыто.


.hover-2 {
  background-image: linear-gradient(#1095c1 0 0);
  background-size: 200% .08em;
  background-position: 200% 100%;
  background-repeat: no-repeat;
  transition: background-size .3s, background-position .3s .3s;
}

.hover-2:hover {
  transition: background-size .3s .3s, background-position .3s;
  background-size: 200% 100%;
  background-position: 100% 100%;
} 

И вот что у нас получается:

See the Pen Hover Effect 2 by Temani Afif on CodePen.

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


.hover-2 {
  background: 
    linear-gradient(#1095c1 0 0) no-repeat
    var(--p, 200%) 100% / 200% var(--p, .08em);
  transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s));
}

.hover-2:hover {
  --p: 100%;
  --t: .3s;
}

Мы сведем все свойства фона в одно сокращенное свойство background, затем используем переменную --p для определения значений. Размеры меняются от .08em до 100%, а позиция от 200% до 100%.

Также, мы используем переменную --t для оптимизации свойства перехода transition. При наведении курсора мыши переменная принимает значение .3s, что дает нам декларацию:


transition: .3s .3s, background-position .3s 0s;

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


transition: .3s 0s, background-position .3s .3s;

А разве свойство background-position не должно быть в переходе?

Это действительно еще одна оптимизация, которую мы можем провести. Если мы не указываем никаких свойств, это означает "все" свойства, поэтому переход определяется для всех свойств (включая background-size и background-position). Затем, переход вновь определяется для background-position, что аналогично определению для background-size, затем для background-position.

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

А можем ли мы все же оптимизировать код и использовать только одну пользовательскую переменную?

Как не странно, можем! Не вдаваясь в особые подробности, наш код можно представить в следующем виде:


.hover-2 {
  background: 
    linear-gradient(#1095c1 0 0) no-repeat
    calc(200% - var(--i, 0) * 100%) 100% / 200% calc(100% * var(--i, 0) + .08em);
  transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - calc(var(--i, 0) * .3s));
}

.hover-2:hover {
  --i: 1;
}

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

И вновь у нас всего три декларации и отличный эффект!

See the Pen Final hover effect 2 by Temani Afif on CodePen.

Эффект №3

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

Вот иллюстрация того, что мы будем делать:

Диаграмма, показывающая анимацию эффекта из двух градиентов
Диаграмма, показывающая анимацию эффекта из двух градиентов

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

Вот так это выглядит на CSS:


.hover-3 {
  background-image:
    linear-gradient(#1095c1 0 0),
    linear-gradient(#1095c1 0 0);
  background-repeat: no-repeat;
  background-size: 50% .08em;
  background-position:
    -100% 100%,
    200% 0;
  transition: background-size .3s, background-position .3s .3s;
}

.hover-3:hover {
  background-size: 50% 100%;
  background-position:
    0 100%,
    100% 0;  
  transition: background-size .3s .3s, background-position .3s;
}

Код почти такой же, как у предыдущих рассмотренных нами эффектов. Единственное отличие заключается в том, что у нас есть два градиента с двумя разными позициями. Значения позиций могут выглядеть странно, но, опять же, это связано с тем, как в CSS работают процентные значения со свойством background-position.

Теперь давайте оптимизировать! Вы уже должны были уловить основную идею: мы используем сокращенные свойства, пользовательские переменные и функцию calc().


.hover-3 {
  --c: no-repeat linear-gradient(#1095c1 0 0);
  background: 
    var(--c) calc(-100% + var(--p, 0%)) 100% / 50% var(--p, .08em),
    var(--c) calc( 200% - var(--p, 0%)) 0    / 50% var(--p, .08em);
  transition: .3s var(--t, 0s), background-position .3s calc(.3s - var(--t, 0s));
}

.hover-3:hover {
  --p: 100%;
  --t: 0.3s;
}

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

See the Pen Hover effect 3 by Temani Afif on CodePen.

В данном примере в качестве значения высоты фонового градиента мы используем 50.1% вместо 50%. Это предотвращает появления визуального промежутка между градиентами. По той же причине мы добавили по 1% к позициям градиентов.

Теперь давайте проведем второй этап оптимизации с использованием переменной-переключателем:


.hover-3 {
  --c: no-repeat linear-gradient(#1095c1 0 0);
  background: 
    var(--c) calc(-100% + var(--i, 0) * 100%) 100% / 50% calc(100% * var(--i, 0) + .08em),
    var(--c) calc( 200% - var(--i, 0) * 100%) 0 / 50% calc(100% * var(--i, 0) + .08em);
  transition: .3s calc(var(--i, 0) * .3s), background-position .3s calc(.3s - var(--i, 0) * .3s);
}

.hover-3:hover {
  --i: 1;
}

See the Pen Final hover effect 3 by Temani Afif on CodePen.

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

Эффект №4

See the Pen Background Hover 4 by Geoff Graham on CodePen.

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

Последний наш эффект основывается на двух конических градиентах и некоторых дополнительных вычислениях.

Диаграмма, показывающая анимацию эффекта из двух конических градиентов
Диаграмма, показывающая анимацию эффекта из двух конических градиентов

Изначально у нас есть два градиента с нулевыми размерами (этап 1). На этапе 2 мы увеличиваем размер каждого из них. Мы продолжаем увеличивать их ширину, пока они полностью не покроют элемент, как показано на этапе 3. После этого мы сдвигаем их вниз, чтобы обновить их положение. Это та самая "волшебная" часть эффекта. Поскольку оба градиента будут одинакового цвета, изменение их положения на этапе 4 визуально будетнезаметно, но мы увидим разницу, как только уменьшим размер при наведении курсора мыши на этапе 5.

Если вы сравните этапы 2 и 5, то вы заметите, что градиенты имеют разный наклон. Давайте переведем это в код:


.hover-4 {
  background-image:
    conic-gradient(/* ??? */),
    conic-gradient(/* ??? */);
  background-position:
    0 0,
    100% 0;
  background-size: 0% 200%;
  background-repeat: no-repeat;
  transition: background-size .4s, background-position 0s;
}

.hover-4:hover {
  background-size: /* ??? */ 200%;
  background-position:
    0 100%,
    100% 100%;
}

Позиции здесь довольно ясны. Один градиент начинается вверху слева (0 0) и заканчивается внизу слева (0 100%), в то время как другой градиент начинается вверху справа (100% 0) и заканчивается внизу справа (100% 100%).

Мы используем переход по background-position и background-size. При этом нам нужен переход только по background-size. А свойство background-position, как и раньше, должны изменяться мгновенно, поэтому для продолжительности переходы мы присваиваем значение 0s.

Что касается размеров, то оба градиента должны иметь ширину 0 и удвоенную высоту (0% 200%). Позже мы увидим, как меняются их размеры при наведении. Давайте сначала определим конфигурацию градиента.

Диаграмма конфигураций двух конических градиентов
Диаграмма конфигураций двух конических градиентов

Обратите внимание, что для второго градиента (обозначенного зеленым цветом) нам нужно знать высоту, чтобы использовать ее внутри свойства conic-gradient. По этой причине мы добавляем свойство line-height, которое задает высоту элемента, а затем используем это же значение для пропущенных значений конического градиента.


.hover-4 {
  --c: #1095c1;
  line-height: 1.2em;
  background-image:
    conic-gradient(from -135deg at 100%  50%, var(--c) 90deg, #0000 0),
    conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0);
  background-position:
    0 0,
    100% 0;
  background-size: 0% 200%;
  background-repeat: no-repeat;
  transition: background-size .4s, background-position 0s;
}

.hover-4:hover {
  background-size: /* ??? */ 200%;
  background-position:
    0 100%,
    100% 100%;
} 

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

50% у каждого градиента оставляет разрыв
Если мы используем 50% для значений background-size у обоих градиентов, то остается большой разрыв

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


.hover-4:hover {
  background-size: calc(50% + .6em) 200%;
  background-position:
    0 100%,
    100% 100%;
} 

Вот что мы получаем после оптимизации, как в предыдущих примерах:


.hover-4 {
  --c: #1095c1;
  line-height: 1.2em;
  background:
    conic-gradient(from -135deg at 100%  50%, var(--c) 90deg, #0000 0) 
      0  var(--p, 0%) / var(--s, 0%) 200% no-repeat,
    conic-gradient(from -135deg at 1.2em 50%, #0000 90deg, var(--c) 0) 
      100% var(--p, 0%) / var(--s, 0%) 200% no-repeat;
  transition: .4s, background-position 0s;
}

.hover-4:hover {
  --p: 100%;
  --s: calc(50% + .6em);
}

See the Pen Hover effect 4 by Temani Afif on CodePen.

А что насчет варианта с одной пользовательской переменной?

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

Заключение

Мы создали четыре отличных эффекта наведения! И хотя это разные эффекты, все они используют один и тот же подход к использованию CSS свойства background, пользовательских переменных и функции calc(). Различные комбинации позволили нам создавать разные версии, используя одни и те же методы, которые в результате дают чистый, легко обслуживаемый код.

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