聚光卡片
一团柔光跟着指针在卡片内移动,并点亮指针所在位置的边框。
方案工作室版RM 49 / 月项目与导出数量不限
用 React 制作一个跟随指针的聚光卡片。在 pointermove 时,把指针相对卡片的位置(px)写入 --x 和 --y。添加一个 ::before 层,使用以 var(--x) var(--y) 为圆心、直径 320px 的 radial-gradient,从 18% 不透明度的强调色过渡到透明,悬停时在 200ms 内从 opacity 0 淡入到 1。在同一位置点亮边框:使用 1px 的 padding-box/border-box 背景技巧(background: linear-gradient(卡片底色, 卡片底色) padding-box, radial-gradient(160px circle at var(--x) var(--y), 强调色, 边框色) border-box,边框设为透明)。卡片显示标签“方案”、标题“工作室版”、价格“RM 49 / 月”和说明“项目与导出数量不限”。在 prefers-reduced-motion 下光晕固定在中央,不跟随。
源代码
'use client'
import s from './demo.module.css'
export default function SpotlightCard({ labels }: { labels: Record<string, string> }) {
const onMove = (e: React.PointerEvent<HTMLDivElement>) => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
const r = e.currentTarget.getBoundingClientRect()
e.currentTarget.style.setProperty('--x', `${e.clientX - r.left}px`)
e.currentTarget.style.setProperty('--y', `${e.clientY - r.top}px`)
}
return (
<div className={s.card} onPointerMove={onMove}>
<span className={s.kicker}>{labels.kicker}</span>
<strong className={s.name}>{labels.name}</strong>
<span className={s.price}>{labels.price}</span>
<span className={s.note}>{labels.note}</span>
</div>
)
}
.card {
--x: 50%;
--y: 50%;
position: relative;
display: grid;
gap: 6px;
width: min(260px, 100%);
padding: 22px;
overflow: hidden;
border: 1px solid transparent;
border-radius: 16px;
background:
linear-gradient(var(--stage-surface), var(--stage-surface)) padding-box,
radial-gradient(160px circle at var(--x) var(--y), var(--stage-accent), var(--stage-rule)) border-box;
}
.card::before {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(320px circle at var(--x) var(--y), color-mix(in oklch, var(--stage-accent) 18%, transparent), transparent 70%);
opacity: 0;
pointer-events: none;
transition: opacity 200ms var(--ease-out);
}
.card:hover::before { opacity: 1; }
.card > * { position: relative; }
.kicker { font: 500 var(--text-xs) / 1 var(--font-mono); letter-spacing: 0.06em; text-transform: uppercase; color: var(--stage-muted); }
.name { font: 600 var(--text-xl) / 1.2 var(--font-display); color: var(--stage-ink); }
.price { font: 600 var(--text-base) / 1.2 var(--font-body); color: var(--stage-accent); }
.note { font-size: var(--text-sm); color: var(--stage-muted); }