uni-app图片滑块验证

2,215 阅读1分钟

操作步骤

  • 按住滑块将卡片移动到对应凹槽位置进行验证
  • 验证成功提示成功文字并刷新验证码
  • 验证失败提示失败文字并刷新验证码

问题

  • 如何实现移动
  • 卡片如何跟着滑块移动
  • 失败/成功后的刷新
  • 成功/失败验证

方法

  • uni-app的 movable-view (movable-view必须在movable-area`组件中,并且必须是直接子节点,否则不能移动。)
  • 绑定style样式, 卡片位置与滑块位置进行绑定
  • 失败/成功所有值置于默认状态, 实现刷新
  • 将滑块移动轨迹和当前页面渲染的尺寸比值传递给后台 tianai-captcha 进行校验

效果图

image.png

验证成功

image.png

验证失败

image.png

HTML部分

<template>
    <view class="container">
        <view class="verify-code">
            <view class="verify-code-top">安全验证</view>
            <view class="verify-box">
                <view class="verify-code-middle">
                    <view class="verify-bg">
                        <image id="bg" :src="captchaData.background_image" mode="heightFix"></image>
                    </view>
                    <view class="verify-slider">
                        <image id="slider-img" :style="{left: leftDistance+'rpx'}"  :src="captchaData.slider_image" mode="heightFix"></image>
                    </view>
                </view>
                <view v-if="isActive" @touchstart="touchstart" 
                                      @touchmove="touchmove"
                                      @touchend="touchend">
                    <movable-area class="move-block">
                        <view class="color-change" :style="{width: colorWidth+'rpx'}"></view>
                            <movable-view class="block-button" :x='x' direction="horizontal" @change="StartMove">
                            <text class="button-font">|||</text/>
                            </movable-view>
                    </movable-area>
                </view>
                <view v-if="isSuccess" class="check-suc-font">
                    <text style="color:green">验证成功</text>
                </view>
                <view v-if="isErr" class="check-err-font">
                        <text style="color: red;">验证失败</text>
                        <text>,请控制图块对齐缺口</text>
                </view>
            </view>
        </view>
    </view>
</template>

JS部分

<script>
export default {
    name:"verifyCode",
    data() {
        return {
            isActive: true,//刷新滑块
            colorWidth: 0,//颜色滑块
            leftDistance: 0,//默认卡片位置固定为靠左
            isSuccess: false,//验证成功
            isErr: false,//验证失败
            x: 0,//滑块的X距离
            xpos: 0,//读取X滑动的距离
            bgImg:{//当前环境滑块背景尺寸
                width: 0,
                height: 0
            },
            sliderImg:{//当前环境滑块尺寸
                width: 0,
                height: 0
            },
            captchaData:{
                id: null,//当前生成的滑块ID, 后端生成
                background_image: '',//滑块背景图
                slider_image: '',//滑块图片
                startTime: new Date(),//起始时间
                trackArr: [],//滑动轨迹
                movePercent: 0,//滑动距离与背景图百分比
                background_image_width: 0,//当前环境滑块背景宽
                background_image_height: 0,//当前环境滑块背景高
                slider_image_width: 0,//当前环境滑块宽
                slider_image_height: 0,//当前环境滑块高
                end: 206//滑块滑动界限值
            }
        };
    },
    onShow() {
        this.getVertifyData()
    },
    methods:{
        // 日志打印
        printLog(...params) {
            console.log(JSON.stringify(params));
        },
        // 滑块初始位置
        touchstart(e){
            let startX = e.changedTouches[0].pageX;
            let startY = e.changedTouches[0].pageY;
            this.captchaData.startX = startX;
            this.captchaData.startY = startY;
            const pageX = this.captchaData.startX;
            const pageY = this.captchaData.startY;
            const startTime = this.captchaData.startTime;
            const trackArr = this.captchaData.trackArr;
            trackArr.push({
                x: pageX - startX,
                y: pageY - startY,
                type: "down",
                t: (new Date().getTime() - startTime.getTime())
            })
            this.printLog('start', startX, startY)
        },
         // 滑块滑动中位置
        touchmove(e){
            let pageX = Math.round(e.changedTouches[0].pageX);
            let pageY = Math.round(e.changedTouches[0].pageY);
            const startX = this.captchaData.startX;
            const startY = this.captchaData.startY;
            const startTime = this.captchaData.startTime;
            const end = this.captchaData.end;
            const bgImageWidth = this.captchaData.background_image_width;
            const trackArr = this.captchaData.trackArr;
            let moveX = pageX - startX;
            const track = {
                x: pageX - startX,
                y: pageY - startY,
                type: "move",
                t: (new Date().getTime() - startTime.getTime())
            };
            trackArr.push(track);
            if(moveX < 0){
                    moveX = 0;
            } else if(moveX > end){
                    moveX = end;
            }
            this.captchaData.moveX = moveX;
            this.captchaData.movePercent = moveX / bgImageWidth;
            this.printLog("move", track)
        },
         // 滑块停止滑动
        touchend(e){
            this.captchaData.stopTime = new Date()
            let pageX = Math.round(e.changedTouches[0].pageX);
            let pageY = Math.round(e.changedTouches[0].pageY);
            const startX = this.captchaData.startX;
            const startY = this.captchaData.startY;
            const startTime = this.captchaData.startTime;
            const trackArr = this.captchaData.trackArr;
            const track = {
                    x: pageX - startX,
                    y: pageY - startY,
                    type: "up",
                    t: (new Date().getTime() - startTime.getTime())
            }
            trackArr.push(track);
            this.printLog("up", track)
            this.setVertifyData()
        },
        // 滑块绑定卡片移动距离
        StartMove(e){
            this.xpos = e.detail.x
            this.colorWidth = this.xpos*2 +80
            this.leftDistance = this.xpos*2
        },
        // 获取背景图和滑块图片
        getVertifyData(){
            let that = this
            this.$http.get('getVertifyData').then(res => {
                setTimeout(() => {
                    // 获取当前设备上渲染的背景图和滑块尺寸
                    let query = uni.createSelectorQuery().in(this)
                    query.select('#bg').boundingClientRect(res => {
                            this.bgImg.width = res.width
                            this.bgImg.height = res.height
                    }).exec();
                    query.select('#slider-img').boundingClientRect(res => {
                            this.sliderImg.width = res.width
                            this.sliderImg.height = res.height
                    }).exec();
                }, 1000)
                this.captchaData.id = res.data.id
                this.captchaData.background_image = res.data.captcha.background_image
                this.captchaData.slider_image = res.data.captcha.slider_image
                setTimeout(() => {
                    this.initConfig(this.bgImg.width, this.bgImg.height, this.sliderImg.width, this.sliderImg.height, 206)
                }, 1500)
            }).catch(err => {
                console.log(err);
            })
        },
        // 初始化配置
        initConfig(bgImageWidth, bgImageHeight, sliderImageWidth, sliderImageHeight, end){
            this.captchaData.background_image_width = bgImageWidth
            this.captchaData.background_image_height = bgImageHeight
            this.captchaData.slider_image_width = sliderImageWidth
            this.captchaData.slider_image_height = sliderImageHeight
            this.captchaData.end = end
        },
        // 校验是否成功
        setVertifyData(){
            const dataForm = {
                bg_image_width: this.captchaData.background_image_width,
                bg_image_height: this.captchaData.background_image_height,
                slider_image_width: this.captchaData.slider_image_width,
                slider_image_height: this.captchaData.slider_image_height,
                start_sliding_time: this.captchaData.startTime,
                ent_sliding_time: this.captchaData.stopTime,
                track_list: this.captchaData.trackArr
            }
            uni.request({
                url: uni.getStorageSync('baseUrl') + 'checkImage?id=' + this.captchaData.id,
                method: 'POST',
                dataType: 'json',
                data: JSON.stringify(dataForm),
                header: {
                    'Content-Type': 'application/json',
                    'token': uni.getStorageSync('token')
                }
            }).then(res => {
                if(res[1].statusCode == 200){
                    if(res[1].data == false){
                        this.isErr = true
                        setTimeout(() => {
                             this.refresh()
                        }, 1000)
                    } else {
                        this.isSuccess = true
                        setTimeout(() => {
                            this.refresh()
                            uni.navigateBack({
                                    delta: 1
                            });
                        }, 1000)
                    }
                }
            }).catch(err => {
                console.log(err);
            })
        },
        // 刷新滑块
        refresh(){
            this.isActive = false
            this.$nextTick(() => {
                    this.isActive = true
            })
            this.colorWidth = 0
            this.x = 0
            this.isErr = false
            this.isSuccess = false
            this.leftDistance = 0
            this.captchaData.trackArr = []
            this.getVertifyData()
        }
    }
}
</script>

CSS部分

<style scoped lang="scss">
.container{
    .verify-code{
        width: 100%; 
        height: 680rpx;
        background-color: #FFFFFF;
        padding: 100rpx 0;
        z-index: 999;
        box-shadow: 0 0 10rpx rgba(227, 227, 227, 0.7);
        .verify-code-top{
            width: 100%; 
            text-align: center; 
            line-height: 60rpx; 
            font-size: 36rpx;
            font-weight: 800;
        }
        .verify-box{
            width:100%;
            padding: 20rpx 5%;
            background-color: #ffffff;
            .verify-code-middle{
                width: 100%; 
                height: 400rpx;
                position: relative;
                .verify-bg{
                    width: 100%;
                    height: 100%;
                    position: absolute; 
                    image{
                        width: 100%;
                        height: 100%;
                    }
                }
                .verify-slider{
                    height: 100%;
                    position: absolute;
                    left: 0;
                    top: 0;
                    image{
                        height: 100%;
                    }
                }
            }
            .move-block{
                width: 100%;
                height: 80rpx;
                margin: 20rpx 0;
                background-color:rgba(255, 170, 0, 0.1);
                border-radius: 100rpx;
                .color-change{
                    height: 80rpx;
                    border-radius: 100rpx;
                    background-color: #ffaa00;
                    z-index:2;
                }
                .block-button{
                    border-radius: 100rpx;
                    background-color:white;
                    height: 100rpx;
                    width: 100rpx;
                    margin-top: -10rpx;
                    opacity: 0.5;
                    touch-action: none;
                    .button-font{
                        color: #ffaa00;
                        text-align: center;
                        font-size: 65rpx;
                        padding-left: 25rpx;
                    }
                }
            }

            .check-suc-font{
                width: 100%;
                text-align: center;
                font-size: 30rpx
            }
            .check-err-font{
                width: 100%;
                text-align: center;
                font-size: 30rpx
            }
        }
    }
}
</style>