vue 滑动拼图验证

简介: vue 滑动拼图验证

使用方法

1. 新建文件 dragVerifyImgChip.vue ,复制粘贴下方代码

<template>
    <div class="drag-verify-container">
 
        <div :style="dragVerifyImgStyle">
            <img ref="checkImg" :src="imgsrc" @load="checkimgLoaded" style="width:100%">
            <canvas ref="maincanvas" class="main-canvas"></canvas>
            <canvas ref="movecanvas" :style="movecanvasStyle" :class="{goFirst:isOk, goKeep:isKeep}"></canvas>
            <div class="refresh" v-if="showRefresh && !isPassing">
                <i :class="refreshIcon" @click="refreshimg"></i>
            </div>
            <div class="tips success" v-if="showTips && isPassing">{{successTip}}</div>
            <div class="tips danger" v-if="showTips && !isPassing && showErrorTip">{{failTip}}</div>
        </div>
        <div
                ref="dragVerify"
                class="drag_verify"
                :style="dragVerifyStyle"
                @mousemove="dragMoving"
                @touchmove="dragMoving"
                @mouseup="dragFinish"
                @mouseleave="dragFinish"
                @touchend="dragFinish"
        >
 
            <div
                    class="dv_progress_bar"
                    :class="{goFirst2:isOk}"
                    ref="progressBar"
                    :style="progressBarStyle"
            >
                {{isPassing ? successText : ""}}
            </div>
 
            {{isPassing ? "" : text}}
 
            <div
                    class="dv_handler dv_handler_bg"
                    :class="{goFirst:isOk}"
                    @mousedown="dragStart"
                    @touchstart="dragStart"
                    ref="handler"
                    :style="handlerStyle"
            >
                <i :class="handlerIcon"></i>
            </div>
        </div>
    </div>
</template>
<script>
    export default {
        name: "dragVerifyImgChip",
        props: {
            width: {
                type: Number,
                default: 250
            },
            height: {
                type: Number,
                default: 40
            },
            text: {
                type: String,
                default: "请按住滑块向右拖动"
            },
            successText: {
                type: String,
                default: "验证通过"
            },
            background: {
                type: String,
                default: "#eee"
            },
            progressBarBg: {
                type: String,
                default: "#76c61d"
            },
            completedBg: {
                type: String,
                default: "#76c61d"
            },
            handlerIcon: {
                type: String,
                default: "el-icon-d-arrow-right"
            },
            successIcon: {
                type: String,
                default: "el-icon-circle-check"
            },
            handlerBg: {
                type: String,
                default: "#fff"
            },
            textSize: {
                type: String,
                default: "14px"
            },
            textColor: {
                type: String,
                default: "#333"
            },
            imgsrc: {
                type: String,
                default: "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1593427296359&di=71d5f2b15199ed5389546528b1c61a83&imgtype=0&src=http%3A%2F%2Fphotocdn.sohu.com%2F20110814%2FImg316287483.jpg"
            },
            barWidth: {
                type: Number,
                default: 40
            },
            barRadius: {
                type: Number,
                default: 8
            },
            showRefresh: {
                type: Boolean,
                default: false
            },
            refreshIcon: {
                type: String,
                default: "el-icon-refresh white"
            },
            showTips: {
                type: Boolean,
                default: true
            },
            failTip: {
                type: String,
                default: "验证失败,请重试"
            },
            diffWidth: {
                type: Number,
                default: 2
            }
        },
        mounted: function () {
            const dragEl = this.$refs.dragVerify;
            dragEl.style.setProperty("--textColor", this.textColor);
            dragEl.style.setProperty("--width", Math.floor(this.width / 2) + "px");
            dragEl.style.setProperty("--pwidth", -Math.floor(this.width / 2) + "px");
        },
        computed: {
            movecanvasStyle: function () {
                return {
                    left: -this.clipBarx + "px ",
                    position: 'absolute',
                    top: 0
                };
            },
            handlerStyle: function () {
                return {
                    left: "0px",
                    width: this.height + "px",
                    height: this.height + "px",
                    background: this.handlerBg
                };
            },
            dragVerifyStyle: function () {
                return {
                    width: this.width + "px",
                    height: this.height + "px",
                    lineHeight: this.height + "px",
                    background: this.background,
 
                };
            },
            dragVerifyImgStyle: function () {
                return {
                    width: this.width + "px",
                    position: 'relative',
                    overflow: 'hidden'
                };
            },
            progressBarStyle: function () {
                return {
                    background: this.progressBarBg,
                    height: this.height + "px",
                    color: "white"
                };
            },
 
        },
        data() {
            return {
                successTip: '',
                beginTime: 0,
                endTime: 0,
                isPassing: false,
                isMoving: false,
                x: 0,
                isOk: false,
                isKeep: false,
                clipBarx: 0,
                showErrorTip: false
            };
        },
        methods: {
            draw: function (ctx, x, y, operation) {
                let l = this.barWidth;
                let r = this.barRadius;
                const PI = Math.PI
                ctx.beginPath()
                ctx.moveTo(x, y)
                ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI)
                ctx.lineTo(x + l, y)
                ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI)
                ctx.lineTo(x + l, y + l)
                ctx.lineTo(x, y + l)
                ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true)
                ctx.lineTo(x, y)
                ctx.lineWidth = 2
                ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'
                ctx.strokeStyle = 'rgba(255, 255, 255, 0.8)'
                ctx.stroke()
                ctx[operation]()
                ctx.globalCompositeOperation = 'destination-over'
            },
            checkimgLoaded: function () {
                // 生成图片缺失位置
                let barWidth = this.barWidth;
                let imgHeight = this.$refs.checkImg.height;
                let imgWidth = this.$refs.checkImg.width;
                let halfWidth = Math.floor(this.width / 2);
                let refreshHeigth = 25;
                let tipHeight = 20;
                let x = halfWidth + Math.ceil(Math.random() * (halfWidth - barWidth));
                let y = refreshHeigth + Math.floor(Math.random() * (imgHeight - barWidth - refreshHeigth - tipHeight));
                this.$refs.maincanvas.setAttribute('width', imgWidth)
                this.$refs.maincanvas.setAttribute('height', imgHeight)
                this.$refs.maincanvas.style.display = 'block'
                let canvasCtx = this.$refs.maincanvas.getContext('2d')
                this.draw(canvasCtx, x, y, 'fill');
                this.clipBarx = x;
 
                let moveCanvas = this.$refs.movecanvas;
                moveCanvas.setAttribute('width', imgWidth)
                this.$refs.movecanvas.style.display = 'block'
                const L = barWidth + this.barRadius * 2 + 3; //实际宽度
                let moveCtx = this.$refs.movecanvas.getContext('2d')
                moveCtx.clearRect(0, 0, imgWidth, imgHeight)
                this.draw(moveCtx, x, y, 'clip');
                moveCtx.drawImage(this.$refs.checkImg, 0, 0, imgWidth, imgHeight)
                y = y - this.barRadius * 2 - 1;
                const ImageData = moveCtx.getImageData(x, y, L, L)
                moveCanvas.setAttribute('width', L)
                moveCanvas.setAttribute('height', imgHeight)
                moveCtx.putImageData(ImageData, 0, y)
            },
            dragStart: function (e) {
                this.beginTime = new Date().getTime()
                if (!this.isPassing) {
                    this.isMoving = true;
                    let handler = this.$refs.handler;
                    this.x =
                        (e.pageX || e.touches[0].pageX) -
                        parseInt(handler.style.left.replace("px", ""), 10);
                }
                this.showErrorTip = false;
                this.$emit("handlerMove");
            },
            dragMoving: function (e) {
                if (this.isMoving && !this.isPassing) {
                    let _x = (e.pageX || e.touches[0].pageX) - this.x;
 
                    let handler = this.$refs.handler;
                    handler.style.left = _x + "px";
                    this.$refs.progressBar.style.width = _x + this.height / 2 + "px";
                    this.$refs.movecanvas.style.left = _x - this.clipBarx + "px";
                }
            },
            dragFinish: function (e) {
                if (this.isMoving && !this.isPassing) {
                    let _x = (e.pageX || e.changedTouches[0].pageX) - this.x;
                    if (Math.abs(_x - this.clipBarx) > this.diffWidth) {
                        this.isOk = true;
                        let that = this;
                        setTimeout(function () {
                            that.$refs.handler.style.left = "0";
                            that.$refs.progressBar.style.width = "0";
 
                            that.$refs.movecanvas.style.left = - that.clipBarx + "px",
                                that.isOk = false;
                        }, 500);
                        this.showErrorTip = true;
                    } else {
                        this.passVerify();
                    }
                    this.isMoving = false;
                }
            },
            passVerify: function () {
                this.endTime = new Date().getTime()
                this.successTip = "耗时" + (this.endTime - this.beginTime) / 1000 + 's'
                this.isPassing = true
                this.isMoving = false;
                let handler = this.$refs.handler;
                handler.children[0].className = this.successIcon;
                this.$refs.progressBar.style.background = this.completedBg;
                this.$refs.progressBar.style.color = "#fff";
                this.$refs.progressBar.style.fontSize = this.textSize;
                this.isKeep = true;
                this.$refs.maincanvas.style.display = 'none'
                this.$refs.movecanvas.style.display = 'none'
                this.$emit("passcallback");
            },
            reset: function () {
                this.reImg();
                this.checkimgLoaded();
            },
            reImg: function () {
                this.$emit("update:isPassing", false);
                const oriData = this.$options.data();
                for (const key in oriData) {
                    if (oriData.hasOwnProperty(key)) {
                        this.$set(this, key, oriData[key]);
                    }
                }
                let handler = this.$refs.handler;
                handler.style.left = "0";
                this.$refs.progressBar.style.width = "0";
                handler.children[0].className = this.handlerIcon;
                this.$refs.movecanvas.style.left = "0px";
            },
            refreshimg: function () {
                this.$emit('refresh')
            }
        },
        watch: {
            imgsrc: {
                immediate: false,
                handler: function () {
                    this.reImg();
                }
            }
        }
    };
</script>
<style scoped>
    .drag_verify {
        position: relative;
        background-color: #e8e8e8;
        text-align: center;
        overflow: hidden;
    }
 
    .drag_verify .dv_handler {
        position: absolute;
        top: 0px;
        left: 0px;
        cursor: move;
    }
 
    .drag_verify .dv_handler i {
        color: #666;
        padding-left: 0;
        font-size: 16px;
    }
 
    .drag_verify .dv_handler .el-icon-circle-check {
        color: #6c6;
        margin-top: 9px;
    }
 
    .drag_verify .dv_progress_bar {
        position: absolute;
        height: 34px;
        width: 0px;
    }
 
    .drag_verify .dv_text {
        position: absolute;
        top: 0px;
        color: transparent;
        -moz-user-select: none;
        -webkit-user-select: none;
        user-select: none;
        -o-user-select: none;
        -ms-user-select: none;
        background: -webkit-gradient(
                linear,
                left top,
                right top,
                color-stop(0, let(--textColor)),
                color-stop(0.4, let(--textColor)),
                color-stop(0.5, #fff),
                color-stop(0.6, let(--textColor)),
                color-stop(1, let(--textColor))
        );
        -webkit-background-clip: text;
        -webkit-text-fill-color: transparent;
        -webkit-text-size-adjust: none;
        animation: slidetounlock 3s infinite;
    }
 
    .drag_verify .dv_text * {
        -webkit-text-fill-color: let(--textColor);
    }
 
    .goFirst {
        transition: left 0.5s;
    }
 
    .goKeep {
        transition: left 0.2s;
    }
 
    .goFirst2 {
        width: 0px !important;
        transition: width 0.5s;
    }
 
    .drag-verify-container {
        position: relative;
        line-height: 0;
    }
 
    .refresh {
        position: absolute;
        right: 5px;
        top: 5px;
        cursor: pointer;
        font-size: 20px;
        z-index: 200;
    }
 
    .tips {
        position: absolute;
        bottom: 0;
        height: 20px;
        line-height: 20px;
        text-align: center;
        width: 100%;
        font-size: 12px;
        z-index: 200;
    }
 
    .tips.success {
        background: rgba(255, 255, 255, 0.6);
        color: green;
    }
 
    .tips.danger {
        background: rgba(0, 0, 0, 0.6);
        color: yellow;
    }
 
    .main-canvas {
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
    }
 
    .white {
        color: white;
    }
 
    @-webkit-keyframes slidetounlock {
        0% {
            background-position: let(--pwidth) 0;
        }
        100% {
            background-position: let(--width) 0;
        }
    }
 
    @-webkit-keyframes slidetounlock2 {
        0% {
            background-position: let(--pwidth) 0;
        }
        100% {
            background-position: let(--pwidth) 0;
        }
    }
</style>

2. 在任意vue文件中使用

<drag-verify-img-chip @passcallback="pass" />
import dragVerifyImgChip from "@/components/utils/verify/dragVerifyImgChip";
components: {dragVerifyImgChip},
            pass() {
                alert("验证通过!")
            },

参数

width

宽度

number

-

250

height

高度

number

-

40

text

初始文字

string

-

swiping to the right side

successText

成功提示文字

string

-

success

background

滑块右侧背景色

string

#eee / red / rgba(52,52,52,0.7)

#eee

progressBarBg

滑块左侧背景色

string

#76c61d / white / rgba(52,52,52,0.7)

#76c61d

handlerBg

滑块背景色

string

#fff / white / rgb(255,255,255)

#fff

completedBg

验证通过背景色

string

#76c61d / white / rgba(52,52,52,0.7)

#76c61d

circle

两侧是否圆形

boolean

true / false

false

handlerIcon

滑块未验证通过时的图标,根据所选框架设置不同class

string

el-icon-d-arrow-right

-

successIcon

滑块验证通过时的图标,根据所选框架设置不同class

string

el-icon-circle-check

-

textSize

文字大小

string

14px / 4em

14px

textColor

文字颜色

string

#333 / gray / rgb(52,52,52)

#333

imgsrc

图片地址

string

-

-

barWidth

拼图宽度,同拼图高度

number

40

40

barRadius

拼图外部圆形半径

number

8

8

showRefresh

是否右上角显示刷新

boolean

true / false

false

refreshIcon

刷新按钮图标的class

string

el-icon-refresh-right

-

showTips

是否显示底部提示

boolean

true / false

true

failTip

底部验证失败提示

string

验证失败

验证失败,请重试

diffWidth

在此范围内松开计算验证成功(单位px)

number

10

2

事件

passcallback

验证通过回调


目录
相关文章
|
7月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
623 2
|
6月前
|
缓存 JavaScript
vue中的keep-alive问题(2)
vue中的keep-alive问题(2)
537 137
|
10月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
1059 0
|
10月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
9月前
|
人工智能 JSON JavaScript
VTJ.PRO 首发 MasterGo 设计智能识别引擎,秒级生成 Vue 代码
VTJ.PRO发布「AI MasterGo设计稿识别引擎」,成为全球首个支持解析MasterGo原生JSON文件并自动生成Vue组件的AI工具。通过双引擎架构,实现设计到代码全流程自动化,效率提升300%,助力企业降本增效,引领“设计即生产”新时代。
642 1
|
9月前
|
JavaScript 安全
在 Vue 中,如何在回调函数中正确使用 this?
在 Vue 中,如何在回调函数中正确使用 this?
446 0
|
10月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
625 1
|
12月前
|
JavaScript
vue实现任务周期cron表达式选择组件
vue实现任务周期cron表达式选择组件
1342 4
|
11月前
|
JavaScript 数据可视化 前端开发
基于 Vue 与 D3 的可拖拽拓扑图技术方案及应用案例解析
本文介绍了基于Vue和D3实现可拖拽拓扑图的技术方案与应用实例。通过Vue构建用户界面和交互逻辑,结合D3强大的数据可视化能力,实现了力导向布局、节点拖拽、交互事件等功能。文章详细讲解了数据模型设计、拖拽功能实现、组件封装及高级扩展(如节点类型定制、连接样式优化等),并提供了性能优化方案以应对大数据量场景。最终,展示了基础网络拓扑、实时更新拓扑等应用实例,为开发者提供了一套完整的实现思路和实践经验。
1439 78
|
12月前
|
缓存 JavaScript 前端开发
Vue 基础语法介绍
Vue 基础语法介绍