← каталог
Ripple Rings
Пульс от центра: кольца расходятся и тают, фазы распределены отрицательными задержками одной анимации. Чистый CSS, подложка отключается для сокета.
cssautobasic0reduced-motion ✓
🎛 Настройки — крути и копируй
Пропсы
Разобрано из кода записи: обязательные — без «?», у остальных показано значение по умолчанию.
| Проп | Тип | По умолчанию | Что это |
|---|---|---|---|
| backdrop? | string | false | BACKDROP | Подложка под кольцами: CSS-значение `background` или `false`. `false` нужен, когда запись стоит фоном СЕКЦИИ и подложку задаёт она. |
| count? | number | 5 | Сколько колец в пульсе. |
| period? | number | 6 | Секунд на один пробег кольца от центра до края. |
| hue? | number | 300 | Тон колец, градусы oklch. |
| children? | ReactNode | — | Содержимое поверх (в витрине — подпись, в секции обычно пусто). |
Установка
npx shadcn@latest add https://motiva.pages.dev/r/ripple-rings.jsonЧто нужно в проекте до установки
components.jsonв корне — с алиасами в форме"components": "@/components". Нет файла — CLI не поймёт, куда класть.- Алиас
@объявлен дважды:"paths": {"@/*": ["./src/*"]}в корневомtsconfig.json(в Vite он часто содержит толькоreferences— тогда CLI создаёт папку@) иresolve.aliasв конфиге сборщика. - Токены подключены один раз на приложение:
import "@/styles/motiva-tokens.css"— они приезжают вместе с записью. Без них секция потеряет цвета, ритм и тему. - Своя типографика не должна спорить: если у вас на обёртке
text-align: center, он протечёт внутрь блоков. - Проект без TypeScript? Всё равно нужен минимальный
tsconfig.jsonсpathsи"tsx": trueвcomponents.json: CLI грузит tsconfig безусловно, а с"tsx": falseидёт заjsconfig.jsonи падает так же. Приехавший рядом.tsxможно не трогать — вам нужен.vanilla.js.
Экспорт
Код
import type { CSSProperties, ReactNode } from "react";
/** Подложка по умолчанию — как в витрине. */
const BACKDROP = "radial-gradient(ellipse at 50% 50%, oklch(0.17 0.02 270), oklch(0.13 0.015 270))";
export interface RippleRingsProps {
/**
* Подложка под кольцами: CSS-значение `background` или `false`.
* `false` нужен, когда запись стоит фоном СЕКЦИИ и подложку задаёт она.
*/
backdrop?: string | false;
/** Сколько колец в пульсе. */
count?: number;
/** Секунд на один пробег кольца от центра до края. */
period?: number;
/** Тон колец, градусы oklch. */
hue?: number;
/** Содержимое поверх (в витрине — подпись, в секции обычно пусто). */
children?: ReactNode;
}
/**
* Пульс от центра: кольца расходятся и тают, распределённые по фазе отрицательными
* задержками одной анимации. Reduced-motion / data-shot — кольца стоят концентрической
* рябью (статичную раскладку держат базовые inline scale/opacity, анимация с явными
* from/to их перекрывает, пока жива). Чистый CSS, 0 КБ.
*/
export function RippleRings({ backdrop = BACKDROP, count = 5, period = 6, hue = 300, children }: RippleRingsProps = {}) {
const n = Math.max(2, Math.round(count));
return (
<div
className="rr"
style={{
position: "absolute",
inset: 0,
overflow: "hidden",
...(backdrop === false ? null : { background: backdrop }),
}}
>
<style>{`
/* размерный контейнер: кольцо меряется в РОДИТЕЛЕ (cqmin), не во вьюпорте */
.rr { container-type: size; }
.rr-ring {
position: absolute; left: 50%; top: 50%;
width: 88cqmin; height: 88cqmin;
margin: -44cqmin;
border-radius: 999px;
border: 1px solid var(--rr-line);
animation: rr-pulse var(--rr-period) linear infinite;
animation-delay: var(--rr-delay);
}
@keyframes rr-pulse {
from { transform: scale(0.12); opacity: 0; }
14% { opacity: var(--rr-alpha); }
to { transform: scale(1); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) { .rr-ring { animation: none; } }
`}</style>
{Array.from({ length: n }, (_, i) => (
<span
key={i}
className="rr-ring"
style={
{
// база для статики: концентрическая рябь с угасанием к краю
transform: `scale(${(0.2 + (0.8 * (i + 1)) / n).toFixed(3)})`,
opacity: (0.5 * (1 - i / n) + 0.08).toFixed(3),
"--rr-period": `${period}s`,
"--rr-delay": `${(-period * i) / n}s`,
"--rr-alpha": "0.5",
"--rr-line": `oklch(0.7 0.12 ${hue} / 0.6)`,
} as CSSProperties
}
/>
))}
{children && (
<div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center" }}>{children}</div>
)}
</div>
);
}
Похожие эффекты
лицензия: MITавтор: Motivaисточник: оригинал (Motiva); идея-референс — Magic UI Ripple (MIT)v1.0.0