← каталог
Grid Beams
Фон-сетка с бегущими лучами: кометы с градиентными хвостами летят по горизонталям и вертикалям решётки.
cssautobasic0reduced-motion ✓
Пропсы
Разобрано из кода записи: обязательные — без «?», у остальных показано значение по умолчанию.
| Проп | Тип | По умолчанию | Что это |
|---|---|---|---|
| backdrop? | string | false | BACKDROP | Подложка (сама сетка + база): CSS-значение `background` или `false`. `false` нужен, когда запись стоит фоном СЕКЦИИ и подложку задаёт она. |
| children? | ReactNode | — | Содержимое поверх сетки. В витрине — подпись, в секции обычно пусто. |
Установка
npx shadcn@latest add https://motiva.pages.dev/r/grid-beams.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 CELL = 48; // шаг сетки, px — лучи привязаны к линиям кратно ему
/** Подложка по умолчанию — как в витрине. */
const BACKDROP = `repeating-linear-gradient(180deg, transparent 0 ${CELL - 1}px, oklch(0.3 0.02 270 / 0.5) ${CELL - 1}px ${CELL}px), repeating-linear-gradient(90deg, transparent 0 ${CELL - 1}px, oklch(0.3 0.02 270 / 0.5) ${CELL - 1}px ${CELL}px), oklch(0.15 0.015 270)`;
export interface GridBeamsProps {
/**
* Подложка (сама сетка + база): CSS-значение `background` или `false`.
* `false` нужен, когда запись стоит фоном СЕКЦИИ и подложку задаёт она.
*/
backdrop?: string | false;
/** Содержимое поверх сетки. В витрине — подпись, в секции обычно пусто. */
children?: ReactNode;
}
const BEAMS: { dir: "h" | "v"; cellIndex: number; delay: number; dur: number; hue: number }[] = [
{ dir: "h", cellIndex: 2, delay: 0, dur: 3.2, hue: 300 },
{ dir: "h", cellIndex: 5, delay: 1.4, dur: 4.1, hue: 210 },
{ dir: "v", cellIndex: 5, delay: 0.7, dur: 3.6, hue: 150 },
{ dir: "v", cellIndex: 12, delay: 2.2, dur: 3, hue: 20 },
];
/**
* Фон-сетка с бегущими лучами: линии — repeating-gradient, «кометы» с градиентным хвостом
* летят по горизонталям и вертикалям. Чистый CSS. Reduced-motion/data-shot — только сетка.
*/
export function GridBeams({ backdrop = BACKDROP, children }: GridBeamsProps = {}) {
return (
<div
className="gb"
style={{
position: "absolute",
inset: 0,
overflow: "hidden",
// ВАЖНО: горизонтальные линии — 180deg (ось сверху вниз). С 0deg ось идёт снизу вверх,
// и повтор отсчитывается от НИЖНЕЙ кромки — тогда линии не совпадают с `top` лучей.
...(backdrop === false ? null : { background: backdrop }),
}}
>
<style>{`
/* размерный контейнер: путь луча меряется в РОДИТЕЛЕ (cqw/cqh), а не во вьюпорте.
С 100vw в секции уже своей ширины луч улетал далеко за кромку и возвращался
с паузой — сетка казалась то живой, то мёртвой */
.gb { container-type: size; }
.gb-beam { position: absolute; border-radius: 999px; animation: var(--gb-anim) linear infinite; }
.gb-beam[data-dir="h"] { width: 130px; height: 2px; left: -140px; --gb-anim: gb-h var(--gb-dur); }
.gb-beam[data-dir="v"] { width: 2px; height: 130px; top: -140px; --gb-anim: gb-v var(--gb-dur); }
@keyframes gb-h { to { transform: translateX(calc(100cqw + 280px)); } }
@keyframes gb-v { to { transform: translateY(calc(100cqh + 280px)); } }
@media (prefers-reduced-motion: reduce) { .gb-beam { display: none; } }
`}</style>
{BEAMS.map((b, i) => (
<span
key={i}
className="gb-beam"
data-dir={b.dir}
style={
{
// центр луча (2px) точно на линии сетки (1px на границе клетки)
[b.dir === "h" ? "top" : "left"]: b.cellIndex * CELL - 1.5,
"--gb-dur": `${b.dur}s`,
animationDelay: `${b.delay}s`,
background:
b.dir === "h"
? `linear-gradient(90deg, transparent, oklch(0.75 0.16 ${b.hue}))`
: `linear-gradient(180deg, transparent, oklch(0.75 0.16 ${b.hue}))`,
boxShadow: `0 0 12px oklch(0.75 0.16 ${b.hue} / 0.7)`,
} as CSSProperties
}
/>
))}
{/* подпись витрины приезжает из preview.tsx: зашитый текст поверх фона печатался бы
и в чужой секции — ровно та причина, по которой запись нельзя было переиспользовать */}
{children && (
<div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center" }}>{children}</div>
)}
</div>
);
}
Похожие эффекты
лицензия: MITавтор: Motivaисточник: оригинал (Motiva); идея-референс — Aceternity grid beams (MIT)v1.0.1