Vue 使用接口返回的背景图片和拼图图片进行滑动拼图验证

一、背景

前两天发了一篇 vue-monoplasty-slide-verify 滑动验证码插件使用及踩坑_vue-monoplasty-slide-verify 引用后不显示-CSDN博客

这两天项目又需要通过接口校验,接口返回了背景图片和拼图图片,于是在网上找了一篇帖子,vue + 图片滑动验证_基于vue图片滑动验证-CSDN博客

写的不错,代码拿过来就能用;但是感觉源码和vue-monoplasty-slide-verify的比较像,vue-monoplasty-slide-verify的icon图片拿过来直接就能在参考帖子里用。

二、获取验证码接口返回数据示例

三、校验接口返回数据示例

四、封装与后端配合的滑动组件

javascript 复制代码
//在参考帖基础上修改的
<template>
    <div>
        <el-dialog :visible.sync="showSlideVerify" title="向右滑动通过验证" :width=" w +48 + 'px'" :close-on-click-modal="false" :close-on-press-escape="false">
            <div class="slide-verify" :style="{width: w + 'px'}" id="slideVerify" onselectstart="return false;">
                <!-- 图片加载遮蔽罩 -->
                <div :class="{'slider-verify-loading': loadBlock}"></div>
                <canvas :width="w" :height="h" ref="canvas"></canvas>
                <div v-if="showRefresh" @click="refresh" class="slide-verify-refresh-icon"></div>
                <canvas :width="block_width" :height="h" ref="block" class="slide-verify-block"></canvas>
                <!-- container -->
                <div class="slide-verify-slider" :class="{'container-active': containerActive, 'container-success': containerSuccess, 'container-fail': containerFail}">
                    <div class="slide-verify-slider-mask" :style="{width: sliderMaskWidth}">
                        <!-- slider -->
                        <div @mousedown="sliderDown" @touchstart="touchStartEvent" @touchmove="touchMoveEvent" @touchend="touchEndEvent" class="slide-verify-slider-mask-item" :style="{left: sliderLeft}">
                            <div class="slide-verify-slider-mask-item-icon"></div>
                        </div>
                    </div>
                    <span class="slide-verify-slider-text">{{sliderText}}</span>
                </div>
            </div>
        </el-dialog>
    </div>
  </template>
<script>
import { getCanvasAndPuzzle, checkPuzzle } from '@/api/modules/setting'

export default {
    name: 'SlideVerify',
    props: {
        sliderText: {
            type: String,
            default: '向右滑动'
        },
        showRefresh: {
            type: Boolean,
            default: true
        }
    },
    data () {
        return {
            showSlideVerify: false,
            containerActive: false, // container active class
            containerSuccess: false, // container success class
            containerFail: false, // container fail class

            nonceStr: '',

            canvasCtx: null,
            blockCtx: null,
            block: null,
            block_src: undefined,
            block_y: undefined,
            block_width: undefined,
            block_height: undefined,
            block_radius: undefined,

            w: 0,
            h:0,
            img: undefined,

            originX: undefined,
            originY: undefined,
            isMouseDown: false,
            trail: [],
            sliderLeft: 0, // block right offset
            sliderMaskWidth: 0, // mask width,
            success: false, // Bug Fixes 修复了验证成功后还能滑动
            loadBlock: true, // Features 图片加载提示,防止图片没加载完就开始验证
            timestamp: null
        }
    },
    methods: {
        show () {
            Object.assign(this.$data, {
                success: false,
                containerActive: false,
                containerSuccess: false,
                containerFail: false,

                nonceStr: '',

                w: 0,
                h: 0,
                img: undefined,
                
                loadBlock: true,
                block_src: undefined,
                block_y: undefined,
                block_width: undefined,
                block_height: undefined,
                block_radius: undefined,

                sliderLeft: 0,
                sliderMaskWidth: 0
            });
            this.getCanvasAndPuzzle();
        },
        getCanvasAndPuzzle () { //从接口获取滑动组件背景图片、图片宽高与拼图图片、拼图宽高、block_radius、垂直方向上的位置
            getCanvasAndPuzzle().then(res => {
                Object.assign(this.$data, {
                    nonceStr: res.data.nonceStr, //根据接口的校验逻辑,校验时需传递给后台
                    w: res.data.canvasWidth,
                    h: res.data.canvasHeight,
                    img: res.data.canvasSrc,
                    block_src: res.data.blockSrc,
                    block_y: res.data.blockY,
                    block_width: res.data.blockWidth,
                    block_height: res.data.blockHeight,
                    block_radius: res.data.blockRadius,
                    showSlideVerify: true
                });
                this.$nextTick(() => {
                    this.init();
                });
            }).catch(error => {
                this.$message.error(error);
            });
        },
        init () {
            this.initDom();
            this.initImg();
            this.bindEvents();
        },
        initDom () {
            this.block = this.$refs.block;
            this.canvasCtx = this.$refs.canvas.getContext('2d');
            this.blockCtx = this.block.getContext('2d');
        },
        initImg () {
            let _this = this, image = new Image(), blockImage = new Image();
            image.src = this.img;
            blockImage.src = this.block_src;
            image.onload = () => {
                _this.canvasCtx.drawImage(image, 0, 0, _this.w, _this.h);
            };
            blockImage.onload = () => {
                _this.loadBlock = false;
                // 第二个、三个参数分别是被拖动的拼图在水平、垂直方向的偏移量
                _this.blockCtx.drawImage(blockImage, 0, _this.block_y, _this.block_width, _this.block_height);

            };
        },
        sliderDown (event) {
            if (this.success) return;
            this.originX = event.clientX;
            this.originY = event.clientY;
            this.isMouseDown = true;
            this.timestamp = + new Date();
        },
        touchStartEvent (e) {
            if (this.success) return;
            this.originX = e.changedTouches[0].pageX;
            this.originY = e.changedTouches[0].pageY;
            this.isMouseDown = true;
            this.timestamp = + new Date();
        },
        bindEvents () {
            document.addEventListener('mousemove', (e) => {
                if (!this.isMouseDown) return false;
                const moveX = e.clientX - this.originX;
                const moveY = e.clientY - this.originY;
                if (moveX < 0 || moveX + 38 >= this.w) return false;
                this.sliderLeft = moveX + 'px';
                let blockLeft = (this.w - 40 - 20) / (this.w - 40) * moveX;
                this.block.style.left = blockLeft + 'px';
                this.containerActive = true;
                this.sliderMaskWidth = moveX + 'px';
                this.trail.push(moveY);
            });
            document.addEventListener('mouseup', (e) => {
                if (!this.isMouseDown) return false;
                this.isMouseDown = false;
                if (e.clientX === this.originX) return false;
                this.containerActive = false;
                this.timestamp = + new Date() - this.timestamp;

                this.verify();
            });
        },
        touchMoveEvent (e) {
            if (!this.isMouseDown) return false;
            const moveX = e.changedTouches[0].pageX - this.originX;
            const moveY = e.changedTouches[0].pageY - this.originY;
            if (moveX < 0 || moveX + 38 >= this.w) return false;
            this.sliderLeft = moveX + 'px';
            let blockLeft = (this.w - 40 - 20) / (this.w - 40) * moveX;
            this.block.style.left = blockLeft + 'px';

            this.containerActive = true;
            this.sliderMaskWidth = moveX + 'px';
            this.trail.push(moveY);
        },
        touchEndEvent (e) {
            if (!this.isMouseDown) return false;
            this.isMouseDown = false;
            if (e.changedTouches[0].pageX === this.originX) return false;
            this.containerActive = false;
            this.timestamp = + new Date() - this.timestamp;
            this.verify();
        },
        verify () {
//将拼图水平方向拖动量传递给接口,接口来校验是否拼好返回校验结果,我也不是很理解这个操作,但没办法,就让这样做,只能自己网上找例子,然后改出来了
            checkPuzzle({
                nonceStr: this.nonceStr,
                value: parseInt(this.block.style.left) //被拖动拼图在水平方向上的移动量
            }).then(res => {
                if(res.code===-1 || !res.data) {//校验通过,res.data的值为true,否则为false
                    this.containerFail = true;
                    this.$message.error(res.message);
                    this.refresh();
                } else {
                    this.$emit('verify-success');
                    Object.assign(this.$data, {
                        containerSuccess: true,
                        success: true,
                        showSlideVerify: false
                    });
                }
            }).catch(error => {
                this.$message.error(error);
            });
        },
        refresh () {
            let { w, h, block_width } = this;
            this.canvasCtx.clearRect(0, 0, w, h);
            this.blockCtx.clearRect(0, 0, block_width, h);
            this.block.style.left = 0;
            Object.assign(this.$data, {
                success: false,
                containerActive: false,
                containerSuccess: false,
                containerFail: false,

                nonceStr: '',
                img: undefined,
                loadBlock: true,
                sliderLeft: 0,
                sliderMaskWidth: 0
            });
            this.getCanvasAndPuzzle();
        }
    }
}
</script>
<style scoped>
.slide-verify {
    position: relative;
}
  
/* 图片加载样式 */
.slider-verify-loading {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    bottom: 0;
    background: rgba(255, 255, 255, 0.9);
    z-index: 999;
    animation: loading 1.5s infinite;
}

@keyframes loading {
    0% {
        opacity: 0.7;
    }
    100% {
        opacity: 9;
    }
}
  
.slide-verify-block {
    position: absolute;
    left: 0;
    top: 0;
}
  
.slide-verify-refresh-icon {
    position: absolute;
    right: 0;
    top: 0;
    width: 34px;
    height: 34px;
    cursor: pointer;
    background: url('./imgs/icon.png') 0 -437px;
    background-size: 34px 471px;
}

.slide-verify-slider {
    position: relative;
    text-align: center;
    width: 100%;
    height: 40px;
    line-height: 40px;
    /* margin-top: 15px; */
    background: #f7f9fa;
    color: #45494c;
    border: 1px solid #e4e7eb;
}

.slide-verify-slider-mask {
    position: absolute;
    left: 0;
    top: 0;
    height: 40px;
    border: 0 solid #1991fa;
    background: #d1e9fe;
}

.slide-verify-slider-mask-item {
    position: absolute;
    top: 0;
    left: 0;
    width: 40px;
    height: 40px;
    background: #fff;
    box-shadow: 0 0 3px rgba(0, 0, 0, 0.3);
    cursor: pointer;
    transition: background 0.2s linear;
}

.slide-verify-slider-mask-item:hover {
    background: #1991fa;
}

.slide-verify-slider-mask-item:hover .slide-verify-slider-mask-item-icon {
    background-position: 0 -13px;
}

.slide-verify-slider-mask-item-icon {
    position: absolute;
    top: 15px;
    left: 13px;
    width: 14px;
    height: 12px;
    background: url('./imgs/icon.png') 0 -26px;
    background-size: 34px 471px;
}
.container-active .slide-verify-slider-mask-item {
    height: 38px;
    top: -1px;
    border: 1px solid #1991fa;
}

.container-active .slide-verify-slider-mask {
    height: 38px;
    border-width: 1px;
}

.container-success .slide-verify-slider-mask-item {
    height: 38px;
    top: -1px;
    border: 1px solid #52ccba;
    background-color: #52ccba !important;
}

.container-success .slide-verify-slider-mask {
    height: 38px;
    border: 1px solid #52ccba;
    background-color: #d2f4ef;
}

.container-success .slide-verify-slider-mask-item-icon {
    background-position: 0 0 !important;
}

.container-fail .slide-verify-slider-mask-item {
    height: 38px;
    top: -1px;
    border: 1px solid #f57a7a;
    background-color: #f57a7a !important;
}

.container-fail .slide-verify-slider-mask {
    height: 38px;
    border: 1px solid #f57a7a;
    background-color: #fce1e1;
}

.container-fail .slide-verify-slider-mask-item-icon {
    top: 14px;
    background-position: 0 -82px !important;
}

.container-active .slide-verify-slider-text,
.container-success .slide-verify-slider-text,
.container-fail .slide-verify-slider-text {
    display: none;
}
</style>

五、 接口

意义不太大,要根据项目实际情况来的

六、CSS 代码里用到的图片资源

https://download.csdn.net/download/hrcsdn13/89709217

七、CSS与组件文件位置

八、效果图

相关推荐
Watermelo61711 分钟前
详解js柯里化原理及用法,探究柯里化在Redux Selector 的场景模拟、构建复杂的数据流管道、优化深度嵌套函数中的精妙应用
开发语言·前端·javascript·算法·数据挖掘·数据分析·ecmascript
m0_7482489412 分钟前
HTML5系列(11)-- Web 无障碍开发指南
前端·html·html5
m0_7482356124 分钟前
从零开始学前端之HTML(三)
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
小型 Vue 项目,该不该用 Pinia 、Vuex呢?
前端·javascript·vue.js
hackeroink5 小时前
【2024版】最新推荐好用的XSS漏洞扫描利用工具_xss扫描工具
前端·xss
迷雾漫步者7 小时前
Flutter组件————FloatingActionButton
前端·flutter·dart
向前看-7 小时前
验证码机制
前端·后端
燃先生._.8 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖9 小时前
[react]searchParams转普通对象
开发语言·前端·javascript