captcha.js一個生成驗證碼的外掛,使用js和canvas生成

saucxs發表於2019-03-08

一、前言

captcha.js是一個生成驗證碼的外掛,使用js和canvas生成的,確保後端服務被暴力攻擊,簡單判斷人機以及系統的安全性,體積小,功能多,支援配置。驗證碼外掛內容,包含1、驗證碼外掛-使用,2、驗證碼外掛栗子,3、API介紹,4、支援瀏覽器。

外掛原始碼地址:github.com/saucxs/capt…

文章首發地址:www.mwcxs.top/page/630.ht…


二、功能

+ 版本v 1.0.0

- 1、支援隨機字元內容配置,字元大小配置,字元型別配置,字元繪製方式配置,字元長度配置等

- 2、支援點位置隨機,數量配置,點半徑的配置

- 3、支援線條位置隨機,寬度配置,線條數量的配置

- 4、支援隨機前景色配置,區間值[0, 255],可以使用預設值

- 5、支援隨機背景色配置,區間值[0, 255],可以使用預設值

- 6、支援點選更新檢視

- 7、支援瀏覽器谷歌瀏覽器,火狐瀏覽器,Safari,IE10+等


三、驗證碼外掛使用

不依賴與其他的外掛,實現起來很容易,`captcha.js`是必須要引進的元件。

3.1 本地引入封裝的js檔案

第一步:獲取元件方式:`git clone https://github.com/saucxs/captcha.git`

第二步:clone後,在需要加驗證碼的相關頁面引入驗證碼檔案"captcha.js"以及準備好裝驗證碼容器:

引入captcha內容:

<script type="text/javascript" src="./captcha.js"></script>複製程式碼

寫入驗證碼容器:

<canvas width="240" height="90" id="captcha1"></canvas>複製程式碼

第三步:在確保頁面dom載入完畢之後,呼叫captcha的方法(手動載入):

/*不傳值,統一走預設值*/
    let captcha1 = new Captcha();
    captcha1.draw(document.querySelector('#captcha1'), r => {
        console.log(r, '驗證碼1');
    });複製程式碼
let captcha2 = new Captcha({
        lineWidth: 1,   //線條寬度
        lineNum: 6,       //線條數量
        dotR: 2,          //點的半徑
        dotNum: 25,       //點的數量
        preGroundColor: [10, 80],    //前景色區間
        backGroundColor: [150, 250], //背景色區間
        fontSize: 30,           //字型大小
        fontFamily: ['Georgia', '微軟雅黑', 'Helvetica', 'Arial'],  //字型型別
        fontStyle: 'stroke',      //字型繪製方法,有fill和stroke
        content: '一個驗證碼abcdefghijklmnopqrstuvw生成的外掛使用的是canvas顯示',  //驗證碼內容
        length: 6    //驗證碼長度
    }); 
    captcha2.draw(document.querySelector('#captcha2'), r => {
        console.log(r, '驗證碼2');
    });複製程式碼

使用外掛的效果地址1:www.mwcxs.top/static/test…


3.2 npm包引入

第一步:npm獲取驗證碼元件

npm install captcha-mini複製程式碼

第二步:引入驗證碼模組

import Captcha from 'captcha-mini'
或者
var Captcha = require("captcha-mini")複製程式碼

第三步:在確保dom載入完畢之後,呼叫captcha的draw方法(手動載入)

/*不傳值,統一走預設值*/
    let captcha1 = new Captcha();
    captcha1.draw(document.querySelector('#captcha1'), r => {
        console.log(r, '驗證碼1');
    });複製程式碼
let captcha2 = new Captcha({
        lineWidth: 1,   //線條寬度
        lineNum: 6,       //線條數量
        dotR: 2,          //點的半徑
        dotNum: 25,       //點的數量
        preGroundColor: [10, 80],    //前景色區間
        backGroundColor: [150, 250], //背景色區間
        fontSize: 30,           //字型大小
        fontFamily: ['Georgia', '微軟雅黑', 'Helvetica', 'Arial'],  //字型型別
        fontStyle: 'stroke',      //字型繪製方法,有fill和stroke
        content: '一個驗證碼abcdefghijklmnopqrstuvw生成的外掛使用的是canvas顯示',  //驗證碼內容
        length: 6    //驗證碼長度
    }); 
    captcha2.draw(document.querySelector('#captcha2'), r => {
        console.log(r, '驗證碼2');
    });複製程式碼


四、原理

1、思路

現在我們需要一個物件,然後呼叫物件的某個方法可以將驗證碼畫出來。所以我們需要一個建構函式,用來例項化物件。

function Regcode() {}複製程式碼

建構函式接受一些引數,用來定製驗證碼的點、線、字的各種屬性(顏色、長短、大小等)。

 function Regcode(params = {}) {
        let p = Object.assign({...}, params); // 這裡有定義好的屬性和預設值
        Object.keys(p).forEach(k => { // 將所有屬性組合後新增到this上
            this[k] = p[k];
        });
    }複製程式碼


2、draw方法

首先我們需要一個 draw 方法,作為驗證碼的繪製方法。

draw 方法接收兩個引數,canvas 的 dom 物件,用來建立繪圖的2d物件。還需要一個回撥函式 callback,用來接收每次繪製的文字。

我們把 draw 方法放在Regcode的原型上,這樣所有的例項物件都可以繼承這些方法,而不是自己獨立有一套。

在 draw 方法中,可以想到的是,我們需要建立 canvas 的 2d物件,建立畫布,然後開始依次繪製點、線、文字。

Regcode.prototype.draw = function(dom, callback = function () {}) { // 繪圖
        // 獲取canvas dom
        if (!this.paint) { // 如果沒有2d物件,再進行賦值操作
            this.canvas = dom; // 儲存到this指標,方便使用
            if (!this.canvas) return;
            this.paint = this.canvas.getContext('2d'); // 儲存到this指標,方便使用
            if (!this.paint) return;
            
            // 回撥函式賦值給this,方便使用
            this.callback = callback;
        }
        // 隨機畫布顏色,使用背景色
        let colors = this.getColor(this.backgroundColor);
        this.paint.fillStyle = `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, 0.8)`;
        // 繪製畫布
        this.paint.fillRect(0, 0, this.canvas.width, this.canvas.height);
        // 繪圖
        this.arc();
        this.line();
        this.font();
    };複製程式碼

需要簡單判斷一下是否有 dom 物件和2d物件,其實應該判斷引數是否為 dom 物件,可以通過判斷節點型別或者通過 dom instanceof HTMLElement(谷歌和火狐支援)來判斷。但是這裡因為要求不高,所以只是簡單判斷。


3、隨機顏色

需要注意的是,在建立畫布的時候,我們使用了獲取背景色的一個方法。在之前的需求中我們可以看到,最高頻的兩個詞是隨機和顏色,所以肯定是需要將這兩個方法單獨封裝的。

隨機顏色這裡採用的是 rgb 的強度值(0 ~ 255, 由暗 -> 亮),需要指定兩個顏色區間:前景色(文字、線條)和背景色(畫布背景)。因為需要將文字和背景顏色區分,避免色值太接近無法識別,所以預設前景色區間 [10, 80],背景色區間 [150, 250]。

Regcode.prototype.getColor = function(arr) { // 隨機獲取顏色
        let colors = new Array(3).fill(''); // 建立一個長度為3的陣列,值都填充為 ''
        colors = colors.map(v => this.getRand(...arr)); // 每個成員隨機獲取一個強度值重組為新陣列
        return colors;
    };複製程式碼

因為 rgb 顏色通常表示為 rgba(0,0,0,0.8),最後一位是透明度,這裡沒有參加隨機。所以只考慮前3個數,在指定的強度區間內,只需要依次隨機出3個數就好。所以在上面的方法中,還需要做的就是隨機在一個數值區間中取值。

Regcode.prototype.getRand = function(...arr) { // 獲取某個區間的隨機數
        arr.sort((a, b) => a - b); // 將傳入的引數從小到大排序
        return Math.floor(Math.random() * (arr[1] - arr[0]) + arr[0]);
    };複製程式碼


4、繪製線條

有了隨機顏色,繪製線條就方便多了。lineNum 用於指定繪製幾條線,預設為2條。之前說過前景色(foregroundColor) 和 背景色 (backgroundColor)也是可以傳參的,文字、線條、點都使用前景色。在繪製線條的時候,還需要計算出線條的隨機起止座標,在這裡 canvas 的寬高範圍內都允許,這樣就可以做到隨機長度。

Regcode.prototype.line = function() { // 繪製線條
        for (let i = 0; i < this.lineNum; i++) {
            // 隨機獲取線條的起止座標
            let x = this.getRand(0, this.canvas.width), y = this.getRand(0, this.canvas.height),
                endx = this.getRand(0, this.canvas.width), endy = this.getRand(0, this.canvas.width);
            this.paint.beginPath(); // 開始繪製
            this.paint.lineWidth = this.lineWidth;
            // 隨機獲取路徑顏色
            let colors = this.getColor(this.foregroundColor); // 使用前景色
            this.paint.strokeStyle = `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, 0.8)`;
            // 指定繪製路徑
            this.paint.moveTo(x, y);
            this.paint.lineTo(endx, endy);
            this.paint.closePath();
            this.paint.stroke(); // 進行繪製
        }
    };複製程式碼


5、繪製圓點

繪製圓點要注意的是需要隨機獲取圓心的位置,即分別隨機獲取在寬高範圍內的 (x, y) 座標。dotNum 是允許傳入的需要繪製圓點的個數,預設為10,dotR 是半徑,預設為 1。

Regcode.prototype.arc = function() { // 繪製圓點
        for (let i = 0; i < this.dotNum; i++) {
            // 隨機獲取圓心
            let x = this.getRand(0, this.canvas.width), y = this.getRand(0, this.canvas.height);
            this.paint.beginPath();
    
            // 指定圓周路徑
            this.paint.arc(x, y, this.dotR, 0, Math.PI * 2, false);
            this.paint.closePath();
    
            // 隨機獲取路徑顏色
            let colors = this.getColor(this.foregroundColor);
            this.paint.fillStyle = `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, 0.8)`;
    
            // 繪製
            this.paint.fill();
        }
    };複製程式碼


6、繪製文字

繪製文字稍微麻煩一些,需要先從定義好的驗證碼因子(允許通過 content 引數自定義,預設為 acdefhijkmnpwxyABCDEFGHJKMNPQWXY12345789,這裡去掉了類似於字母 b 和 數字 6 這樣的容易混淆的字元。)中,隨機獲取指定長度(允許通過引數自定義)的驗證碼。

Regcode.prototype.getText = function() { // 隨機獲取驗證碼
        let len = this.content.length, str = '';
        for (let i = 0; i < this.len; i++) { // 隨機獲取每個因子,組成驗證碼
            str += this.content[this.getRand(0, len)];
        }
        return str;
    };複製程式碼

繪製文字的時候,注意以下幾點:

1、需要通過回撥函式將當前繪製的文字輸出。

2、需要指定文字的旋轉角度、字型型別、文字顏色、繪製風格(填充或者不填充)。

3、需要獲得文字的實際寬度,用來確定單個文字的活動範圍。

Regcode.prototype.font = function() { // 繪製文字
        let str = this.getText(); // 獲取驗證碼
        this.callback(str); // 利用回撥函式輸出文字,用於與使用者輸入驗證碼進行比對
        // 指定文字風格
        this.paint.font = `${this.fontSize}px ${this.fontFamily}`;
        this.paint.textBaseline = 'middle'; // 設定文字基線,middle是整個文字所佔方框的高度的正中。
        // 指定文字繪製風格
        let fontStyle = `${this.fontStyle}Text`;
        let colorStyle = `${this.fontStyle}Style`;
        for (let i = 0; i < this.len; i++) { // 迴圈繪製每個字
            let fw = this.paint.measureText(str[i]).width; // 獲取文字繪製的實際寬度
            // 獲取每個字的允許範圍,用來確定繪製單個文字的橫座標
            let x = this.getRand(this.canvas.width / this.len * i, (this.canvas.width / this.len) * i + fw/2);
            // 隨機獲取字型的旋轉角度
            let deg = this.getRand(-6, 6);
            // 隨機獲取文字顏色
            let colors = this.getColor(this.foregroundColor);
            this.paint[colorStyle] = `rgba(${colors[0]}, ${colors[1]}, ${colors[2]}, 0.8)`;
            // 開始繪製
            this.paint.save();
            this.paint.rotate(deg * Math.PI / 180);
            this.paint[fontStyle](str[i], x, this.canvas.height / 2);
            this.paint.restore();
        }
    };複製程式碼

基本上就完成了。


五、支援瀏覽器

Chrome,FireFox,Safari,IE9及以上瀏覽器


六、其他

1、歡迎使用[watermark-dom](github.com/saucxs/wate…)外掛,功能:給B/S網站系統加一個很淺的dom水印外掛。
2、歡迎使用[captcha-mini](github.com/saucxs/capt…)外掛,功能:生成驗證碼的外掛,使用js和canvas生成的。
3、歡迎使用[watermark-image](github.com/saucxs/wate…)外掛,目前功能:圖片打馬賽克。



相關文章