Задача
СкопированоСкелетон — это временная «заглушка». Он показывается вместо основного контента страницы на время загрузки данных. Пользователь видит не пустоту, а однотонные блоки, похожие на будущее содержимое: текст, кнопки или картинки. Часто их анимируют, что создаёт эффект загрузки. Это распространённый паттерн в веб-интерфейсах.
Скелетон выполняет ту же роль, что и спиннер, но воспринимается приятнее. Пользователь не только видит, что страница загружается, но и примерно понимает, какой контент появится после загрузки.
В рецепте разберём создание анимированного скелетона для карточки статьи:
Готовое решение
СкопированоДля создания скелетона потребуется немного HTML.
<div class="skeleton"></div>
<div class="skeleton"></div>
И чуть побольше CSS.
.skeleton { /* Общий цвет для блоков внутри скетелона */ --skeleton-bg-color: #5F377D; /* Внутренний отступ для текста внутри карточки */ --padding: 15px; /* Параметры для анимации загрузки */ --blur-width: 600%; --blur-height: 200%; --blur-start-position: 130%; --blur-end-position: 0%; --blur-background: linear-gradient( 130deg, transparent 0, transparent 35%, #18191c99 40%, #18191ce6 50%, #18191c99 60%, transparent 70%, transparent 100% ); /* Параметры для скелетона картинки */ --image-width: 100%; --image-height: 250px; --image-x: 0px; --image-y: 0px; --image-position: var(--image-x) var(--image-y); --image-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона заголовка */ --title-width: calc(80%); --title-height: 40px; --title-x: var(--padding); --title-y: calc(var(--image-height) + 25px); --title-position: var(--title-x) var(--title-y); --title-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); --title-margin-bottom: 25px; /* Общие параметры скелетонов для текстовых блоков */ --text-gap: 18px; --text-line-height: 18px; /* Параметры для скелетона первой строки текста */ --text-line-first-width: calc(100% - var(--padding) - var(--padding)); --text-line-first-height: var(--text-line-height); --text-line-first-x: var(--padding); --text-line-first-y: calc(var(--title-y) + var(--title-height) + var(--title-margin-bottom)); --text-line-first-position: var(--text-line-first-x) var(--text-line-first-y); --text-line-first-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона второй строки текста */ --text-line-second-width: calc(100% - var(--padding) - var(--padding)); --text-line-second-height: var(--text-line-height); --text-line-second-x: var(--padding); --text-line-second-y: calc(var(--text-line-first-y) + var(--text-line-first-height) + var(--text-gap)); --text-line-second-position: var(--text-line-second-x) var(--text-line-second-y); --text-line-second-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона третьей строки текста */ --text-line-third-width: 70%; --text-line-third-height: var(--text-line-height); --text-line-third-x: var(--padding); --text-line-third-y: calc(var(--text-line-second-y) + var(--text-line-second-height) + var(--text-gap)); --text-line-third-position: var(--text-line-third-x) var(--text-line-third-y); --text-line-third-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); width: 340px; height: 450px; /* Цвет фона */ background-color: #5F377D33; /* no-repeat нужен, чтобы скелетоны вложенных блоков не повторялись */ background-repeat: no-repeat; /* Задаём цвет и форму вложенных блоков */ background-image: var(--blur-background), var(--image-skeleton), var(--title-skeleton), var(--text-line-first-skeleton), var(--text-line-second-skeleton), var(--text-line-third-skeleton); /* Задаём размеры вложенных блоков */ background-size: var(--blur-width) var(--blur-height), var(--image-width) var(--image-height), var(--title-width) var(--title-height), var(--text-line-first-width) var(--text-line-first-height), var(--text-line-second-width) var(--text-line-second-height), var(--text-line-third-width) var(--text-line-third-height); /* Задаём расположение вложенных блоков */ background-position: var(--blur-start-position), var(--image-position), var(--title-position), var(--text-line-first-position), var(--text-line-second-position), var(--text-line-third-position); /* Добавляем анимацию загрузки */ animation: loading 1.8s ease-in infinite;}/* Анимация загрузки работает за счет сдвига блюра. Все остальные блоки неподвижны: не меняют своей позиции.*/@keyframes loading { to { background-position: var(--blur-end-position), var(--image-position), var(--title-position), var(--text-line-first-position), var(--text-line-second-position), var(--text-line-third-position); }}
.skeleton { /* Общий цвет для блоков внутри скетелона */ --skeleton-bg-color: #5F377D; /* Внутренний отступ для текста внутри карточки */ --padding: 15px; /* Параметры для анимации загрузки */ --blur-width: 600%; --blur-height: 200%; --blur-start-position: 130%; --blur-end-position: 0%; --blur-background: linear-gradient( 130deg, transparent 0, transparent 35%, #18191c99 40%, #18191ce6 50%, #18191c99 60%, transparent 70%, transparent 100% ); /* Параметры для скелетона картинки */ --image-width: 100%; --image-height: 250px; --image-x: 0px; --image-y: 0px; --image-position: var(--image-x) var(--image-y); --image-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона заголовка */ --title-width: calc(80%); --title-height: 40px; --title-x: var(--padding); --title-y: calc(var(--image-height) + 25px); --title-position: var(--title-x) var(--title-y); --title-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); --title-margin-bottom: 25px; /* Общие параметры скелетонов для текстовых блоков */ --text-gap: 18px; --text-line-height: 18px; /* Параметры для скелетона первой строки текста */ --text-line-first-width: calc(100% - var(--padding) - var(--padding)); --text-line-first-height: var(--text-line-height); --text-line-first-x: var(--padding); --text-line-first-y: calc(var(--title-y) + var(--title-height) + var(--title-margin-bottom)); --text-line-first-position: var(--text-line-first-x) var(--text-line-first-y); --text-line-first-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона второй строки текста */ --text-line-second-width: calc(100% - var(--padding) - var(--padding)); --text-line-second-height: var(--text-line-height); --text-line-second-x: var(--padding); --text-line-second-y: calc(var(--text-line-first-y) + var(--text-line-first-height) + var(--text-gap)); --text-line-second-position: var(--text-line-second-x) var(--text-line-second-y); --text-line-second-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона третьей строки текста */ --text-line-third-width: 70%; --text-line-third-height: var(--text-line-height); --text-line-third-x: var(--padding); --text-line-third-y: calc(var(--text-line-second-y) + var(--text-line-second-height) + var(--text-gap)); --text-line-third-position: var(--text-line-third-x) var(--text-line-third-y); --text-line-third-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); width: 340px; height: 450px; /* Цвет фона */ background-color: #5F377D33; /* no-repeat нужен, чтобы скелетоны вложенных блоков не повторялись */ background-repeat: no-repeat; /* Задаём цвет и форму вложенных блоков */ background-image: var(--blur-background), var(--image-skeleton), var(--title-skeleton), var(--text-line-first-skeleton), var(--text-line-second-skeleton), var(--text-line-third-skeleton); /* Задаём размеры вложенных блоков */ background-size: var(--blur-width) var(--blur-height), var(--image-width) var(--image-height), var(--title-width) var(--title-height), var(--text-line-first-width) var(--text-line-first-height), var(--text-line-second-width) var(--text-line-second-height), var(--text-line-third-width) var(--text-line-third-height); /* Задаём расположение вложенных блоков */ background-position: var(--blur-start-position), var(--image-position), var(--title-position), var(--text-line-first-position), var(--text-line-second-position), var(--text-line-third-position); /* Добавляем анимацию загрузки */ animation: loading 1.8s ease-in infinite; } /* Анимация загрузки работает за счет сдвига блюра. Все остальные блоки неподвижны: не меняют своей позиции. */ @keyframes loading { to { background-position: var(--blur-end-position), var(--image-position), var(--title-position), var(--text-line-first-position), var(--text-line-second-position), var(--text-line-third-position); } }
Разбор решения
СкопированоРазметка
СкопированоДля создания скелетона карточки нам потребуется всего один div
.
<div class="skeleton"></div>
<div class="skeleton"></div>
Вложенные блоки нарисуем на CSS.
Стили
СкопированоДля создания заглушек содержимого карточки используем линейный градиент и свойства семейства background
:
Для начала заведём CSS-переменные, чтобы не повторять одни и те же значения и проще ориентироваться в коде. Параметры каждого нижеследующего блока используют размер и положение предыдущего. Важно, чтобы размеры и форма блоков были похожи на части контента, для которого делается скелетон.
.skeleton { /* Общий цвет для блоков внутри скетелона */ --skeleton-bg-color: #5F377D; /* Внутренний отступ для текста внутри карточки */ --padding: 15px; /* Параметры для анимации загрузки */ --blur-width: 600%; --blur-height: 200%; --blur-start-position: 130%; --blur-end-position: 0%; --blur-background: linear-gradient( 130deg, transparent 0, transparent 35%, #18191c99 40%, #18191ce6 50%, #18191c99 60%, transparent 70%, transparent 100% ); /* Параметры для скелетона картинки */ --image-width: 100%; --image-height: 250px; --image-x: 0px; --image-y: 0px; --image-position: var(--image-x) var(--image-y); --image-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона заголовка */ --title-width: calc(80%); --title-height: 40px; --title-x: var(--padding); --title-y: calc(var(--image-height) + 25px); --title-position: var(--title-x) var(--title-y); --title-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); --title-margin-bottom: 25px; /* Общие параметры скелетонов для текстовых блоков */ --text-gap: 18px; --text-line-height: 18px; /* Параметры для скелетона первой строки текста */ --text-line-first-width: calc(100% - var(--padding) - var(--padding)); --text-line-first-height: var(--text-line-height); --text-line-first-x: var(--padding); --text-line-first-y: calc(var(--title-y) + var(--title-height) + var(--title-margin-bottom)); --text-line-first-position: var(--text-line-first-x) var(--text-line-first-y); --text-line-first-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона второй строки текста */ --text-line-second-width: calc(100% - var(--padding) - var(--padding)); --text-line-second-height: var(--text-line-height); --text-line-second-x: var(--padding); --text-line-second-y: calc(var(--text-line-first-y) + var(--text-line-first-height) + var(--text-gap)); --text-line-second-position: var(--text-line-second-x) var(--text-line-second-y); --text-line-second-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона третьей строки текста */ --text-line-third-width: 70%; --text-line-third-height: var(--text-line-height); --text-line-third-x: var(--padding); --text-line-third-y: calc(var(--text-line-second-y) + var(--text-line-second-height) + var(--text-gap)); --text-line-third-position: var(--text-line-third-x) var(--text-line-third-y); --text-line-third-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color));}
.skeleton { /* Общий цвет для блоков внутри скетелона */ --skeleton-bg-color: #5F377D; /* Внутренний отступ для текста внутри карточки */ --padding: 15px; /* Параметры для анимации загрузки */ --blur-width: 600%; --blur-height: 200%; --blur-start-position: 130%; --blur-end-position: 0%; --blur-background: linear-gradient( 130deg, transparent 0, transparent 35%, #18191c99 40%, #18191ce6 50%, #18191c99 60%, transparent 70%, transparent 100% ); /* Параметры для скелетона картинки */ --image-width: 100%; --image-height: 250px; --image-x: 0px; --image-y: 0px; --image-position: var(--image-x) var(--image-y); --image-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона заголовка */ --title-width: calc(80%); --title-height: 40px; --title-x: var(--padding); --title-y: calc(var(--image-height) + 25px); --title-position: var(--title-x) var(--title-y); --title-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); --title-margin-bottom: 25px; /* Общие параметры скелетонов для текстовых блоков */ --text-gap: 18px; --text-line-height: 18px; /* Параметры для скелетона первой строки текста */ --text-line-first-width: calc(100% - var(--padding) - var(--padding)); --text-line-first-height: var(--text-line-height); --text-line-first-x: var(--padding); --text-line-first-y: calc(var(--title-y) + var(--title-height) + var(--title-margin-bottom)); --text-line-first-position: var(--text-line-first-x) var(--text-line-first-y); --text-line-first-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона второй строки текста */ --text-line-second-width: calc(100% - var(--padding) - var(--padding)); --text-line-second-height: var(--text-line-height); --text-line-second-x: var(--padding); --text-line-second-y: calc(var(--text-line-first-y) + var(--text-line-first-height) + var(--text-gap)); --text-line-second-position: var(--text-line-second-x) var(--text-line-second-y); --text-line-second-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); /* Параметры для скелетона третьей строки текста */ --text-line-third-width: 70%; --text-line-third-height: var(--text-line-height); --text-line-third-x: var(--padding); --text-line-third-y: calc(var(--text-line-second-y) + var(--text-line-second-height) + var(--text-gap)); --text-line-third-position: var(--text-line-third-x) var(--text-line-third-y); --text-line-third-skeleton: linear-gradient(var(--skeleton-bg-color), var(--skeleton-bg-color)); }
Далее зададим размеры скелетона.
.skeleton { ... width: 340px; height: 450px; ...}
.skeleton { ... width: 340px; height: 450px; ... }
И опишем вложенные блоки с помощью CSS-свойств из группы background
:
background
— задаём цвет фона;- color background
— отключаем повторение блоков с помощью значения- repeat no
;- repeat background
— задаём цвет и форму вложенных блоков;- image background
— задаём размеры вложенных блоков;- size background
— задаём расположение вложенных блоков.- position
Блоки описываем в порядке их визуального следования: сначала самый верхний (блюр), затем остальные.
.skeleton { ... /* Цвет фона */ background-color: #5F377D33; /* no-repeat нужен, чтобы скелетоны вложенных блоков не повторялись */ background-repeat: no-repeat; /* Задаём цвет и форму вложенных блоков */ background-image: var(--blur-background), var(--image-skeleton), var(--title-skeleton), var(--text-line-first-skeleton), var(--text-line-second-skeleton), var(--text-line-third-skeleton); /* Задаём размеры вложенных блоков */ background-size: var(--blur-width) var(--blur-height), var(--image-width) var(--image-height), var(--title-width) var(--title-height), var(--text-line-first-width) var(--text-line-first-height), var(--text-line-second-width) var(--text-line-second-height), var(--text-line-third-width) var(--text-line-third-height); /* Задаём расположение вложенных блоков */ background-position: var(--blur-start-position), var(--image-position), var(--title-position), var(--text-line-first-position), var(--text-line-second-position), var(--text-line-third-position); ...}
.skeleton { ... /* Цвет фона */ background-color: #5F377D33; /* no-repeat нужен, чтобы скелетоны вложенных блоков не повторялись */ background-repeat: no-repeat; /* Задаём цвет и форму вложенных блоков */ background-image: var(--blur-background), var(--image-skeleton), var(--title-skeleton), var(--text-line-first-skeleton), var(--text-line-second-skeleton), var(--text-line-third-skeleton); /* Задаём размеры вложенных блоков */ background-size: var(--blur-width) var(--blur-height), var(--image-width) var(--image-height), var(--title-width) var(--title-height), var(--text-line-first-width) var(--text-line-first-height), var(--text-line-second-width) var(--text-line-second-height), var(--text-line-third-width) var(--text-line-third-height); /* Задаём расположение вложенных блоков */ background-position: var(--blur-start-position), var(--image-position), var(--title-position), var(--text-line-first-position), var(--text-line-second-position), var(--text-line-third-position); ... }
Добавим анимацию для скелетона.
.skeleton { ... /* Добавляем анимацию загрузки */ animation: loading 1.8s ease-in infinite;}
.skeleton { ... /* Добавляем анимацию загрузки */ animation: loading 1.8s ease-in infinite; }
И опишем анимацию с помощью @keyframes
. Для создания эффекта загрузки будем сдвигать только блюр.
/* Анимация загрузки работает за счет сдвига блюра. Все остальные блоки неподвижны.*/@keyframes loading { to { background-position: var(--blur-end-position), var(--image-position), var(--title-position), var(--text-line-first-position), var(--text-line-second-position), var(--text-line-third-position); }}
/* Анимация загрузки работает за счет сдвига блюра. Все остальные блоки неподвижны. */ @keyframes loading { to { background-position: var(--blur-end-position), var(--image-position), var(--title-position), var(--text-line-first-position), var(--text-line-second-position), var(--text-line-third-position); } }
В итоге получился скелетон, который довольно точно описывает форму и структуру карточки.
Подсказки
Скопировано💡 Если нужно использовать один и тот же контейнер для показа скелетона и контента, можно воспользоваться псевдоклассом :empty
.
.card { /* стили контента */}.card:empty { /* стили скелетона */}
.card { /* стили контента */ } .card:empty { /* стили скелетона */ }