vue 滑動拼圖驗證

目錄

1. 新建文件 dragVerifyImgChip.vue ,複製粘貼下方代碼

2. 在任意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

驗證通過回調

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章