Border Glow | 指针跟随边框反馈
Border Glow 用指针位置驱动边框高光,把用户注意力、可点击区域和确认状态合在一个轻量面板里。
效果展示
设计说明
场景
边框高光很容易变成装饰,但真正有价值的用法是提示“这里是当前可操作区域”。用户在卡片、设置项、发布门禁或确认面板上移动时,高光跟随边界,能帮助他理解焦点所在。
Border Glow 适合做成一个极小案例:指针靠近时边框发亮,点击后状态被确认。它不替代按钮、卡片或面板,而是补足这些组件库常规组件没有覆盖的注意力反馈。
交互设计
这个案例只保留三个动作:
- Approach
指针进入面板边界,高光在边框包装层出现。
- Track
指针移动时,高光中心跟随局部坐标,但内容层不重新布局。
- Commit
点击面板切换确认状态,用户能看到 Idle 与 Armed 的差异。
实现原理
最小实现由外层 p-px 包装层和内层语义化 button 组成。外层负责边框高光,内层负责内容和可访问交互。onPointerMove 计算指针相对外层的位置,再把坐标写入内联 radial-gradient()。
这种方式不需要额外动画库,也不需要 Tailwind 插件。高光只是背景层,按钮仍然使用浏览器原生 focus、click 和 aria-pressed。
SEO 与内容定位
这个案例适合被描述为 “pointer glow”、“micro interaction”、“attention feedback”。它与常规组件库的区别是:组件库通常给出静态边框、hover 色或 focus ring,而这个案例讲的是边界反馈如何跟随用户注意力。
测试说明
测试时检查五点:高光不改变面板尺寸,离开指针后边框回到安静状态,点击切换状态,键盘 focus ring 可见,移动端没有依赖 hover 才能理解的必要信息。
可复制源码
源码从案例目录的 examples 文件读取,展示内容和参与 typecheck 的代码保持同一事实来源。
"use client"; import * as React from "react"; type GlowState = { x: number; y: number; active: boolean;}; const hoverGlowColor = "rgb(var(--lab-hover-purple-rgb) / 0.9)";const hoverGlowFadeColor = "rgb(var(--lab-hover-purple-rgb) / 0.22)"; function pointerState(event: React.PointerEvent<HTMLElement>): GlowState { const rect = event.currentTarget.getBoundingClientRect(); return { active: true, x: event.clientX - rect.left, y: event.clientY - rect.top, };} export function BorderGlowPanel() { const [glow, setGlow] = React.useState<GlowState>({ x: 220, y: 120, active: false, }); const [armed, setArmed] = React.useState(false); const glowStyle: React.CSSProperties = { background: glow.active ? `radial-gradient(160px circle at ${glow.x}px ${glow.y}px, ${hoverGlowColor}, ${hoverGlowFadeColor} 44%, transparent 72%)` : "linear-gradient(135deg, hsl(var(--border)), transparent, hsl(var(--border)))", }; return ( <div className="rounded-card border border-border bg-background p-2 shadow-soft"> <div className="flex min-h-[340px] items-center justify-center rounded-card border border-border/70 bg-card/60 p-5"> <div className="relative w-full max-w-[420px] rounded-card p-px" onPointerEnter={(event) => setGlow(pointerState(event))} onPointerLeave={() => setGlow((current) => ({ ...current, active: false })) } onPointerMove={(event) => setGlow(pointerState(event))} > <div aria-hidden="true" className="pointer-events-none absolute inset-0 rounded-card opacity-80 transition-opacity duration-200" data-border-glow="" style={glowStyle} /> <button aria-pressed={armed} className="relative w-full rounded-card border border-border bg-card p-5 text-left outline-none transition-colors hover:bg-muted/40 focus-visible:ring-2 focus-visible:ring-ring" onClick={() => setArmed((value) => !value)} type="button" > <span className="flex items-start justify-between gap-4"> <span> <span className="text-sm font-semibold text-foreground"> Release gate </span> <span className="mt-2 block text-sm leading-6 text-muted-foreground"> The border glow follows attention before the user commits. </span> </span> <span className="rounded-full border border-border bg-background px-2.5 py-1 text-xs font-medium text-muted-foreground"> {armed ? "Armed" : "Idle"} </span> </span> <span className="mt-6 grid gap-2"> {["Diff reviewed", "Preview verified", "Fallback defined"].map( (item) => ( <span className="flex items-center gap-2 text-sm text-muted-foreground" key={item} > <span className="size-1.5 rounded-full bg-primary" /> {item} </span> ), )} </span> </button> </div> </div> </div> );} export default BorderGlowPanel;Agent Prompt
Agent Prompt
Build a Border Glow interaction inspired by https://reactbits.dev/components/border-glow, but implement the simplified Lab version with only React, pointer events, inline radial gradients, and existing Tailwind utility classes. Do not install animation libraries, do not add Tailwind plugins, and do not change Tailwind config. Deliver one self-contained React component, a small preview, and localized Markdown content. The interaction should show a panel whose border glow follows the pointer, while clicking the panel toggles a visible committed state. Use a semantic button, `aria-pressed`, theme tokens, and a fallback border when the pointer is not active. Acceptance: the glow stays on the border wrapper, pointer leave returns to a quiet border, clicking toggles state without layout shift, keyboard focus remains visible, and the example source is the same component used by the demo.