JavaScript实现前端登录拼图验证的完整流程与代码解析

作者:白露时间:2026-10-26 09:15:59浏览:466

本文详细解析如何使用JavaScript实现前端登录拼图验证功能。内容涵盖底图与验证区域的CSS布局、滑块与对号的HTML/CSS实现,以及通过监听鼠标按下、移动和抬起事件计算位移值。文章还探讨了pageX与offsetX的差异,并提供了验证通过后的回弹动画处理逻辑及完整示例代码。

参考相关技术文章,本文旨在复现并实现一个用于登录场景的拼图验证码功能。具体效果如示例图所示。

底图实现

底图实现

首先构建一个容器元素,用于承载背景图片。

底图实现

// html
        <div class="check">
        </div>
// css
    .check {
            width: 400px;
            height: 300px;
            background-image: url(./img/bg.avif);
            background-size: 100% 100%;
            background-repeat: no-repeat;
            position: relative;
            filter: brightness(80%);
    }

验证区域在底图中的显示通过CSS伪类完成。

底图实现

    .check::before {
            position: absolute;
            content: '';
            width: 50px;
            height: 50px;
            background: rgba(0, 0, 0, 0.5);
            top: 100px;
            left: 280px;
    }

被验证区域块

被验证区域的实现类似于图像裁剪,即从底图中截取特定部分并调整其位置,使其在视觉上成为整体的一部分。核心思路是利用background-position属性设置负坐标值,从而获取图片的指定局部。

        <div class="check">
                <!-- 验证区块通过伪类实现 -->
                <!-- 被验证区块 -->
                <div class="check-box" id="check-box"></div>
        </div>
        .check-box {
                width: 50px;
                height: 50px;
                position: absolute;
                top: 100px;
                left: 0;
                background-image: inherit;
                background-repeat: inherit;
                background-size: 400px 300px;
                background-position: -280px -100px;
                border: 1px solid #fff;
        }

滑块区域

滑块的HTML结构与CSS样式实现较为简单。其中,滑块内的对号图标通过设置属性中的border-width实现,这种设计方式颇具巧思。

    <!-- 拖动条 -->
    <div class="drag">
            <!-- 滑块 -->
            <div class="drag-box" id="drag-box">
                    <!-- 对号 -->
                    <div id="drag-mark" class="drag-mark"></div>
            </div>
            <!-- 文字提示 -->
            <div class="drag-tips">
                    <span>按住左边按钮向右拖动完成上方图像验证</span>
            </div>
    </div>
    .drag {
            width: 400px;
            height: 50px;
            background-color: #e3e3e3;
            margin-top: 10px;
            position: relative;
    }
    .drag-box {
            position: absolute;
            top: 0;
            left: 0;
            width: 50px;
            height: 50px;
            background-color: aquamarine;
            z-index: 10;
            display: flex;
            justify-content: center;
            align-items: center;
    }
    .drag-tips {
            position: absolute;
            top: 0;
            left: 0;
            display: flex;
            justify-content: end;
            align-items: center;
            width: 95%;
            height: 100%;
            font-size: 12px;
            color: #8a8a8a;
            user-select: none;
    }
    .drag-mark {
            width: 8px;
            height: 16px;
            border-color: #009933;
            border-style: solid;
            border-width: 0 3px 5px 0;
            transform: rotate(45deg);
            opacity: 0;
            transition: 0.3s;
    }

动态滑动

滑动交互过程主要涉及鼠标按下、鼠标移动以及鼠标弹起三个阶段。

底图实现

鼠标按下

在鼠标按下事件中,保存鼠标的初始水平坐标pageX,以便后续计算移动距离。

同时监听鼠标的移动事件。

    // 鼠标按下
    const mouseDown = (event) => {
            // 获取鼠标坐标
            preX = event.pageX
            // 监听鼠标移动
            document.addEventListener('mousemove', mouseMove)
    }
···

鼠标移动

  • 在鼠标移动过程中,获取当前鼠标的pageX值,并与初始值相减,从而得到当前的移动偏移量。
  • 在鼠标弹起时,移除对鼠标移动事件的监听。
  • 根据计算出的移动值,同步更新滑块和被验证区域块的位置。

此处存在一个技术疑问:使用pageX或clientX均可实现预期效果,但使用offsetX时会出现画面闪烁。尽管网上有建议通过pointer-events: none解决该问题,但在本实现中并未生效。因此,关于鼠标移动值的精确计算方式仍存疑,待后续深入研究后更新。

    // 鼠标移动
    const mouseMove = (event) => {
            const { pageX } = event
            // 移动距离
            const moveX = pageX - preX
            // 移动不能超出区域
            if (moveX < 0 || moveX > 350) {
                    return
            }
            check.style.transform = `translateX(${moveX}px)`
            drag.style.transform = `translateX(${moveX}px)`
    }

鼠标抬起

  • 在鼠标抬起事件中,获取当前pageX值并与初始值相减,得到最终的移动偏移量。
  • 判断该移动偏移量是否落在预设的有效区间内。
  • 若移动值在有效区间内,则调用验证通过的回调函数。
  • 若移动值不在有效区间内,需为滑块和被验证区域块设计回弹动画效果。
        // 鼠标抬起
        const mouseUp = (event) => {
                // 移出移动事件
                document.removeEventListener('mousemove', mouseMove)
                const { pageX } = event
                const moveX = pageX - preX
                if (moveX < 278 || moveX > 285) {
                .......
                } else {
                        success()
                }
        }

有效验证

所谓有效验证,即触发验证通过的回调逻辑。

    // 校验回调函数
    const success = () => {
            console.log('通过校验!')
    }

动画处理

回弹动画的处理分为CSS和JavaScript两部分。CSS方面定义动画效果,JavaScript方面则为滑块和被验证区域块赋值动画状态,并监听动画结束事件。当动画结束时,将滑块和被验证区域块重置为初始位置(0),并清除动画相关样式。

    /* 回弹动画 */
    @keyframes move {
            to {
                    transform: translateX(0);
            }
    }
    if (moveX < 278 || moveX > 285) {
            // 没有在校验区域 增加回弹动画
            drag.style.animation = 'move 0.5s ease-in-out'
            check.style.animation = 'move 0.5s ease-in-out'
            // 动画结束回调
            const animationEnd = () => {
                    check.style.transform = `translateX(${0}px)`
                    drag.style.transform = `translateX(${0}px)`
                    mark.style.opacity = 0
                    // 清除动画
                    drag.style.animation = ''
                    check.style.animation = ''
                    // 取消监听动画结束回调
                    document.removeEventListener('animationEnd', animationEnd)
            }
            document.addEventListener('animationend', animationEnd)
    }

完整代码

示例中使用的图片原本位于远程服务器,现已下载至本地。点击上方的底图链接即可获取原图。

<!--
 * @Author: Kongjingjing
 * @Date: 2023-01-10 15:37:03
 * @Description: 
-->
<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta http-equiv="X-UA-Compatible" content="IE=edge" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Document</title>
		<style>
			.check {
				width: 400px;
				height: 300px;
				background-image: url(./img/bg.avif);
				background-size: 100% 100%;
				background-repeat: no-repeat;
				position: relative;
				filter: brightness(80%);
			}
			.check::before {
				position: absolute;
				content: '';
				width: 50px;
				height: 50px;
				background: rgba(0, 0, 0, 0.5);
				top: 100px;
				left: 280px;
			}
			.check-box {
				width: 50px;
				height: 50px;
				position: absolute;
				top: 100px;
				left: 0;
				background-image: inherit;
				background-repeat: inherit;
				background-size: 400px 300px;
				background-position: -280px -100px;
				border: 1px solid #fff;
			}
			.drag {
				width: 400px;
				height: 50px;
				background-color: #e3e3e3;
				margin-top: 10px;
				position: relative;
			}
			.drag-box {
				position: absolute;
				top: 0;
				left: 0;
				width: 50px;
				height: 50px;
				background-color: aquamarine;
				z-index: 10;
				display: flex;
				justify-content: center;
				align-items: center;
			}
			.drag-tips {
				position: absolute;
				top: 0;
				left: 0;
				display: flex;
				justify-content: end;
				align-items: center;
				width: 95%;
				height: 100%;
				font-size: 12px;
				color: #8a8a8a;
				user-select: none;
			}
			/* 回弹动画 */
			@keyframes move {
				to {
					transform: translateX(0);
				}
			}
			.drag-mark {
				width: 8px;
				height: 16px;
				border-color: #009933;
				border-style: solid;
				border-width: 0 3px 5px 0;
				transform: rotate(45deg);
				opacity: 0;
				transition: 0.3s;
			}
		</style>
	</head>
	<body>
		<div class="check">
			<!-- 验证区块通过伪类实现 -->
			<!-- 被验证区块 -->
			<div class="check-box" id="check-box"></div>
		</div>
		<!-- 拖动条 -->
		<div class="drag">
			<!-- 滑块 -->
			<div class="drag-box" id="drag-box">
				<!-- 对号 -->
				<div id="drag-mark" class="drag-mark"></div>
			</div>
			<!-- 文字提示 -->
			<div class="drag-tips">
				<span>按住左边按钮向右拖动完成上方图像验证</span>
			</div>
		</div>
	</body>
	<script>
		// 被验证区块
		const check = document.getElementById('check-box')
		// 滑块
		const drag = document.getElementById('drag-box')
		// 对号
		const mark = document.getElementById('drag-mark')
		let preX = 0
		// 鼠标按下
		const mouseDown = (event) => {
			// 获取鼠标坐标
			preX = event.pageX
			// 监听鼠标移动
			document.addEventListener('mousemove', mouseMove)
		}
		// 鼠标移动
		const mouseMove = (event) => {
			const { pageX } = event
			// 移动距离
			const moveX = pageX - preX
			// 移动不能超出区域
			if (moveX < 0 || moveX > 350) {
				return
			}
			check.style.transform = `translateX(${moveX}px)`
			drag.style.transform = `translateX(${moveX}px)`
		}
		// 鼠标抬起
		const mouseUp = (event) => {
			// 移出移动事件
			document.removeEventListener('mousemove', mouseMove)
			const { pageX } = event
			const moveX = pageX - preX
			if (moveX < 278 || moveX > 285) {
				// 没有在校验区域 增加回弹动画
				drag.style.animation = 'move 0.5s ease-in-out'
				check.style.animation = 'move 0.5s ease-in-out'
				// 动画结束回调
				const animationEnd = () => {
					check.style.transform = `translateX(${0}px)`
					drag.style.transform = `translateX(${0}px)`
					mark.style.opacity = 0
					// 清除动画
					drag.style.animation = ''
					check.style.animation = ''
					// 取消监听动画结束回调
					document.removeEventListener('animationEnd', animationEnd)
				}
				document.addEventListener('animationend', animationEnd)
			} else {
				// 对号
				mark.style.opacity = 1
				success()
			}
		}
		// 校验回调函数
		const success = () => {
			console.log('通过校验!')
		}
		// 滑块绑定鼠标按下事件
		drag.addEventListener('mousedown', mouseDown)
		document.addEventListener('mouseup', mouseUp)
	</script>
</html>

至此,关于JavaScript实现登录拼图验证的示例代码介绍完毕。如需了解更多相关内容,可搜索本站往期文章或继续浏览后续文章,感谢支持。

作者最新文章