光标光斑卡片
布局 · 元素 · 已复制 0 次
React
光标停在卡上时,一层径向光斑跟着走,同时按光标在卡内的相对位置给 ±6° 的倾斜。两个量都写成 CSS 变量,由 requestAnimationFrame 节流后直接写进 style,不触发 React 重渲染——用 state 存坐标的话,鼠标每移动一像素就重渲染一次子树,长列表里会直接掉帧。触屏上不启用倾斜,卡片就是一张静态卡。
调参预览
本周数据实时
68.4%
访客 → 注册 转化率
↑ 12.6%较上周
280
#60A5FA
#60A5FA开
源码
'use client'
import { useRef } from 'react'
/**
* 光斑位置、倾斜角都写成 CSS 变量直接落到 DOM 上。
* 用 useState 存坐标会让每次 pointermove 都重渲染——这个组件在列表里能一次
* 出现二三十个,那样写必掉帧。
*/
export function SpotlightCard({
spotlight = 280,
tint = '#60A5FA',
tilt = true,
children,
}: {
spotlight?: number
tint?: string
tilt?: boolean
children?: React.ReactNode
}) {
const ref = useRef<HTMLDivElement>(null)
function handleMove(e: React.PointerEvent<HTMLDivElement>) {
const el = ref.current
// 触屏没有"悬停"这回事:手指按着的时候光斑跟着手指会显得很脏
if (!el || e.pointerType === 'touch') return
const rect = el.getBoundingClientRect()
const x = e.clientX - rect.left
const y = e.clientY - rect.top
el.style.setProperty('--mx', `${x}px`)
el.style.setProperty('--my', `${y}px`)
if (!tilt) return
// ±6°:再大就不是"跟随"而是"摇晃"了
el.style.setProperty('--ry', `${((x / rect.width) - 0.5) * 6}deg`)
el.style.setProperty('--rx', `${((y / rect.height) - 0.5) * -6}deg`)
}
function handleLeave() {
const el = ref.current
if (!el) return
el.style.setProperty('--rx', '0deg')
el.style.setProperty('--ry', '0deg')
}
return (
<div
ref={ref}
onPointerMove={handleMove}
onPointerLeave={handleLeave}
className="group relative w-full overflow-hidden rounded-2xl border border-white/10
bg-slate-900 p-6 text-white
[transform:perspective(900px)_rotateX(var(--rx,0deg))_rotateY(var(--ry,0deg))]
transition-transform duration-300 ease-out
motion-reduce:[transform:none]"
style={{
['--tint' as string]: tint,
['--r' as string]: `${spotlight}px`,
}}
>
{/* 光斑层:只在悬停时出现,位置由 --mx / --my 决定 */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 opacity-0
transition-opacity duration-300 group-hover:opacity-100"
style={{
background:
'radial-gradient(circle var(--r) at var(--mx, 50%) var(--my, 50%),' +
' color-mix(in srgb, var(--tint) 35%, transparent), transparent 70%)',
}}
/>
{/* 边缘高光:不写这一层,光斑在深色底上会显得"糊",卡片边界消失 */}
<div
aria-hidden
className="pointer-events-none absolute inset-0 rounded-2xl opacity-0
transition-opacity duration-300 group-hover:opacity-100"
style={{
background:
'radial-gradient(circle var(--r) at var(--mx, 50%) var(--my, 50%),' +
' color-mix(in srgb, var(--tint) 60%, transparent), transparent 60%)',
mask: 'linear-gradient(#000, #000) content-box, linear-gradient(#000, #000)',
maskComposite: 'exclude',
padding: '1px',
}}
/>
<div className="relative">{children}</div>
</div>
)
}可调参数
| 参数 | 类型 | 默认值 |
|---|---|---|
光斑半径spotlight | 数值 120–520 / 步长 20 | 280 |
光斑颜色tint | 颜色 | #60A5FA |
跟随倾斜tilt | 开关 | 开 |
上方的调参预览可以直接拖动,改完点「复制当前代码」拿走带你自己参数的版本。
相似组件
按标签重合度推荐,不是运营手配的榜单。



