Skip to content

图片拖动验证

思路

  1. 初始化

    随机生成背景图:随机选取背景图

    计算空白块的位置: 空白块的位置也是随机的,x方向只能在右半区域,y方向就是背景图高度-空白快高度

    计算拖动块的位置: 拖动块x初始位置为0 y方向和空白块保持一致。

    其他设置:初始title文字,颜色

  2. 交互

    点击滑块:设置一些基础状态,绑定按钮mousemove和document的mouseup事件

    移动滑块:计算移动位置、同步滑块和滑动块的位置,边界判断

    松开滑块:验证是否通过、清除对应的事件绑定,设置元素状态和文字。

  3. 拖动按钮的距离计算

要弄清楚如何计算拖动按钮的距离。如下图:

image-20210724210123819

用户之所以可以按住按钮实时的进行滑动,实际上就是在不停的修改按钮的 left 值。

那么实时的得到按钮最新的 left 值就是一个非常重要的点。

按钮实时的 left 值计算 = 滑条的 clientX - 滑条的 offsetLeft - 按钮的 offsetX

ps: 滑块松开后会有一个缓慢返回的效果,使用transition来实现即可,但是要注意的是,每次点击滑块都要清除掉transition,否则会出现滑动不同步的问题。

实践

html
<!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>图片拖动验证</title>
    <link rel="stylesheet" href="../css/index.css">
</head>
<body>
    <!-- 最外层的容器 -->
    <div class="container">
        <!-- 上方的切换图片部分 -->
        <p class="changeImg">图片不好看?换一张吧</p>
        <!-- 图片容器 -->
        <div class="imgContainer">
            <!-- 标题 -->
            <h3>请完成图片验证</h3>
            <!-- 图片区域 -->
            <div class="imgBox">
                <!-- 可以拖动的图片块 -->
                <div class="imgBlock"></div>
                <!-- 需要填补的图片缺口 -->
                <div class="imgGap"></div>
            </div>
            <!-- 滑动块 -->
            <div class="slider">
                <span>拖动滑块完成拼图</span>
                <button type="button" id="btn"></button>
            </div>
        </div>
    </div>
    <script src="../js/myIndex.js"></script>
</body>
</html>
css
/* 整体容器的样式 */

.container {
    width: 280px;
    height: 400px;
    margin: 100px auto;
}

/* 上方的图片不好看切换一张的样式 */

.container .changeImg {
    text-align: center;
    position: relative;
    font-size: 16px;
    color: rgb(126, 57, 218);
    font-weight: bolder;
    cursor: pointer;
    user-select: none;
}

/* 前面的图标 */

.changeImg::before {
    content: '';
    display: block;
    position: absolute;
    left: 10%;
    top: calc(50% - 13px);
    width: 26px;
    height: 26px;
    background: url('../img/sx.png') no-repeat;
    background-size: cover;
    background-position: center;
}

/* img 容器整体样式,包含标题 + 图片 + 滑条 */

.imgContainer {
    height: 320px;
    box-sizing: border-box;
    padding: 15px;
    border: 1px solid #adadad;
    box-shadow: 0px 0px 2px #adadad;
    /* 设置盒子阴影 */
    border-radius: 15px;
    /* 设置圆角 */
}

/* 上方标题 */

.imgContainer h3 {
    text-align: center;
    margin: 0;
    margin-bottom: 10px;
}

/* 中间的图片 */

.imgContainer .imgBox {
    width: 100%;
    height: 200px;
    background-repeat: no-repeat;
    position: relative;
}

/* 可以拖动的图片 */

.imgBox .imgBlock {
    width: 50px;
    height: 50px;
    position: absolute;
    z-index: 10;
    opacity: 0;
}

/* 图片缺口 */

.imgBox .imgGap {
    width: 50px;
    height: 50px;
    position: absolute;
    background-color: white;
    box-shadow: 0px 0px 3px #adadad;
    /* 设置盒子阴影 */
}

/* 滑动条 */

.slider {
    width: 100%;
    height: 30px;
    margin: auto;
    margin-top: 15px;
    background-color: #ddd;
    border-radius: 10px;
    position: relative;
    text-align: center;
    line-height: 30px;
    font-size: 14px;
    font-weight: 200;
}

/* 滑动条的按钮 */

.slider button {
    position: absolute;
    top: -5px;
    left: -2px;
    background: white url('../img/yz.png') no-repeat;
    background-size: 100%;
    border-radius: 50%;
    border: 0;
    width: 40px;
    height: 40px;
    cursor: pointer;
    z-index: 20;
}

/* 滑动条文字的样式 */

.slider span {
    transition: all .5s;
}
js
/* 工具 */
// 选择器
function $(selector) {
    return document.querySelector(selector);
}

// 随机数
function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min) + min);
}


/* 初始化 */
const doms = {
    imgBox: $('.imgBox'),// 图片容器
    imgBlock: $('.imgBlock'),// 可以拖动的图片
    imgGap: $('.imgGap'),// 图片缺口
    title: $('.imgContainer h3'),// 获取标题
    slider: $('.slider'),// 滑块条
    btn: $('#btn'),// 拖动的滑块
    span: $('.slider span'),// 滑块条的文字
    changeImg: $(".changeImg")//更换图片
}

function init() {
    // 随机生成背景图
    const imgArr = ['t1.png', 't2.png', 't3.png', 't4.png', 't5.png'];
    const src = getRandom(0, imgArr.length);

    // 计算空白块的位置
    const xRange = doms.imgBox.offsetWidth / 2 - doms.imgGap.offsetWidth;
    const yRange = doms.imgBox.offsetHeight - doms.imgGap.offsetHeight;
    const dx = getRandom(xRange + doms.imgBox.offsetWidth / 2, xRange);
    const dy = getRandom(0, yRange);

    // 设置背景
    doms.imgBox.style.backgroundImage = `url('../img/${imgArr[src]}')`
    doms.imgBlock.style.backgroundImage = `url('../img/${imgArr[src]}')`

    // 设置空白块位置
    doms.imgGap.style.left = `${dx}px`;
    doms.imgGap.style.top = `${dy}px`;
    doms.imgGap.style.opacity = 1;

    // 设置拖动块背景和位置
    doms.imgBlock.style.left = `0px`;
    doms.imgBlock.style.top = `${dy}px`;
    doms.imgBlock.style.backgroundPositionX = `-${dx}px`;
    doms.imgBlock.style.backgroundPositionY = `-${dy}px`;
    doms.imgBlock.style.opacity = 0;

    // 其他初始设置
    doms.btn.style.left = '-2px';//滑块初始位置
    doms.span.style.opacity = 1;//滑块区域内文字
    doms.title.style.color = '#333';
    doms.title.innerHTML = '请完成图片验证'
}

init();


/* 交互 */
// 点击滑块
function sliderClick(e) {
    doms.imgBlock.style.opacity = 1;//显示滑动块
    doms.title.innerHTML = `拖动图片完成验证`;//设置标题
    doms.title.style.color = "#333";//设置标题颜色
    doms.span.style.opacity = 0;//隐藏滑块文字
    doms.imgBlock.style.transition = 'none';//清空滑动块过渡
    doms.btn.style.transition = 'none';//清空按钮过渡
    // 绑定事件
    doms.btn.onmousemove = (ev) => (move(e, ev));//绑定移动事件
    document.onmouseup = sliderFree;//绑定松开鼠标事件
}

// 松开滑块
function sliderFree() {
    // 校验
    const diff = doms.imgGap.offsetLeft - doms.imgBlock.offsetLeft;//获取两个图块的差值
    const isPassed = diff < 5 && diff > -5;

    // 校验成功
    if (isPassed) {
        doms.title.innerHTML = `校验成功`;
        doms.title.style.color = "#008c8c";
        doms.imgBlock.style.opacity = 0;
        doms.imgGap.style.opacity = 0;
        doms.slider.onmousemove = null;
        doms.btn.onmousedown = null;
        document.onmouseup = null;
        return
    }

    // 校验失败
    doms.imgBlock.style.transition = 'all .5s';//添加过渡
    doms.btn.style.transition = 'all .5s';//添加过渡
    doms.imgBlock.style.left = "0px";//恢复初始位置
    doms.btn.style.left = "-2px";
    doms.title.innerHTML = `校验失败`;//设置失败文字
    doms.title.style.color = "#f55";
    doms.span.style.opacity = 1;//显示滑块区域文字
    doms.btn.onmousemove = null;
    document.onmouseup = null;
}

// 移动
function move(clickEv, mouveEv) {
    let dx = mouveEv.clientX - doms.slider.offsetLeft - clickEv.offsetX;
    const min = -2;
    const max = doms.imgBox.offsetWidth - doms.imgBlock.offsetWidth;

    // 边界处理
    dx < min && (dx = min);
    dx > max && (dx = max);

    // 设置滑块和滑动块位移
    doms.btn.style.left = `${dx}px`;
    doms.imgBlock.style.left = `${dx}px`;
}

// 绑定事件
doms.slider.onmousedown = sliderClick;
doms.changeImg.onclick = init;

MIT License