Анимированная граница в 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);
}
Благодарю за прочтение!