Canvas系列-文字點選驗證碼

前言

?前面的文章寫過使用Canvas如何實現滑動驗證碼、隨機字符串驗證碼、計算驗證碼等,今天我們看看如何實現一個文字點選驗證碼。

?之前的文章有寫過Canvas系列小功能文章,有興趣的可以瞧上一瞧~
?Canvas系列-下雪特效
?Canvas系列-簽字功能
?Canvas系列-滑動驗證
?Canvas系列-字符驗證
?Canvas系列-計算驗證

實現隨機字符串驗證功能

?效果圖如下,源碼鏈接

canvas點選驗證演示.gif

基本思路

1、繪制背景,根據傳入的pops中的images列表屬性隨機選取一張圖片

2、根據傳入的fontStr字符串隨機生成規定長度的字符數組,并保存下來

3、在canvas畫布中繪制隨機生成的字符,并且隨機抽取文字組成新的數組,并且保存下來用于提示文字和驗證

4、點擊Canvas畫布獲取點選位置數據并用數組保存下來,然后根據定位渲染點選圓形

5、根據點選保存的信息和提示文字數組,按序判斷點選的圓形邊界是否在精度范圍內,如果都邊界判斷都為真則表示驗證通過

6、重置功能則清空所有保存的數據并從第1步重新開始

具體實現代碼

// HTML
<div id="app" v-cloak>
  <div class="verify-container" :style="{width: `${width}px`}">
    <div class="refresh" @click="reset">
      <svg t="1637315258145" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2420" width="20" height="20"><path d="M960 416V192l-73.056 73.056a447.712 447.712 0 0 0-373.6-201.088C265.92 63.968 65.312 264.544 65.312 512S265.92 960.032 513.344 960.032a448.064 448.064 0 0 0 415.232-279.488 38.368 38.368 0 1 0-71.136-28.896 371.36 371.36 0 0 1-344.096 231.584C308.32 883.232 142.112 717.024 142.112 512S308.32 140.768 513.344 140.768c132.448 0 251.936 70.08 318.016 179.84L736 416h224z" p-id="2421" fill="#8a8a8a"></path></svg>
    </div>
    <div class="pic">
      <canvas class="canvas" ref="canvas" :width="width" :height="height" @click="createPointer"></canvas>
      <span class="pointer"
        v-for="(item, index) in pointer"
        :style="{left: `${item.x}px`, top: `${item.y}px`}">
        <i>{{ index + 1 }}</i>
      </span>
    </div>
    <div :class="['toolbar', state]">
      <p v-if="state==='fail'">驗證失敗</p>
      <p v-else-if="state==='success'">驗證通過</p>
      <p v-else>請順序點擊【<span v-for="(item, index) in tips" :key="index">{{ item.character }}</span>】</p>
    </div>
  </div>
  <br>
</div>
// JS
const App = {
  props: {
    width: {
      type: Number,
      default: 320
    },
    height: {
      type: Number,
      default: 160
    },
    fontStr: {
      type: String,
      default: '趙錢孫李周吳鄭王朱秦尤許何呂施張孔曹嚴華金魏陶姜戚謝鄒喻柏水竇章云蘇潘葛奚范彭郎魯韋昌馬苗鳳花方俞任袁柳酆鮑史唐'
    },
    fontNum: {  // 顯示幾個
      type: Number,
      default: 5
    },
    checkNum: {  // 點擊驗證數
      type: Number,
      default: 3
    },
    accuracy: {  // 精度
      type: Number,
      default: 15
    },
    images: {
      type: Array,
      default: [
        'https://img2.baidu.com/it/u=172118635,3843440198&fm=26&fmt=auto',
        'https://img2.baidu.com/it/u=2726247805,538885610&fm=26&fmt=auto',
        'https://img1.baidu.com/it/u=1078976348,1462740125&fm=26&fmt=auto'
      ]
    }
  },
  data() {
    return {
      bgImg: null,  // 背景圖
      ctx: null, // 背景畫筆
      fontArr: [], // 顯示的字符
      tips: [], // 提示文字
      pointer: [], // 點擊序號
      state: '' , // success fail active
      timeIns: null,
    }
  },
  mounted () {
    this.init()
  },
  beforeDestroy () {
    clearTimeout(this.timeIns)
  },
  methods: {
    init () {
      this.ctx = this.$refs['canvas'].getContext('2d');

      this.getImg();
    },
    getImg () {
      const img = document.createElement('img');
      const imagesLen = this.images.length;
      const randomIndex = Math.floor(Math.random() * imagesLen);
      img.crossOrigin = "Anonymous"; 
      img.src = this.images[randomIndex];
      this.bgImg = img;

      img.onload = () => {
        console.log('圖片加載完成')
        this.draw();
      }
      console.log(this.bgImg)
    },
    draw () {
      // 繪制背景圖
      this.ctx.drawImage(this.bgImg, 0, 0, this.width, this.height);


      for (let i = 0; i < this.fontNum; i++) {
        const character = this.getRandomCharacter();
        console.log(character)
        const fontSize = this.randomNum(20, this.height * 1 / 4);
        const fontWeight = Math.random() > 0.5 ? 'bold' : 'normal';
        const fontStyle = Math.random() > 0.5 ? 'italic' : 'normal';
        const fontFamily = Math.random() > 0.5 ? 'sans-serif' : 'serif'
        const x = this.width / this.fontNum * i + 10;
        const y = Math.random() * (this.height - fontSize);

        this.ctx.fillStyle = this.randomColor(0, 255);
        this.ctx.font = `${fontStyle} ${fontWeight} ${fontSize}px ${fontFamily}`;
        this.ctx.textBaseline = 'top';
        this.ctx.fillText(character, x, y);

        this.fontArr.push({
          character,
          // fontSize,
          x,
          y
        })
      }
      console.log(this.fontArr)

      for (let i = 0; i < this.checkNum; i++) {
        const randomIndex = Math.floor(Math.random() * this.fontArr.length);
        const character = this.fontArr.splice([randomIndex], 1)[0];
        this.tips.push(character);
        // console.log(character, this.fontArr)
      }
      console.log(this.tips)
    },
    // 獲取隨機字符
    getRandomCharacter () {
      const fontStrLen = this.fontStr.length;
      const randomIndex = Math.floor(Math.random() * fontStrLen);
      const character = this.fontStr.charAt([randomIndex]);

      // debugger
      const isSome = this.fontArr.some(item => {
        return item.character === character;
      })
      if (isSome) {
        console.log(`>>>${character}已存在>>>`)
        return this.getRandomCharacter();
      } else {
        return character;
      }
    },
    randomColor (min, max) {
      let r = this.randomNum(min, max)
      let g = this.randomNum(min, max)
      let b = this.randomNum(min, max)
      return 'rgb(' + r + ',' + g + ',' + b + ')'
    },
    randomNum (min, max) {
      return Math.floor(Math.random() * (max - min) + min)
    },
    createPointer (e) {
      // console.log(e)
      const canvasRect = this.$refs.canvas.getBoundingClientRect();
      const x = e.offsetX - 15;
      const y = e.offsetY - 15;

      if (this.pointer.length < this.tips.length) {
        this.pointer.push({x, y});
        // console.log(this.pointer.length)
        // this.verify()
        this.state = 'active'
      }
      if (this.pointer.length === this.tips.length) {
        const isPass = this.verify();
        if (isPass) {
          this.state = 'success';
        } else {
          this.state = 'fail';
          // 如果失敗則1000毫秒后重置
          this.timeIns = setTimeout(() => {
              this.reset()
          }, 1000)
        }
      }
    },
    // 判斷精度
    verify () {
      console.log("驗證")
      const result = this.pointer.every((item, index) => {
        const _left = item.x > this.tips[index].x - this.accuracy;
        const _right = item.x < this.tips[index].x + this.accuracy;
        const _top = item.y > this.tips[index].y - this.accuracy;
        const _bottom = item.y < this.tips[index].y + this.accuracy;
        return _left && _right && _top && _bottom;
      })
      console.log(result)
      return result;
    },
    // 重置
    reset () {
      this.fontArr = [];
      this.tips = [];
      this.pointer = [];
      this.state = '';
      this.ctx.clearRect(0, 0, this.width, this.height);
      this.getImg();
    }
  }
}
Vue.createApp(App).mount('#app');

解析:上面就是布局和所有js代碼

結尾

上面就是【文字點選驗證碼】的實現原理,代碼是使用vue編寫的小demo,可能存在一些兼容性問題,也沒有封裝成組件,但是具有一些參考意義,用于生產可以自己去封裝成組件使用,完整的代碼在我的GitHub倉庫


本文是筆者總結編撰,如有偏頗,歡迎留言指正,若您覺得本文對你有用,不妨點個贊~

關于作者:
GitHub
簡書
掘金

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容