DevGang
Авторизоваться

Анимированная граница в CSS

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

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

Настройка

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

<button>Add border</button>
<div></div>
:root {
 --border-colour: black;
 --border-thickness: 5px;
 --padding: 8px; --border-transition-time: 0ms;
}

@media screen and (prefers-reduced-motion: no-preference) {
 :root {
 --border-transition-time: 250ms;
 }
}

div {
 position: relative;
 width: 200px;
 height: 100px;
 top: 50px;
 left: 50px;
 background-color: pink;
 padding: var(--padding);
}

Здесь я добавил несколько пользовательских свойств, потому что мы будем использовать эти значения несколько раз. И я просто немного отодвинул div от края, чтобы его было легче увидеть. Единственная строка кода div, которая нужна на вашем элементе, - это position: relative, все остальное - только для этой демонстрации.

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

const button = document.querySelector('button');
const div = document.querySelector('div');
button.addEventListener('click', function() { 
  div.classList.add('border');
});

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

Установка границы

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

div::before,
div::after {
 content: '';
 position: absolute;
 width: 0;
 height: 0;
 z-index: -1;
}

div::before {
 top: calc(var(--padding) * -1 - var(--border-thickness));
 left: calc(var(--padding) * -1);
 border-top: 0px solid var(--border-colour);
 border-right: 0px solid var(--border-colour);
}

div::after {
 right: calc(var(--padding) * -1);
 bottom: calc(var(--padding) * -1 - var(--border-thickness));
 border-bottom: 0px solid var(--border-colour); border-left: 0px solid var(--border-colour);
}

Здесь происходит много всего, так что давайте разберемся.

Оба набора границ начинаются без ширины и высоты, поэтому мы их не видим. И z-index равен -1, так что если у вас там есть текст или что-то, с чем можно взаимодействовать, псевдоэлементы не окажутся поверх него.

Оба псевдоэлемента содержат границы – потому что в противном случае они будут занимать весь div. На данный момент ширина этих границ равна 0px, поскольку в противном случае вы бы увидели квадраты в левом верхнем и правом нижнем углах: несмотря на то, что у них нет ширины и высоты, граница все равно видна.

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

Добавление границы

Когда класс border добавлен, нам остается только задать размер и ширину границы обоих псевдоэлементов:

div.border::before,
div.border::after {
 width: calc(100% + var(--padding) * 2);
 height: calc(100% + var(--padding) * 2 + var(--border-thickness));
 border-width: var(--border-thickness);
}

Конечно, это просто добавит мгновенные границы. Нам также нужно сделать переход, чтобы мы могли видеть, как они анимируются:

div::before {
 /* ... */
 transition:
   width var(--border-transition-time) linear,
   height var(--border-transition-time) linear var(--border-transition-time);
}

div::after {
 /* ... */
 transition:
   width var(--border-transition-time) linear calc(var(--border-transition-time) * 2),
   height var(--border-transition-time) linear calc(var(--border-transition-time) * 3),
   border-width 0s linear calc(var(--border-transition-time) * 2);}

Они немного длинные, поэтому давайте пройдемся по ним.

Сначала мы хотим увидеть границу, проходящую через верх. Поэтому мы изменяем ширину псевдоэлемента before в параметре --border-transition-time.

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

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

Наконец, мы хотим, чтобы граница шла вверх по левой стороне, поэтому теперь нам нужно подождать три раза --border-transition-time.

И еще нам нужно задержать изменение ширины границы с 0px до --border-thickness, иначе, как только верхняя граница начнет анимироваться, мы увидим квадрат границы после псевдоэлементов.

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

Готовый код

Вот окончательный вариант кода:

HTML:

<button>Add border</button>
<div></div>

JS:

const button = document.querySelector('button');
const div = document.querySelector('div');

button.addEventListener('click', function() {
  div.classList.add('border');
});

CSS:

:root {
  --border-colour: black;  
  --border-thickness: 5px;
  --padding: 8px;
  --border-transition-time: 0ms;
}

@media screen and (prefers-reduced-motion: no-preference) { 
  :root {
    --border-transition-time: 250ms;
  }
}

div {
  position: relative;
  width: 200px;
  height: 100px;
  top: 50px;
  left: 50px;
  background-color: pink;
  padding: var(--padding);
}

div::before,
div::after {
  content: '';
  position: absolute;
  width: 0;
  height: 0;
  z-index: -1;
}

div::before {
  top: calc(var(--padding) * -1 - var(--border-thickness));
  left: calc(var(--padding) * -1);
  border-top: 0px solid var(--border-colour);
  border-right: 0px solid var(--border-colour);
  transition: width var(--border-transition-time) linear, height var(--border-transition-time) linear var(--border-transition-time);
}

div::after {
  right: calc(var(--padding) * -1);
  bottom: calc(var(--padding) * -1 - var(--border-thickness));
  border-bottom: 0px solid var(--border-colour);
  border-left: 0px solid var(--border-colour);
  transition: border-width 0s linear calc(var(--border-transition-time) * 2), width var(--border-transition-time) linear calc(var(--border-transition-time) * 2), height var(--border-transition-time) linear calc(var(--border-transition-time) * 3);
}

div.border::before,
div.border::after {
  width: calc(100% + var(--padding) * 2);
  height: calc(100% + var(--padding) * 2 + var(--border-thickness));
  border-width: var(--border-thickness);
}

Благодарю за прочтение!

Источник:

#JavaScript #CSS #HTML
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В этом месте могла бы быть ваша реклама

Разместить рекламу