
功能简介
LockScreenPassword 是一个基于 React 实现的图案解锁组件,用户可以通过鼠标或触摸手势,在 3×3 的点阵上绘制解锁图案。组件会实时记录用户的绘制顺序,并可用于密码验证、图案设置等多种场景。
实现亮点
- 响应式交互体验:支持鼠标和触摸操作,兼容 PC 和移动端,交互流畅自然。
- SVG 绘制:采用 SVG 渲染点阵和连线,视觉效果清晰美观,易于自定义样式。
- 实时反馈:用户每经过一个点,都会高亮显示并连线,实时反馈当前图案路径。
- 易于集成:组件结构清晰,状态管理简单,便于在不同项目中快速集成和二次开发。
- 灵活扩展:可根据业务需求扩展为图案密码设置、验证、错误提示等功能。
典型使用场景
- 移动端应用解锁:模仿安卓系统的图案解锁,用于 App 登录、隐私保护等场景。
- 网页端安全验证:作为二次验证手段,提升网页端的安全性和趣味性。
- 儿童教育/游戏:用于儿童图案记忆训练、益智小游戏等互动场景。
- 自定义交互控件:可作为创意输入控件,应用于签到、抽奖、互动问答等场景。
全部源码
import React, { useRef, useState } from "react";
const GRID_SIZE = 3;
const DOTS = Array.from({ length: GRID_SIZE * GRID_SIZE }, (_, i) => i);
function getDotPosition(index, size, padding) {
const row = Math.floor(index / GRID_SIZE);
const col = index % GRID_SIZE;
const gap = (size - 2 * padding) / (GRID_SIZE - 1);
return {
x: padding + col * gap,
y: padding + row * gap,
};
}
export default function LockScreenPassword() {
const [selected, setSelected] = useState([]);
const [isDrawing, setIsDrawing] = useState(false);
const svgRef = useRef(null);
const size = 300;
const padding = 30;
const handlePointerDown = () => {
setSelected([]);
setIsDrawing(true);
};
const handlePointerUp = () => {
setIsDrawing(false);
// 这里可以处理密码验证或设置逻辑
// alert(`图案顺序: ${selected.join("-")}`);
};
const handlePointerMove = (e) => {
if (!isDrawing) return;
const rect = svgRef.current.getBoundingClientRect();
const x = (e.touches ? e.touches[0].clientX : e.clientX) - rect.left;
const y = (e.touches ? e.touches[0].clientY : e.clientY) - rect.top;
DOTS.forEach((dot, idx) => {
const { x: dx, y: dy } = getDotPosition(idx, size, padding);
const dist = Math.sqrt((dx - x) ** 2 + (dy - y) ** 2);
if (dist < 25 && !selected.includes(idx)) {
setSelected((prev) => [...prev, idx]);
}
});
};
return (
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: "center",
marginTop: 40,
}}
>
<svg
ref={svgRef}
width={size}
height={size}
onPointerDown={handlePointerDown}
onPointerUp={handlePointerUp}
onPointerMove={handlePointerMove}
onTouchStart={handlePointerDown}
onTouchEnd={handlePointerUp}
onTouchMove={handlePointerMove}
>
{/* 连线 */}
{selected.map((idx, i) => {
if (i === 0) return null;
const from = getDotPosition(selected[i - 1], size, padding);
const to = getDotPosition(idx, size, padding);
return (
<line
key={"line-" + i}
x1={from.x}
y1={from.y}
x2={to.x}
y2={to.y}
stroke="#1976d2"
strokeWidth={6}
strokeLinecap="round"
/>
);
})}
{/* 点 */}
{DOTS.map((_, idx) => {
const { x, y } = getDotPosition(idx, size, padding);
const isActive = selected.includes(idx);
return (
<circle
key={idx}
cx={x}
cy={y}
r={20}
fill={isActive ? "#1976d2" : "#fff"}
stroke="#1976d2"
strokeWidth={3}
/>
);
})}
</svg>
<div style={{ marginTop: 20 }}>
当前图案顺序: {selected.join("-") || "无"}
</div>
</div>
);
}
原文链接:https://code.ifrontend.net/archives/680,转载请注明出处。
评论0