如何在vue3中使用滑塊檢驗(yàn)vue-puzzle-verification
官網(wǎng)鏈接 https://github.com/he4398/vue-puzzle-verification
因?yàn)楣倬W(wǎng)是vue2寫的,而我現(xiàn)在的項(xiàng)目是使用vue3,所有我就把官網(wǎng)的源碼拉取下來,改成vue3的寫法(沒有做任何邏輯上變動(dòng))
1、進(jìn)去官網(wǎng),把源碼拉取下來,把下面兩個(gè)文件直接放在到自己項(xiàng)目中
我把這兩個(gè)文件放在自己的項(xiàng)目 新建文件 verification 下
2、把源碼的 puzzleVerification.vue文件改寫成vue3寫法(下面代碼復(fù)制過去覆蓋官網(wǎng)文件):
<template> <div class="puzzle-container" v-show="isVerificationShow" > <div class="puzzle-header"> <span class="puzzle-header-left">拖動(dòng)下方滑塊完成拼圖</span> <div> <span class="re-btn iconfont icon-shuaxin" @click="refreshImg"></span> <span class="close-btn iconfont icon-guanbi" @click="closeVerificationBox" ></span> </div> </div> <div :style="'position:relative;overflow:hidden;width:' + dataWidth + 'px;'" > <div :style=" 'position:relative;width:' + dataWidth + 'px;height:' + dataHeight + 'px;' " > <img id="scream" ref="scream" :src="imgRandom" :style="'width:' + dataWidth + 'px;height:' + dataHeight + 'px;'" /> <canvas id="puzzle-box" ref="puzzleBox" :width="dataWidth" :height="dataHeight" ></canvas> </div> <div class="puzzle-lost-box" :style=" 'left:' + left_Num + 'px;width:' + dataWidth + 'px;height:' + dataHeight + 'px;' " > <canvas id="puzzle-shadow" ref="puzzleShadow" :width="dataWidth" :height="dataHeight" ></canvas> <canvas id="puzzle-lost" ref="puzzleLost" :width="dataWidth" :height="dataHeight" ></canvas> </div> <p :class="'ver-tips' + (displayTips ? ' slider-tips' : '')" ref="verTips" > <template v-if="verification"> <i style="background-position: -4px -1207px"></i> <span style="color: #42ca6b">驗(yàn)證通過</span> <span></span> </template> <template v-if="!verification"> <i style="background-position: -4px -1229px"></i> <span style="color: red">驗(yàn)證失敗:</span> <span style="margin-left: 4px">拖動(dòng)滑塊將懸浮圖像正確拼合</span> </template> </p> </div> <div class="slider-container" :style="'width:' + dataWidth + 'px;'"> <div class="slider-bar"></div> <div class="slider-btn" ref="sliderBtn" @mousedown="startMove" @touchstart="startMove" ></div> </div> </div> </template> <script setup lang="ts"> import { ref, defineEmits, defineProps, onBeforeMount, onMounted, watch, nextTick, } from "vue"; const emits = defineEmits(["update:modelValue"]); const props = defineProps({ // 畫布圖片的尺寸 width: { type: [String, Number], default: 260, }, height: { type: [String, Number], default: 120, }, // 圖集 puzzleImgList: { type: Array, default: () => [ require("../assets/thumbnail-img01.jpg"), require("../assets/thumbnail-img02.jpg"), require("../assets/thumbnail-img03.jpg"), ], }, // 滑塊的大小 blockSize: { type: [String, Number], default: 40, }, // 誤差 deviation: { type: [String, Number], default: 4, }, // 滑塊圓角的大?。▋H當(dāng)其形狀是square有效) blockRadius: { type: [String, Number], default: 4, }, // 滑塊隨機(jī)出現(xiàn)的范圍 wraperPadding: { type: [String, Number], default: 20, }, // 滑塊形狀 square puzzle blockType: { type: String, default: "square", }, // 成功的回調(diào) onSuccess: { type: Function, default: () => { console.log("成功"); }, }, // 失敗的回調(diào) onError: { type: Function, default: () => { console.log("失敗"); }, }, verificationShow: { type: Boolean, default: false, }, modelValue: { type: Boolean, default: false }, }); const isVerificationShow = ref(true); const moveStart = ref<any>(""); const displayTips = ref(false); const verification = ref(false); const randomX = ref(0); const randomY = ref(0); const imgRandom = ref<any>(""); const left_Num = ref(0); const dataWidth = ref(0); const dataHeight = ref(0); const puzzleSize = ref(0); // 滑塊的大小 const deviationDate = ref(0); const radiusData = ref(0); const paddingDate = ref(0); const puzzleBox = ref<any>(null); //元素標(biāo)識(shí) const puzzleLost = ref<any>(null); //元素標(biāo)識(shí) const puzzleShadow = ref<any>(null); //元素標(biāo)識(shí) const sliderBtn = ref<any>(null); //元素標(biāo)識(shí) /* 關(guān)閉驗(yàn)證 */ const closeVerificationBox = () => { isVerificationShow.value = false; }; /* 刷新 */ const refreshImg = () => { let imgRandomIndex = Math.round( Math.random() * (props.puzzleImgList.length - 1) ); imgRandom.value = props.puzzleImgList[imgRandomIndex]; initCanvas(); }; /* 畫布初始化 */ const initCanvas = () => { clearCanvas(); let w = dataWidth.value; let h = dataHeight.value; let PL_Size = puzzleSize.value; let padding = paddingDate.value; let MinN_X = padding + PL_Size; let MaxN_X = w - padding - PL_Size - PL_Size / 6; let MaxN_Y = padding; let MinN_Y = h - padding - PL_Size - PL_Size / 6; randomX.value = Math.round(Math.random() * (MaxN_X - PL_Size) + MinN_X); randomY.value = Math.round(Math.random() * MaxN_Y + MinN_Y); let X = randomX.value; let Y = randomY.value; left_Num.value = -X + 10; let d = PL_Size / 3; let radius = Number(radiusData.value); let c = puzzleBox.value; let c_l = puzzleLost.value; let c_s = puzzleShadow.value; let ctx = c.getContext("2d"); let ctx_l = c_l.getContext("2d"); let ctx_s = c_s.getContext("2d"); ctx.globalCompositeOperation = "xor"; ctx.shadowBlur = 10; ctx.shadowColor = "#fff"; ctx.shadowOffsetX = 3; ctx.shadowOffsetY = 3; ctx.fillStyle = "rgba(0,0,0,0.7)"; ctx.beginPath(); ctx.lineWidth = "1"; ctx.strokeStyle = "rgba(0,0,0,0)"; if (props.blockType === "square") { ctx.arc(X + radius, Y + radius, radius, Math.PI, (Math.PI * 3) / 2); ctx.lineTo(PL_Size - radius + X, Y); ctx.arc( PL_Size - radius + X, radius + Y, radius, (Math.PI * 3) / 2, Math.PI * 2 ); ctx.lineTo(PL_Size + X, PL_Size + Y - radius); ctx.arc( PL_Size - radius + X, PL_Size - radius + Y, radius, 0, (Math.PI * 1) / 2 ); ctx.lineTo(radius + X, PL_Size + Y); ctx.arc( radius + X, PL_Size - radius + Y, radius, (Math.PI * 1) / 2, Math.PI ); } else { ctx.moveTo(X, Y); ctx.lineTo(X + d, Y); ctx.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y); ctx.lineTo(X + 3 * d, Y); ctx.lineTo(X + 3 * d, Y + d); ctx.bezierCurveTo( X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d ); ctx.lineTo(X + 3 * d, Y + 3 * d); ctx.lineTo(X, Y + 3 * d); } ctx.closePath(); ctx.stroke(); ctx.fill(); let img = new Image(); img.src = imgRandom.value; img.onload = function () { ctx_l.drawImage(img, 0, 0, w, h); }; ctx_l.beginPath(); ctx_l.strokeStyle = "rgba(0,0,0,0)"; if (props.blockType === "square") { ctx_l.arc(X + radius, Y + radius, radius, Math.PI, (Math.PI * 3) / 2); ctx_l.lineTo(PL_Size - radius + X, Y); ctx_l.arc( PL_Size - radius + X, radius + Y, radius, (Math.PI * 3) / 2, Math.PI * 2 ); ctx_l.lineTo(PL_Size + X, PL_Size + Y - radius); ctx_l.arc( PL_Size - radius + X, PL_Size - radius + Y, radius, 0, (Math.PI * 1) / 2 ); ctx_l.lineTo(radius + X, PL_Size + Y); ctx_l.arc( radius + X, PL_Size - radius + Y, radius, (Math.PI * 1) / 2, Math.PI ); } else { ctx_l.moveTo(X, Y); ctx_l.lineTo(X + d, Y); ctx_l.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y); ctx_l.lineTo(X + 3 * d, Y); ctx_l.lineTo(X + 3 * d, Y + d); ctx_l.bezierCurveTo( X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d ); ctx_l.lineTo(X + 3 * d, Y + 3 * d); ctx_l.lineTo(X, Y + 3 * d); } ctx_l.closePath(); ctx_l.stroke(); ctx_l.shadowBlur = 10; ctx_l.shadowColor = "black"; ctx_l.clip(); ctx_s.beginPath(); ctx_s.lineWidth = "1"; ctx_s.strokeStyle = "rgba(0,0,0,0)"; if (props.blockType === "square") { ctx_s.arc(X + radius, Y + radius, radius, Math.PI, (Math.PI * 3) / 2); ctx_s.lineTo(PL_Size - radius + X, Y); ctx_s.arc( PL_Size - radius + X, radius + Y, radius, (Math.PI * 3) / 2, Math.PI * 2 ); ctx_s.lineTo(PL_Size + X, PL_Size + Y - radius); ctx_s.arc( PL_Size - radius + X, PL_Size - radius + Y, radius, 0, (Math.PI * 1) / 2 ); ctx_s.lineTo(radius + X, PL_Size + Y); ctx_s.arc( radius + X, PL_Size - radius + Y, radius, (Math.PI * 1) / 2, Math.PI ); } else { ctx_s.moveTo(X, Y); ctx_s.lineTo(X + d, Y); ctx_s.bezierCurveTo(X + d, Y - d, X + 2 * d, Y - d, X + 2 * d, Y); ctx_s.lineTo(X + 3 * d, Y); ctx_s.lineTo(X + 3 * d, Y + d); ctx_s.bezierCurveTo( X + 2 * d, Y + d, X + 2 * d, Y + 2 * d, X + 3 * d, Y + 2 * d ); ctx_s.lineTo(X + 3 * d, Y + 3 * d); ctx_s.lineTo(X, Y + 3 * d); } ctx_s.closePath(); ctx_s.stroke(); ctx_s.shadowBlur = 20; ctx_s.shadowColor = "black"; ctx_s.fill(); }; /* 通過重置畫布尺寸清空畫布,這種方式更徹底 */ const clearCanvas = () => { let c = puzzleBox.value; let c_l = puzzleLost.value; let c_s = puzzleShadow.value; c.setAttribute("height", c.getAttribute("height")); c_l.setAttribute("height", c.getAttribute("height")); c_s.setAttribute("height", c.getAttribute("height")); }; /* 按住滑塊后初始化移動(dòng)監(jiān)聽,記錄初始位置 */ const startMove = (e:any) => { // console.log(e); e = e || window.event; sliderBtn.value.style.backgroundPosition = "0 -216px"; moveStart.value = e.pageX || e.targetTouches[0].pageX; addMouseMoveListener(); }; /* 滑塊移動(dòng) */ const moving = (e:any) => { e = e || window.event; let moveX = e.pageX || e.targetTouches[0].pageX; let d = moveX - moveStart.value; let w = dataWidth.value; let PL_Size = puzzleSize.value; let padding = paddingDate.value; if (moveStart.value === "") { return ""; } if (d < 0 || d > w - padding - PL_Size) { return ""; } sliderBtn.value.style.left = d + "px"; sliderBtn.value.style.transition = "inherit"; puzzleLost.value.style.left = d + "px"; puzzleLost.value.style.transition = "inherit"; puzzleShadow.value.style.left = d + "px"; puzzleShadow.value.style.transition = "inherit"; }; /* 移動(dòng)結(jié)束,驗(yàn)證并回調(diào) */ const moveEnd = (e:any) => { e = e || window.event; let moveEnd_X = (e.pageX || e.changedTouches[0].pageX) - moveStart.value; let ver_Num = randomX.value - 10; let deviationValue = deviationDate.value; let Min_left = ver_Num - deviationValue; let Max_left = ver_Num + deviationValue; if (moveStart.value !== "") { if (Max_left > moveEnd_X && moveEnd_X > Min_left) { displayTips.value = true; verification.value = true; setTimeout(function () { displayTips.value = false; initCanvas(); /* 成功的回調(diào)函數(shù) */ props.onSuccess(); }, 500); } else { displayTips.value = true; verification.value = false; setTimeout(function () { displayTips.value = false; initCanvas(); /* 失敗的回調(diào)函數(shù) */ props.onError(); }, 800); } } if ( typeof sliderBtn.value !== "undefined" && typeof puzzleLost.value !== "undefined" && typeof puzzleShadow.value !== "undefined" ) { setTimeout(function () { sliderBtn.value.style.left = 0; sliderBtn.value.style.transition = "left 0.5s"; puzzleLost.value.style.left = 0; puzzleLost.value.style.transition = "left 0.5s"; puzzleShadow.value.style.left = 0; puzzleShadow.value.style.transition = "left 0.5s"; }, 400); sliderBtn.value.style.backgroundPosition = "0 -84px"; } moveStart.value = ""; }; /* 全局綁定滑塊移動(dòng)與滑動(dòng)結(jié)束,移動(dòng)過程中鼠標(biāo)可在頁(yè)面任何位置 */ const addMouseMoveListener = () => { document.addEventListener("mousemove", moving); document.addEventListener("touchmove", moving); document.addEventListener("mouseup", moveEnd); document.addEventListener("touchend", moveEnd); }; watch( //監(jiān)控?cái)?shù)據(jù)變化 () => isVerificationShow.value, (newVal, _d) => { emits("update:modelValue", newVal); }, { immediate: true } ); watch( () => props.modelValue, (val) => { isVerificationShow.value = val; } ) watch( //監(jiān)控?cái)?shù)據(jù)變化 () => props.verificationShow, (newVal, _d) => { isVerificationShow.value = newVal; } ); onBeforeMount(() => { // 隨機(jī)顯示一張圖片 let imgRandomIndex = Math.round( Math.random() * (props.puzzleImgList.length - 1) ); imgRandom.value = props.puzzleImgList[imgRandomIndex]; puzzleSize.value = Number(props.blockSize); deviationDate.value = Number(props.deviation); radiusData.value = Number(props.blockRadius); dataWidth.value = Number(props.width); dataHeight.value = Number(props.height); paddingDate.value = Number(props.wraperPadding) || 20; }); onMounted(() => { nextTick(() => { initCanvas(); }); }); </script> <style scoped> .slider-btn { position: absolute; width: 44px; height: 44px; left: 0; top: -7px; z-index: 12; cursor: pointer; background-image: url(../assets/sprite.3.2.0.png); background-position: 0 -84px; transition: inherit; } .ver-tips { position: absolute; left: 0; bottom: -22px; background: rgba(255, 255, 255, 0.9); height: 22px; line-height: 22px; font-size: 12px; width: 100%; margin: 0; text-align: left; padding: 0 8px; transition: all 0.4s; } .slider-tips { bottom: 0; } .ver-tips i { display: inline-block; width: 22px; height: 22px; vertical-align: top; background-image: url(../assets/sprite.3.2.0.png); background-position: -4px -1229px; } .ver-tips span { display: inline-block; vertical-align: top; line-height: 22px; color: #455; } .active-tips { display: block; } .hidden { display: none; } .puzzle-container { position: relative; display: inline-block; padding: 15px 15px 28px; border: 1px solid #ddd; background: #ffffff; border-radius: 16px; } .puzzle-header { display: flex; justify-content: space-between; margin: 5px 0; } .puzzle-header-left { color: #333; } .re-btn, .close-btn { font-size: 16px; cursor: pointer; color: #666; } .re-btn:hover { color: #67c23a; } .close-btn:hover { color: #f56c6c; } .close-btn { margin-left: 5px; } .slider-container { position: relative; margin: 10px auto 0; min-height: 15px; } .slider-bar { height: 10px; border: 1px solid #c3c3c3; border-radius: 5px; background: #e4e4e4; box-shadow: 0 1px 1px rgba(12, 10, 10, 0.2) inset; position: absolute; width: 100%; top: 7px; } #puzzle-box { position: absolute; left: 0; top: 0; z-index: 22; } #puzzle-shadow { position: absolute; left: 0; top: 0; z-index: 22; } #puzzle-lost { position: absolute; left: 0; top: 0; z-index: 33; } .puzzle-lost-box { position: absolute; width: 260px; height: 116px; left: 0; top: 0; z-index: 111; } @font-face { font-family: "iconfont"; src: url("../assets/icon-font/iconfont.eot?t=1565160368550"); /* IE9 */ src: url("../assets/icon-font/iconfont.eot?t=1565160368550#iefix") format("embedded-opentype"), /* IE6-IE8 */ url("data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAANUAAsAAAAAByQAAAMIAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCfAqCFIIkATYCJAMMCwgABCAFhG0HOhtkBsi+QDw2JxWVhBKVFukepjkT9iz2vr6/fAq8RxAPz4+9O/e9O9WkGj15Fk3LQ/NCJ41QaVASVSzTSJ7E2p//a/4CvASvALwTjbaVddq9o71hlfDegDawAW//1zQFGqAwEaokh6yK8jD/9/c4lxuw3PPkdI3ZegseIJGz/1WKn5AESUiaSApHHzJ2/2uCAT/S0Iz7q0i8eCKrBLgEQJauMPX/uZzeKPH5KSuXuSYd9aI4DiigvbFNViAJeovsrkAs8jyBdmME8ODibgRNyZwWiIdGO9DM+KXkU61Cc8XaFI9p0loe6Q3gNvh+fKdFk6KpMuceX5/7cPTJ/Tfy4spDEIxnBb+JihVAEueV9kOVKL4Ca1dZq6YAKXljibz/PCFRiGZmcSdYIInCJw6Toid+iM6+oYJmd5A8BW4pefDQFKWzc2SkXpr+P1LN9+L/JeF4UsZPUGWa/nUPMypPfme7Do2j785+zvL4Z37s1l/wcAEwhNPJqPMcHgerCdZ/wbrLlYRrg2tSMZ45fpHJLQ7humLaL9WW9r64yBa+7D3UcBOs/3jDHKz7+MTzgPkExVfzZnHa3qveHHJTfVjsFjh5X99QxlP8cdhYHOyY+Fmy4BUXoLG2ngiSbe2xv/AbW/8ptNb3/tiQ4MN+w2saBeoEoFUBLHjnAmBTaiLTUFPhO3zDUa2nvAqYK7g0MN39vv3VQzeXDK2GEihajELVagZN8go06bAKzVptQ7tlzuYOAyoqItuwZIgg9LpC0e05VL1eaJI/0GTYD5r1RgXaXcb2nh3mwiydMjlCPrpPaLwkVrZJlsLSAekidDgtCigT4tyEsCcp+dQlxcRjLMjvdV9EoeIkwgt0GYVhgiknc/KkPRNJdzpyXPWmtpdEsGQfIw5BfMj3BGU8iZjyOoulwucHiFYIObiBUGWfICxnekcqiQJAL+UxiHAt11Td0/pqhIJiLBFBNrKS0IonUKl61BzxiLa0RzS1QyatYqi8Pb8yer5d0M7co0aJGqn5TuHErmnksyD2aGIA") format("woff2"), url("../assets/icon-font/iconfont.woff?t=1565160368550") format("woff"), url("../assets/icon-font/iconfont.ttf?t=1565160368550") format("truetype"), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */ url("../assets/icon-font/iconfont.svg?t=1565160368550#iconfont") format("svg"); /* iOS 4.1- */ } .iconfont { font-family: "iconfont" !important; font-size: 16px; font-style: normal; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-guanbi:before { content: "\f01f1"; } .icon-shuaxin:before { content: "\e609"; } </style>
3、在需要使用的地方直接使用(具體參數(shù)參考官網(wǎng)):
<template> <PuzzleVerification v-model="isVerificationShow1" :onSuccess="handleSuccess" :onError="handleError" blockType="puzzle" /> </template> <script setup lang="ts"> import { ref, onMounted, watch } from "vue"; import PuzzleVerification from "@/verification/components/puzzleVerification.vue"; const isVerificationShow1 = ref(true); const handleSuccess = () => { console.log("成功"); isVerificationShow1.value = true; }; const handleError = () => { console.log("handleError"); isVerificationShow1.value = true; }; </script> <style scoped></style>
到此這篇關(guān)于在vue3中使用滑塊檢驗(yàn)vue-puzzle-verification的文章就介紹到這了,更多相關(guān)vue3滑塊檢驗(yàn)vue-puzzle-verification內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue保持用戶登錄狀態(tài)(各種token存儲(chǔ)方式)
本文主要介紹了Vue保持用戶登錄狀態(tài)(各種token存儲(chǔ)方式),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09vue中keep-alive組件實(shí)現(xiàn)多級(jí)嵌套路由的緩存
本文主要介紹了vue中keep-alive組件實(shí)現(xiàn)多級(jí)嵌套路由的緩存,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03vue播放flv、m3u8視頻流(監(jiān)控)的方法實(shí)例
隨著前端大屏頁(yè)面的逐漸壯大,客戶的...其中實(shí)時(shí)播放監(jiān)控的需求逐步增加,視頻流格式也是有很多種,用到最多的.flv、.m3u8,下面這篇文章主要給大家介紹了關(guān)于vue播放flv、m3u8視頻流(監(jiān)控)的相關(guān)資料,需要的朋友可以參考下2023-04-04Vue實(shí)現(xiàn)在線預(yù)覽pdf文件功能(利用pdf.js/iframe/embed)
項(xiàng)目要求需要預(yù)覽pdf文件,網(wǎng)上找了很久,發(fā)現(xiàn)pdf.js的效果,這篇文章主要給大家介紹了關(guān)于Vue實(shí)現(xiàn)在線預(yù)覽pdf文件功能,主要利用pdf.js/iframe/embed來實(shí)現(xiàn)的,需要的朋友可以參考下2021-06-06vue遞歸組件實(shí)戰(zhàn)之簡(jiǎn)單樹形控件實(shí)例代碼
這篇文章主要介紹了vue遞歸組件實(shí)戰(zhàn)之簡(jiǎn)單樹形控件的實(shí)例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08Pure admin-Router標(biāo)簽頁(yè)配置與頁(yè)面持久化實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Pure admin-Router標(biāo)簽頁(yè)配置與頁(yè)面持久化實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2023-01-01