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

Первым решением был простой статический SVG с конфетти на заднем плане. Позже команда обнаружила 12 разных статических конфетти по всему коду и поняла, что нужен другой подход. Подробностями решения делимся под катом, пока начинается наш курс по Frontend-разработке.



Первое решение


Вот так выглядело первое решение:



Всего несколько месяцев спустя мы обнаружили, что этот же подход используется в продукте 12 раз, каждая итерация отличается от других. Появились другие варианты, например confetti-bold.svg.



Поиск подхода


Как известно многим пользователям Stack Overflow, лучший способ начать работу — это опереться на хороший пример. К счастью, в интернете можно найти множество примеров конфетти. 


Кто-то визуализируют его на JavaScript, а кто-то решает проблему при помощи gif. Иногда для конфетти используются целые сцены WebGL, написанные на Three.JS


Нам же нужно было нечто предельно портативное, максимально близкое простым HTML и CSS. Не хотелось вводить зависимости или инициализировать JS каждый раз, когда нужно показать конфетти.

В поиске решения распространённых проблем я часто начинаю с Codepen. И этот поиск привёл меня к Энди О'Браена.


Его подход прост и элегантен, анимация зацикливается! Используется Sass-генерируемый CSS для анимации отдельных элементов DOM. И конфетти похоже на последнюю итерацию от нашего дизайнера Вивиан Чжан.

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


Надо сказать, что у этого подхода есть некоторые проблемы. В наших контекстах мы не имеем компонентного подхода в необходимом масштабе. Иными словами, инженерам приходится копировать и вставлять десяток div'ов с конфетти и заботиться о том, чтобы управлять z-index и событиями указателя. 

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

Готов поспорить, что вместо представления в виде элементов DOM мы могли бы поместить всё это в SVG и представить как фоновое изображение, это дало бы нам гораздо больший контроль над позиционированием, размером и повторением элементов.

Организация SVG


А что насчёт SVG в качестве фонового изображения? Давайте попробуем. Здесь придётся создать SVG-элемент для размещения кусочков конфетти — и это вопрос распределения прямоугольников на холсте, а ещё выбора размеров, которые нам нравятся. 

Заменим DOM из предыдущих Codepen, чтобы код был переносимым. Чтобы работать быстро, я воспользовался Figma, но всё это можно написать и от руки:


<svg width="600" height="90" viewBox="0 0 600 90" fill="none" xmlns="http://www.w3.org/2000/svg">
    <rect x="42" y="0" width="6" height="10"/>
    <rect x="84" y="0" width="6" height="10"/>
    <rect x="126" y="0" width="5" height="13"/>
    <rect x="168" y="0" width="5" height="13"/>
    <rect x="210" y="0" width="6" height="10"/>
    <rect x="252" y="0" width="5" height="13"/>
    <rect x="294" y="0" width="6" height="10"/>
    <rect x="336" y="0" width="5" height="13"/>
    <rect x="378" y="0" width="5" height="13"/>
    <rect x="420" y="0" width="6" height="10"/>
    <rect x="462" y="0" width="6" height="10"/>
    <rect x="504" y="0" width="5" height="13"/>
    <rect x="546" y="0" width="6" height="10"/>
</svg>

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

<svg>
    <style type="text/css">...</style>
    <rect />
</svg>

И теперь все селекторы CSS nth-child смещены на один, потому что CSS считает элемент стиля дочерним. Думаю, мы перенесём этот элемент в самый низ.

Кроме того, функции Sass не будут работать в SVG, поэтому придётся компилировать код в CSS и вставлять его в SVG. Codepen может выполнить компиляцию, а я скопирую и вставлю результат.

<svg width="600" height="90" viewBox="0 0 600 90" fill="none" xmlns="http://www.w3.org/2000/svg">
    <rect x="42" y="0" width="6" height="10"/>
    <rect x="84" y="0" width="6" height="10"/>
    <rect x="126" y="0" width="5" height="13"/>
    <rect x="168" y="0" width="5" height="13"/>
    <rect x="210" y="0" width="6" height="10"/>
    <rect x="252" y="0" width="5" height="13"/>
    <rect x="294" y="0" width="6" height="10"/>
    <rect x="336" y="0" width="5" height="13"/>
    <rect x="378" y="0" width="5" height="13"/>
    <rect x="420" y="0" width="6" height="10"/>
    <rect x="462" y="0" width="6" height="10"/>
    <rect x="504" y="0" width="5" height="13"/>
    <rect x="546" y="0" width="6" height="10"/>

    <style type="text/css">
        rect {
            opacity: 0;
        }
        rect:nth-child(1) {
            transform: rotate(-145deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 88ms;
            animation-duration: 631ms;
        }
        rect:nth-child(2) {
            transform: rotate(164deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 131ms;
            animation-duration: 442ms;
        }
        rect:nth-child(3) {
            transform: rotate(4deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 92ms;
            animation-duration: 662ms;
        }
        rect:nth-child(4) {
            transform: rotate(-175deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 17ms;
            animation-duration: 593ms;
        }
        rect:nth-child(5) {
            transform: rotate(-97deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 122ms;
            animation-duration: 476ms;
        }
        rect:nth-child(6) {
            transform: rotate(57deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 271ms;
            animation-duration: 381ms;
        }
        rect:nth-child(7) {
            transform: rotate(-46deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 131ms;
            animation-duration: 619ms;
        }
        rect:nth-child(8) {
            transform: rotate(-65deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 85ms;
            animation-duration: 668ms;
        }
        rect:nth-child(9) {
            transform: rotate(13deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 128ms;
            animation-duration: 377ms;
        }
        rect:nth-child(10) {
            transform: rotate(176deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 311ms;
            animation-duration: 508ms;
        }
        rect:nth-child(11) {
            transform: rotate(108deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 108ms;
            animation-duration: 595ms;
        }
        rect:nth-child(12) {
            transform: rotate(62deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 105ms;
            animation-duration: 375ms;
        }
        rect:nth-child(13) {
            transform: rotate(16deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 149ms;
            animation-duration: 491ms;
        }
        rect:nth-child(odd) {
            fill: #65BB5C;
        }
        rect:nth-child(even) {
            z-index: 1;
            fill: #33AAFF;
        }
        rect:nth-child(4n) {
            animation-duration: 1400ms;
            fill: #F23B14;
        }
        rect:nth-child(3n) {
            animation-duration: 1750ms;
            animation-delay: 700ms;
        }
        rect:nth-child(4n-7) {
            fill: #2A2F6A;
        }
        rect:nth-child(6n) {
            fill: #FBBA23;
        }

        @keyframes blast {
            from {
                opacity: 0;
            }
            20% {
                opacity: 1;
            }
            to {
                transform: translateY(90px);
            }
        }
    </style>
</svg>

Сохраните код как SVG и откройте в браузере. Вы увидите анимированное конфетти. Но есть проблема с расположением кусочков. В SVG преобразования начинаются из другой точки: начало координат расположено в левом верхнем углу. Эту проблему я решил, измерив расстояния в Figma и захардкодив некоторые исходные данные, например transform-origin: 45px 5px. Не идеально, но достаточно близко к центру:

<svg width="600" height="90" viewBox="0 0 600 90" fill="none" xmlns="http://www.w3.org/2000/svg">
    <rect x="42" y="0" width="6" height="10"/>
    <rect x="84" y="0" width="6" height="10"/>
    <rect x="126" y="0" width="5" height="13"/>
    <rect x="168" y="0" width="5" height="13"/>
    <rect x="210" y="0" width="6" height="10"/>
    <rect x="252" y="0" width="5" height="13"/>
    <rect x="294" y="0" width="6" height="10"/>
    <rect x="336" y="0" width="5" height="13"/>
    <rect x="378" y="0" width="5" height="13"/>
    <rect x="420" y="0" width="6" height="10"/>
    <rect x="462" y="0" width="6" height="10"/>
    <rect x="504" y="0" width="5" height="13"/>
    <rect x="546" y="0" width="6" height="10"/>

    <style type="text/css">
        rect {
            opacity: 0;
        }
        rect:nth-child(1) {
            transform-origin: 45px 5px;
            transform: rotate(-145deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 88ms;
            animation-duration: 631ms;
        }
        rect:nth-child(2) {
            transform-origin: 87px 5px;
            transform: rotate(164deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 131ms;
            animation-duration: 442ms;
        }
        rect:nth-child(3) {
            transform-origin: 128px 6px;
            transform: rotate(4deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 92ms;
            animation-duration: 662ms;
        }
        rect:nth-child(4) {
            transform-origin: 170px 6px;
            transform: rotate(-175deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 17ms;
            animation-duration: 593ms;
        }
        rect:nth-child(5) {
            transform-origin: 213px 5px;
            transform: rotate(-97deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 122ms;
            animation-duration: 476ms;
        }
        rect:nth-child(6) {
            transform-origin: 255px 6px;
            transform: rotate(57deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 271ms;
            animation-duration: 381ms;
        }
        rect:nth-child(7) {
            transform-origin: 297px 5px;
            transform: rotate(-46deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 131ms;
            animation-duration: 619ms;
        }
        rect:nth-child(8) {
            transform-origin: 338px 6px;
            transform: rotate(-65deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 85ms;
            animation-duration: 668ms;
        }
        rect:nth-child(9) {
            transform-origin: 380px 6px;
            transform: rotate(13deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 128ms;
            animation-duration: 377ms;
        }
        rect:nth-child(10) {
            transform-origin: 423px 5px;
            transform: rotate(176deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 311ms;
            animation-duration: 508ms;
        }
        rect:nth-child(11) {
            transform-origin: 465px 5px;
            transform: rotate(108deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 108ms;
            animation-duration: 595ms;
        }
        rect:nth-child(12) {
            transform-origin: 506px 6px;
            transform: rotate(62deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 105ms;
            animation-duration: 375ms;
        }
        rect:nth-child(13) {
            transform-origin: 549px 5px;
            transform: rotate(16deg);
            animation: blast 700ms infinite ease-out;
            animation-delay: 149ms;
            animation-duration: 491ms;
        }
        rect:nth-child(odd) {
            fill: #65BB5C;
        }
        rect:nth-child(even) {
            z-index: 1;
            fill: #33AAFF;
        }
        rect:nth-child(4n) {
            animation-duration: 1400ms;
            fill: #F23B14;
        }
        rect:nth-child(3n) {
            animation-duration: 1750ms;
            animation-delay: 700ms;
        }
        rect:nth-child(4n-7) {
            fill: #2A2F6A;
        }
        rect:nth-child(6n) {
            fill: #FBBA23;
        }

        @keyframes blast {
            from {
                opacity: 0;
            }
            20% {
                opacity: 1;
            }
            to {
                transform: translateY(90px);
            }
        }
    </style>
</svg>

И позволим себе последнее изящество. Заметно, что из-за специфического начала координат SVG в нижней части некоторые кусочки конфетти обрываются в конце анимации. Эта проблема решается подталкиванием кусочка вверх и в сторону от canvas:

<rect x="42" y="-10" width="6" height="10"/>
<rect x="84" y="-10" width="6" height="10"/>
<rect x="126" y="-13" width="5" height="13"/>
<rect x="168" y="-13" width="5" height="13"/>
<rect x="210" y="-10" width="6" height="10"/>
<rect x="252" y="-13" width="5" height="13"/>
<rect x="294" y="-10" width="6" height="10"/>
<rect x="336" y="-13" width="5" height="13"/>
<rect x="378" y="-13" width="5" height="13"/>
<rect x="420" y="-10" width="6" height="10"/>
<rect x="462" y="-10" width="6" height="10"/>
<rect x="504" y="-13" width="5" height="13"/>
<rect x="546" y="-10" width="6" height="10"/>

Доставка


Отлично! У нас есть один SVG-файл, который теперь отвечает за всю анимацию, и мы можем сделать его фоном любого элемента. Инкапсуляцию можно продолжить, закодировав весь SVG (вместе с CSS) в CSS-файл дизайн-системы. Получится CSS в SVG в CSS. Почему бы и нет? Достаточно указать SVG как фоновое изображение:

.bg-confetti-animated {
    background-repeat: repeat-x;
    background-position: top -10px center;
    background-image: url("data:image/svg+xml,%3Csvg width='600' height='90' viewBox='0 0 600 90' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='42' y='-10' width='6' height='10'/%3E%3Crect x='84' y='-10' width='6' height='10'/%3E%3Crect x='126' y='-13' width='5' height='13'/%3E%3Crect x='168' y='-13' width='5' height='13'/%3E%3Crect x='210' y='-10' width='6' height='10'/%3E%3Crect x='252' y='-13' width='5' height='13'/%3E%3Crect x='294' y='-10' width='6' height='10'/%3E%3Crect x='336' y='-13' width='5' height='13'/%3E%3Crect x='378' y='-13' width='5' height='13'/%3E%3Crect x='420' y='-10' width='6' height='10'/%3E%3Crect x='462' y='-10' width='6' height='10'/%3E%3Crect x='504' y='-13' width='5' height='13'/%3E%3Crect x='546' y='-10' width='6' height='10'/%3E%3Cstyle type='text/css'%3E rect %7B opacity: 0; %7D rect:nth-child(1) %7B transform-origin: 45px 5px; transform: rotate(-145deg); animation: blast 700ms infinite ease-out; animation-delay: 88ms; animation-duration: 631ms; %7D rect:nth-child(2) %7B transform-origin: 87px 5px; transform: rotate(164deg); animation: blast 700ms infinite ease-out; animation-delay: 131ms; animation-duration: 442ms; %7D rect:nth-child(3) %7B transform-origin: 128px 6px; transform: rotate(4deg); animation: blast 700ms infinite ease-out; animation-delay: 92ms; animation-duration: 662ms; %7D rect:nth-child(4) %7B transform-origin: 170px 6px; transform: rotate(-175deg); animation: blast 700ms infinite ease-out; animation-delay: 17ms; animation-duration: 593ms; %7D rect:nth-child(5) %7B transform-origin: 213px 5px; transform: rotate(-97deg); animation: blast 700ms infinite ease-out; animation-delay: 122ms; animation-duration: 476ms; %7D rect:nth-child(6) %7B transform-origin: 255px 6px; transform: rotate(57deg); animation: blast 700ms infinite ease-out; animation-delay: 271ms; animation-duration: 381ms; %7D rect:nth-child(7) %7B transform-origin: 297px 5px; transform: rotate(-46deg); animation: blast 700ms infinite ease-out; animation-delay: 131ms; animation-duration: 619ms; %7D rect:nth-child(8) %7B transform-origin: 338px 6px; transform: rotate(-65deg); animation: blast 700ms infinite ease-out; animation-delay: 85ms; animation-duration: 668ms; %7D rect:nth-child(9) %7B transform-origin: 380px 6px; transform: rotate(13deg); animation: blast 700ms infinite ease-out; animation-delay: 128ms; animation-duration: 377ms; %7D rect:nth-child(10) %7B transform-origin: 423px 5px; transform: rotate(176deg); animation: blast 700ms infinite ease-out; animation-delay: 311ms; animation-duration: 508ms; %7D rect:nth-child(11) %7B transform-origin: 465px 5px; transform: rotate(108deg); animation: blast 700ms infinite ease-out; animation-delay: 108ms; animation-duration: 595ms; %7D rect:nth-child(12) %7B transform-origin: 506px 6px; transform: rotate(62deg); animation: blast 700ms infinite ease-out; animation-delay: 105ms; animation-duration: 375ms; %7D rect:nth-child(13) %7B transform-origin: 549px 5px; transform: rotate(16deg); animation: blast 700ms infinite ease-out; animation-delay: 149ms; animation-duration: 491ms; %7D rect:nth-child(odd) %7B fill: %2365BB5C; %7D rect:nth-child(even) %7B z-index: 1; fill: %2333AAFF; %7D rect:nth-child(4n) %7B animation-duration: 1400ms; fill: %23F23B14; %7D rect:nth-child(3n) %7B animation-duration: 1750ms; animation-delay: 700ms; %7D rect:nth-child(4n-7) %7B fill: %232A2F6A; %7D rect:nth-child(6n) %7B fill: %23FBBA23; %7D @keyframes blast %7B from %7B opacity: 0; %7D 20%25 %7B opacity: 1; %7D to %7B transform: translateY(90px); %7D %7D %3C/style%3E%3C/svg%3E%0A");
}

В таких случаях я пользуюсь yoksel.

Тонкости анимации


Некоторые люди с трудом переносят анимацию. Другие предпочитают избегать её в принципе. К счастью, браузеры предлагают медиазапрос media (prefers-reduced-motion). Давайте воспользуемся им, чтобы показать статически отрисованное конфетти. Статическую версию в Figma нарисовала Вивиан:


Остаётся встроить её в медиа-запрос:

.bg-confetti-animated {
    background-repeat: repeat-x;
    background-position: top -10px center;
    background-image: url("data:image/svg+xml,%3Csvg width='600' height='90' viewBox='0 0 600 90' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='42' y='-10' width='6' height='10'/%3E%3Crect x='84' y='-10' width='6' height='10'/%3E%3Crect x='126' y='-13' width='5' height='13'/%3E%3Crect x='168' y='-13' width='5' height='13'/%3E%3Crect x='210' y='-10' width='6' height='10'/%3E%3Crect x='252' y='-13' width='5' height='13'/%3E%3Crect x='294' y='-10' width='6' height='10'/%3E%3Crect x='336' y='-13' width='5' height='13'/%3E%3Crect x='378' y='-13' width='5' height='13'/%3E%3Crect x='420' y='-10' width='6' height='10'/%3E%3Crect x='462' y='-10' width='6' height='10'/%3E%3Crect x='504' y='-13' width='5' height='13'/%3E%3Crect x='546' y='-10' width='6' height='10'/%3E%3Cstyle type='text/css'%3E rect %7B opacity: 0; %7D rect:nth-child(1) %7B transform-origin: 45px 5px; transform: rotate(-145deg); animation: blast 700ms infinite ease-out; animation-delay: 88ms; animation-duration: 631ms; %7D rect:nth-child(2) %7B transform-origin: 87px 5px; transform: rotate(164deg); animation: blast 700ms infinite ease-out; animation-delay: 131ms; animation-duration: 442ms; %7D rect:nth-child(3) %7B transform-origin: 128px 6px; transform: rotate(4deg); animation: blast 700ms infinite ease-out; animation-delay: 92ms; animation-duration: 662ms; %7D rect:nth-child(4) %7B transform-origin: 170px 6px; transform: rotate(-175deg); animation: blast 700ms infinite ease-out; animation-delay: 17ms; animation-duration: 593ms; %7D rect:nth-child(5) %7B transform-origin: 213px 5px; transform: rotate(-97deg); animation: blast 700ms infinite ease-out; animation-delay: 122ms; animation-duration: 476ms; %7D rect:nth-child(6) %7B transform-origin: 255px 6px; transform: rotate(57deg); animation: blast 700ms infinite ease-out; animation-delay: 271ms; animation-duration: 381ms; %7D rect:nth-child(7) %7B transform-origin: 297px 5px; transform: rotate(-46deg); animation: blast 700ms infinite ease-out; animation-delay: 131ms; animation-duration: 619ms; %7D rect:nth-child(8) %7B transform-origin: 338px 6px; transform: rotate(-65deg); animation: blast 700ms infinite ease-out; animation-delay: 85ms; animation-duration: 668ms; %7D rect:nth-child(9) %7B transform-origin: 380px 6px; transform: rotate(13deg); animation: blast 700ms infinite ease-out; animation-delay: 128ms; animation-duration: 377ms; %7D rect:nth-child(10) %7B transform-origin: 423px 5px; transform: rotate(176deg); animation: blast 700ms infinite ease-out; animation-delay: 311ms; animation-duration: 508ms; %7D rect:nth-child(11) %7B transform-origin: 465px 5px; transform: rotate(108deg); animation: blast 700ms infinite ease-out; animation-delay: 108ms; animation-duration: 595ms; %7D rect:nth-child(12) %7B transform-origin: 506px 6px; transform: rotate(62deg); animation: blast 700ms infinite ease-out; animation-delay: 105ms; animation-duration: 375ms; %7D rect:nth-child(13) %7B transform-origin: 549px 5px; transform: rotate(16deg); animation: blast 700ms infinite ease-out; animation-delay: 149ms; animation-duration: 491ms; %7D rect:nth-child(odd) %7B fill: %2365BB5C; %7D rect:nth-child(even) %7B z-index: 1; fill: %2333AAFF; %7D rect:nth-child(4n) %7B animation-duration: 1400ms; fill: %23F23B14; %7D rect:nth-child(3n) %7B animation-duration: 1750ms; animation-delay: 700ms; %7D rect:nth-child(4n-7) %7B fill: %232A2F6A; %7D rect:nth-child(6n) %7B fill: %23FBBA23; %7D @keyframes blast %7B from %7B opacity: 0; %7D 20%25 %7B opacity: 1; %7D to %7B transform: translateY(90px); %7D %7D %3C/style%3E%3C/svg%3E%0A");

    @media (prefers-reduced-motion) {
        background-image: url("data:image/svg+xml,%3Csvg width='574' height='60' viewBox='0 0 574 60' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect opacity='0.8' x='27.1224' y='20.0458' width='5' height='13' transform='rotate(-139 27.1224 20.0458)' fill='%23F23B14'/%3E%3Crect opacity='0.8' x='118.478' y='7.00201' width='5' height='13' transform='rotate(-38.8114 118.478 7.00201)' fill='%23FBBA23'/%3E%3Crect opacity='0.8' x='504.616' y='25.4479' width='5' height='13' transform='rotate(-60.2734 504.616 25.4479)' fill='%23F23B14'/%3E%3Crect opacity='0.6' x='538.983' y='45.555' width='5' height='13' transform='rotate(16.7826 538.983 45.555)' fill='%232A2F6A'/%3E%3Crect opacity='0.3' x='470.322' y='2.63625' width='5' height='13' transform='rotate(11.295 470.322 2.63625)' fill='%2333AAFF'/%3E%3Crect opacity='0.3' x='190.295' y='4.58138' width='5' height='13' transform='rotate(27.5954 190.295 4.58138)' fill='%23F23B14'/%3E%3Crect opacity='0.8' x='234.303' y='16.3233' width='5' height='13' transform='rotate(-41.8233 234.303 16.3233)' fill='%2365BB5C'/%3E%3Crect opacity='0.6' x='369.702' y='40.9875' width='5' height='13' transform='rotate(-56.419 369.702 40.9875)' fill='%2333AAFF'/%3E%3Crect opacity='0.3' x='402.121' y='31.0848' width='5' height='13' transform='rotate(-17.9234 402.121 31.0848)' fill='%23F23B14'/%3E%3Crect opacity='0.6' x='200.316' y='31.9328' width='5' height='13' transform='rotate(-15.8896 200.316 31.9328)' fill='%232A2F6A'/%3E%3Crect opacity='0.6' x='69.6745' y='23.4725' width='6' height='10' transform='rotate(70.0266 69.6745 23.4725)' fill='%2365BB5C'/%3E%3Crect opacity='0.6' x='291.945' y='7.16931' width='6' height='10' transform='rotate(30.4258 291.945 7.16931)' fill='%23FBBA23'/%3E%3Crect opacity='0.3' x='33.7754' y='38.2208' width='6' height='10' transform='rotate(38.6056 33.7754 38.2208)' fill='%23FBBA23'/%3E%3Crect opacity='0.8' x='109.752' y='31.1743' width='6' height='10' transform='rotate(28.5296 109.752 31.1743)' fill='%2333AAFF'/%3E%3Crect opacity='0.3' x='278.081' y='37.8695' width='6' height='10' transform='rotate(-26.5651 278.081 37.8695)' fill='%23F23B14'/%3E%3Crect opacity='0.8' x='416.294' y='11.5573' width='6' height='10' transform='rotate(-22.8498 416.294 11.5573)' fill='%23FBBA23'/%3E%3Crect opacity='0.3' x='354.667' y='9.32341' width='6' height='10' transform='rotate(17.7506 354.667 9.32341)' fill='%232A2F6A'/%3E%3Crect opacity='0.8' x='532.404' y='16.6372' width='6' height='10' transform='rotate(-75.3432 532.404 16.6372)' fill='%23FBBA23'/%3E%3Crect opacity='0.6' x='460.463' y='39.3557' width='6' height='10' transform='rotate(45.4982 460.463 39.3557)' fill='%2365BB5C'/%3E%3C/svg%3E");
    }
}

Или предоставить код как отдельный класс .bg-confetti-static:

.bg-confetti-static {
    background-repeat: repeat-x;
    background-position: top -10px center;
    background-image: url("data:image/svg+xml,%3Csvg width='574' height='60' viewBox='0 0 574 60' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect opacity='0.8' x='27.1224' y='20.0458' width='5' height='13' transform='rotate(-139 27.1224 20.0458)' fill='%23F23B14'/%3E%3Crect opacity='0.8' x='118.478' y='7.00201' width='5' height='13' transform='rotate(-38.8114 118.478 7.00201)' fill='%23FBBA23'/%3E%3Crect opacity='0.8' x='504.616' y='25.4479' width='5' height='13' transform='rotate(-60.2734 504.616 25.4479)' fill='%23F23B14'/%3E%3Crect opacity='0.6' x='538.983' y='45.555' width='5' height='13' transform='rotate(16.7826 538.983 45.555)' fill='%232A2F6A'/%3E%3Crect opacity='0.3' x='470.322' y='2.63625' width='5' height='13' transform='rotate(11.295 470.322 2.63625)' fill='%2333AAFF'/%3E%3Crect opacity='0.3' x='190.295' y='4.58138' width='5' height='13' transform='rotate(27.5954 190.295 4.58138)' fill='%23F23B14'/%3E%3Crect opacity='0.8' x='234.303' y='16.3233' width='5' height='13' transform='rotate(-41.8233 234.303 16.3233)' fill='%2365BB5C'/%3E%3Crect opacity='0.6' x='369.702' y='40.9875' width='5' height='13' transform='rotate(-56.419 369.702 40.9875)' fill='%2333AAFF'/%3E%3Crect opacity='0.3' x='402.121' y='31.0848' width='5' height='13' transform='rotate(-17.9234 402.121 31.0848)' fill='%23F23B14'/%3E%3Crect opacity='0.6' x='200.316' y='31.9328' width='5' height='13' transform='rotate(-15.8896 200.316 31.9328)' fill='%232A2F6A'/%3E%3Crect opacity='0.6' x='69.6745' y='23.4725' width='6' height='10' transform='rotate(70.0266 69.6745 23.4725)' fill='%2365BB5C'/%3E%3Crect opacity='0.6' x='291.945' y='7.16931' width='6' height='10' transform='rotate(30.4258 291.945 7.16931)' fill='%23FBBA23'/%3E%3Crect opacity='0.3' x='33.7754' y='38.2208' width='6' height='10' transform='rotate(38.6056 33.7754 38.2208)' fill='%23FBBA23'/%3E%3Crect opacity='0.8' x='109.752' y='31.1743' width='6' height='10' transform='rotate(28.5296 109.752 31.1743)' fill='%2333AAFF'/%3E%3Crect opacity='0.3' x='278.081' y='37.8695' width='6' height='10' transform='rotate(-26.5651 278.081 37.8695)' fill='%23F23B14'/%3E%3Crect opacity='0.8' x='416.294' y='11.5573' width='6' height='10' transform='rotate(-22.8498 416.294 11.5573)' fill='%23FBBA23'/%3E%3Crect opacity='0.3' x='354.667' y='9.32341' width='6' height='10' transform='rotate(17.7506 354.667 9.32341)' fill='%232A2F6A'/%3E%3Crect opacity='0.8' x='532.404' y='16.6372' width='6' height='10' transform='rotate(-75.3432 532.404 16.6372)' fill='%23FBBA23'/%3E%3Crect opacity='0.6' x='460.463' y='39.3557' width='6' height='10' transform='rotate(45.4982 460.463 39.3557)' fill='%2365BB5C'/%3E%3C/svg%3E");
}

Вот и всё! У наших инженеров и дизайнеров есть единый портативный класс .bg-confetti-animated, который они могут добавить к любому блочному элементу. Документацию вы найдёте в Stacks, а ещё вы можете узнать, как мы стилизовали модальные окна.

Продолжить изучение CSS вы сможете на наших курсах:



Узнайте подробности здесь.

Комментарии (2)


  1. irikonova45
    10.12.2021 11:28
    +1

    Спасибо. Возьму на вооружение. Про такой (prefers-reduced-motion) медиа запрос не знала.


    1. stranger777
      10.12.2021 11:47
      +1

      Есть и другие подобные запросы, список можно посмотреть здесь. И вот здесь рассказывается о поддержке таких запросов браузерами.