复古网格地平线
发光的网格地面在地平线下源源不断地朝你滚来,给首屏一种向前行进的感觉。
下一站
用纯 CSS 制作复古网格地平线背景。在 overflow hidden 的容器中,于下半部分放一个比容器更宽的地面元素(宽 200%,left -50%),用两个 repeating-linear-gradient 画出 40px 间距、50% 不透明度主色的网格线。用 transform: perspective(300px) rotateX(60deg) 让它倾斜,transform-origin 设在顶部。用 1.2s linear infinite 把 background-position-y 从 0 动到 40px,让线条无缝地朝观者滚动。用 mask-image linear-gradient 淡化远端,让网格融入地平线,并在地平线处加一条柔和的主色光线。标题放在地平线之上。离开屏幕时暂停,在 prefers-reduced-motion 下让网格保持静止。标题为“下一站”。
源代码
'use client'
import { useEffect, useRef } from 'react'
import s from './demo.module.css'
export default function RetroGrid({ labels }: { labels: Record<string, string> }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const el = ref.current
if (!el) return
const io = new IntersectionObserver(([e]) => el.toggleAttribute('data-paused', !e.isIntersecting))
io.observe(el)
return () => io.disconnect()
}, [])
return (
<div ref={ref} className={s.scene}>
<p className={s.title}>{labels.title}</p>
<div className={s.floor} aria-hidden="true" />
</div>
)
}
.scene {
position: absolute;
inset: 0;
overflow: hidden;
background: radial-gradient(ellipse 70% 40% at 50% 52%, color-mix(in oklch, var(--stage-accent) 22%, transparent), transparent), var(--stage-bg);
}
.scene::after {
content: '';
position: absolute;
left: 0;
right: 0;
top: 52%;
height: 1px;
background: var(--stage-accent);
box-shadow: 0 0 16px 2px var(--stage-accent);
}
.title {
position: absolute;
left: 0;
right: 0;
top: 20%;
margin: 0;
text-align: center;
font: 700 var(--text-2xl) / 1 var(--font-display);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--stage-ink);
}
.floor {
--line: color-mix(in oklch, var(--stage-accent) 50%, transparent);
position: absolute;
left: -50%;
width: 200%;
top: 52%;
height: 60%;
background:
repeating-linear-gradient(90deg, var(--line) 0 1px, transparent 1px 40px),
repeating-linear-gradient(0deg, var(--line) 0 1px, transparent 1px 40px);
transform: perspective(300px) rotateX(60deg);
transform-origin: top;
-webkit-mask-image: linear-gradient(transparent, black 40%);
mask-image: linear-gradient(transparent, black 40%);
animation: roll 1.2s linear infinite;
}
.scene[data-paused] .floor { animation-play-state: paused; }
@keyframes roll {
from { background-position: 0 0; }
to { background-position: 0 40px; }
}
@media (prefers-reduced-motion: reduce) {
.floor { animation: none; }
}