react实现滑块安全验证功能
·
效果:点击后提示验证成功

代码:
下载依赖:react-draggable、lucide-react;
index.tsx
import { useState } from 'react'; import SliderCaptcha from '../components/SliderCaptcha'; const Index = () => { const [showCaptcha, setShowCaptcha] = useState(false); const handleSuccess = () => { setTimeout(() => { setShowCaptcha(false); alert('验证成功!'); }, 500); }; return ( <div className="min-h-screen flex items-center justify-center bg-gray-100"> <div className="text-center"> <h1 className="text-4xl font-bold mb-4">滑块验证示例</h1> <button onClick={() => setShowCaptcha(true)} className="px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors" > 点击验证 </button> {showCaptcha && ( <SliderCaptcha onClose={() => setShowCaptcha(false)} onSuccess={handleSuccess} /> )} </div> </div> ); }; export default Index;
SliderCaptcha.tsx
import { ArrowLeftRight, X, Check } from 'lucide-react';
import { useRef, useEffect, memo, useState } from 'react';
import { useImageCrop } from '../utils/imageUtils';
import Draggable from 'react-draggable';
const PuzzlePiece = memo(({ croppedPuzzle, position, isDragging }) => (
<div
className={`absolute top-[60px] w-[40px] h-[40px] cursor-pointer overflow-hidden transition-transform duration-0
${isDragging ? 'shadow-lg' : ''}`}
style={{
border: '1px solid rgba(255,255,255,0.3)',
boxShadow: isDragging ? '0 0 10px rgba(0,0,0,0.3)' : 'none',
left: 0,
transform: `translateX(${position.x}px)`
}}
>
{croppedPuzzle && (
<img
src={croppedPuzzle}
alt="拼图块"
className="w-full h-full"
draggable={false}
/>
)}
</div>
));
const SliderHandle = memo(({ position, isSuccess, isDragging }) => (
<div
className={`absolute top-1 h-10 w-10 rounded shadow cursor-pointer
flex items-center justify-center transition-colors duration-300
${isDragging ? 'shadow-lg' : ''}
${isSuccess ? 'bg-green-500 text-white' : 'bg-white'}`}
style={{
willChange: 'transform'
}}
>
{isSuccess ? (
<Check className="h-5 w-5" />
) : (
<ArrowLeftRight className="h-5 w-5 text-gray-600" />
)}
</div>
));
const SliderCaptcha = ({ onClose, onSuccess }) => {
const [position, setPosition] = useState({ x: 0, y: 0 });
const [isDragging, setIsDragging] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const containerRef = useRef(null);
const sliderRef = useRef(null);
const puzzleWidth = 40;
const puzzleHeight = 40;
const targetPosition = 200;
const tolerance = 10;
const backgroundImage = "https://nocode.meituan.com/photo/search?keyword=canyon&width=320&height=160&source=meituan";
const croppedPuzzle = useImageCrop(backgroundImage, targetPosition, 60, puzzleWidth, puzzleHeight);
const handleDrag = (e, data) => {
const newX = Math.max(0, data.x);
console.log('newX', newX, data.x)
setPosition({ x: newX, y: 0 });
setIsDragging(true);
};
const handleDragStop = (e, data) => {
setIsDragging(false);
const currentPosition = Math.max(0, data.x);
if (Math.abs(currentPosition - targetPosition) <= tolerance) {
setIsSuccess(true);
setPosition({ x: targetPosition, y: 0 });
onSuccess?.();
} else {
setPosition({ x: 0, y: 0 });
}
};
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-white rounded-lg p-4 w-[320px]" ref={containerRef}>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-medium">安全验证</h3>
<button onClick={onClose} className="p-1 hover:bg-gray-100 rounded">
<X className="h-5 w-5" />
</button>
</div>
<div className="relative mb-4 h-[160px] bg-gray-100 overflow-hidden">
<img
src={backgroundImage}
className="w-full h-full object-cover"
alt="验证背景"
draggable={false}
/>
<div
className="absolute top-[60px] left-[200px] w-[40px] h-[40px] bg-black/40 backdrop-blur-[1px]"
style={{
boxShadow: 'inset 0 0 10px rgba(0,0,0,0.3)',
border: '1px solid rgba(255,255,255,0.2)'
}}
/>
<PuzzlePiece
croppedPuzzle={croppedPuzzle}
position={position}
isDragging={isDragging}
/>
</div>
<div className="relative h-12 bg-gray-100 rounded" ref={sliderRef}>
<Draggable
axis="x"
position={position}
onDrag={handleDrag}
onStop={handleDragStop}
bounds={{left: 0, right: 280}}
nodeRef={sliderRef}
>
<div className="absolute top-0 left-0 w-10 h-full cursor-pointer">
<SliderHandle
position={position}
isSuccess={isSuccess}
isDragging={isDragging}
/>
</div>
</Draggable>
<div className="h-full flex items-center justify-center text-gray-400">
{isSuccess ? '验证通过' : '拖动下方滑块完成拼图'}
</div>
</div>
</div>
</div>
);
};
export default SliderCaptcha;
useImageCrop.tsx
import { useEffect, useState, useCallback } from 'react';
export const useImageCrop = (imageUrl, x, y, width, height) => {
const [croppedImage, setCroppedImage] = useState(null);
const cropImage = useCallback(() => {
const img = new Image();
img.crossOrigin = "anonymous";
img.onload = () => {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { alpha: false });
canvas.width = width;
canvas.height = height;
ctx.imageSmoothingEnabled = false;
ctx.drawImage(img, x, y, width, height, 0, 0, width, height);
const dataUrl = canvas.toDataURL('image/png', 0.8);
setCroppedImage(dataUrl);
};
img.src = imageUrl;
}, [imageUrl, x, y, width, height]);
useEffect(() => {
cropImage();
}, [cropImage]);
return croppedImage;
};
更多推荐


所有评论(0)