H5 Canvas 簽名板

簽名板介紹

在最近的一個項目中,最后的一個功能是實現(xiàn)一個簽名板供客戶簽名使用。這需要用到canvas來實現(xiàn)。

我將這個功能分成實現(xiàn)和優(yōu)化兩個階段。首先來看實現(xiàn)

實現(xiàn)

對于一個canvas簽名板,首先我們要完成最最最基本的功能,簽名!

步驟為下:

1、提供畫布:

通過canvas的API,很容易定義出一塊畫布。

2、定義畫筆

實際情況上是沒有畫筆這個東西的,所以需要自己來假定一個畫筆。

畫筆具有三個狀態(tài),準(zhǔn)備畫,畫,畫完了。用一個flag和三個方法來表示這一過程:

mousePress:false  // 這個flag表示是否開始畫了,準(zhǔn)備畫的時候這個標(biāo)志位置為true,畫完置為false。
beginDraw (e) { ... } // 對應(yīng)準(zhǔn)備畫的方法
drawing (e) {...} // 對應(yīng)畫的方法
endDraw (e) {...} // 對應(yīng)畫完的方法 。。。 這三個方法的實現(xiàn)下面會具體講,這里假定已經(jīng)實現(xiàn)了。
3、實現(xiàn)簽名

現(xiàn)在距離實現(xiàn)簽名就差一步,那就是記錄下鼠標(biāo)每次經(jīng)過的位置并用ctx.stroke()方法將它畫出來即可。
這里需要知道:畫過的路徑不會變,將要畫的路徑不知道,正在畫的地方能獲取。所以定義一個叫做last的對象來記錄上一次鼠標(biāo)畫過的點的位置并把它畫到現(xiàn)在的位置,以此類推,就可以畫出一條鼠標(biāo)經(jīng)過的線路了?,F(xiàn)在來具體實現(xiàn)畫畫這一過程:

a、beginDraw

  beginDraw(e) {
        mousePress = true;
    },  // 沒什么好說的  

b、endDraw

  endDraw  (e) {
        e.preventDefault();
        mousePress = false;
        last = null;
  } // 也沒什么好說的,畫完了標(biāo)志位和記錄對象都該還原了。

c、drawing

  drawing (e) {
        e.preventDefault();
        if (!this.mousePress) {
            return; // 這個雖然不會觸發(fā),但是好像需要這個邏輯也就寫上去了
        }
        var xy = this.getCoordinate(e);  // 這個方法是獲取當(dāng)前的鼠標(biāo)的位置并將它賦值給一個叫做xy的對象
        if (this.last != null) {
            this.context.beginPath();
            this.context.moveTo(this.last.x, this.last.y);
            this.context.lineTo(xy.x, xy.y);
            this.context.stroke();
        }
        // 開始移動,將坐標(biāo)賦值給last。那么下次再移動就會通過上面的操作從上一個xy移動到當(dāng)前的xy處
        this.last = xy;
  }

d、getCoordinate 獲取當(dāng)前鼠標(biāo)位置

getCoordinate(e) {
        var x, y;
        x = e.offsetX + e.target.offsetLeft;
        y = e.offsetY + e.target.offsetTo; // 獲取當(dāng)前x,y坐標(biāo)
        return {
           x,y
        } //es6語法多簡潔
    },
4、事件綁定

接下來只需要在canvas上綁定事件去對應(yīng)準(zhǔn)備畫,畫和結(jié)束畫三個方法就可以了。

canvas.onmousedown = beginDraw; // 鼠標(biāo)按下事件
canvas.onmouseup = endDraw; // 鼠標(biāo)松開的事件
canvas.onmousemove = drawing; // 鼠標(biāo)移動事件

好了,如果你稍微了解一些canvas的基礎(chǔ),補(bǔ)全這些代碼,現(xiàn)在你的簽名板就實現(xiàn)了。

優(yōu)化

簽名板,在電腦上簽名,用鼠標(biāo)簽名,怎么說都顯得怪異。

優(yōu)化1、移動端的簽名:

首先,把移動端的事件綁定到對應(yīng)的三個方法:

 canvas.addEventListener('touchstart',beginDraw,false)
canvas.addEventListener('touchmove',drawing,false)
canvas.addEventListener('touchend',endDraw,false)

然后,在移動端試了試,不行。那是因為在getCoordinate 方法中獲取的當(dāng)前位置有問題,在移動端,要用另一種方法來獲取。為了區(qū)分移動端和PC,需要用一個控制語句來區(qū)分它們。如下

getCoordinate(e) {
        var x, y;
        if (this.isTouch(e)) {  // 判斷當(dāng)前事件是移動端事件還是PC端事件
            x = e.touches[0].pageX; 
            y = e.touches[0].pageY;
        }
        else {
            x = e.offsetX + e.target.offsetLeft;
            y = e.offsetY + e.target.offsetTop;
        }
        return {
            x,y
        }
    },

  isTouch(e) { // 判斷當(dāng)前事件是移動端事件還是PC端事件
        var type = e.type;
        if (type.indexOf('touch') >= 0) {
            return true;
        } else {
            return false;
        }
    },

OK,現(xiàn)在簽名板以及可以兼容PC端和移動端兩個部分了。

優(yōu)化2、個性化簽名

當(dāng)然,這個名還是要自己簽的,不過可以自己設(shè)定簽名的線條的粗細(xì),顏色等。這些樣式設(shè)定放在beginDraw這個方法處即可。具體實現(xiàn)就不說了。

優(yōu)化3、用Vue來實現(xiàn)。

用Vue的話,更方便地控制這個項目中需要用的變量等。具體改變就是把上述需要定義的變量放在vue的data中,方法房子啊methods中。綁定事件在canvas處,如圖:

canvas.png

這樣也是非常的方便。

優(yōu)化4、移動端橫豎屏畫布大小調(diào)整

在PC端的話,設(shè)定固定大小的畫布即可。但對于移動端來說,畫布的寬度需要撐滿屏幕才勉強(qiáng)可以做到簽名。這時候,需要實現(xiàn)將手機(jī)變成橫屏?xí)r,監(jiān)聽手機(jī)的變化,改變畫布的大小。這里,初始化畫布寬度為手機(jī)屏幕的寬度,高度為width*380/750。

這時候,需要用到window.onorientationchange來監(jiān)聽橫豎屏的轉(zhuǎn)變和window.orientation來獲取橫豎屏轉(zhuǎn)變的參數(shù),如圖所示:

監(jiān)聽橫豎屏.png
獲取參數(shù).png

0表示正常,90和-90表示橫屏,180就是把手機(jī)倒過來。然后把orientationChange方法下面開始方案。

方案1、獲取手機(jī)屏幕大小,直接進(jìn)行改變(寬變高,高變寬)。
這是我最初的實現(xiàn)方案,這種直接改變畫布寬和高是銷毀了原來的畫布重新生成了一張畫布。確實達(dá)到了橫屏簽名的效果,但這卻存在很大缺陷,那就是橫豎屏轉(zhuǎn)變重置畫布會消除畫布原有的內(nèi)容。在實際場景中,有些客戶如果想要橫屏簽字,豎屏再查看一下自己寫的什么樣子,就不滿足于客戶需求了。

方案2、由于橫豎屏轉(zhuǎn)變,畫布必然會被重置,所以就不需要考慮如何讓原有的簽名內(nèi)容不變保留過來了。解決方案就是在每次橫豎屏轉(zhuǎn)變的時候,獲取原有簽名板上內(nèi)容數(shù)據(jù),在橫豎屏轉(zhuǎn)變之后,將數(shù)據(jù)放入到新生成的畫布中。這里,我將畫布重置和畫布內(nèi)容導(dǎo)入封裝到了一個叫做resize的方法中,代碼如下:

  /**
     * 重置畫布操作
     * @param {*} w 畫布寬 
     * @param {*} h 畫布高
     * @param {*} flag 橫豎屏標(biāo)志
     */  
    resize(w, h, flag) {
        var that = this;
        var nc = document.createElement("canvas"); // nc用來保留原有畫布數(shù)據(jù)
        nc.width = that.canvas.width;
        nc.height = that.canvas.height;
        nc.getContext("2d").drawImage(that.canvas, 0, 0); // 將原有畫布數(shù)據(jù)放到同樣大小的nc畫布中,就像是copy了一份
        this.canvas.width = w; 
        this.canvas.height = h; //畫布大小重置
        // 橫轉(zhuǎn)豎,原有數(shù)據(jù)放入到新的畫布中。等比放置簽名數(shù)據(jù)。
        if (flag) {
            // crossWidth是我設(shè)置的變量,表示橫屏情況下,屏幕的寬度,verticalWidth表示豎屏情況下屏幕的寬度。
            this.context.drawImage(nc, 0, 0, this.crossWidth, this.crossHeight, 0, 0, w, h); 
        }
        // 豎轉(zhuǎn)橫
        else {
            this.context.drawImage(nc, 0, 0, this.verticalWidth, this.verticalHeight, 0, 0, w, h); 
        }
    },
優(yōu)化5、高清屏適配。

通常對于canvas來說,我們會在html中設(shè)置

 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=no">

這樣一串東西來規(guī)定這個頁面不能縮放,縮放比為1,這樣我們在開發(fā)canvas的時候就不會因為不同手機(jī)的dpr不同導(dǎo)致畫布呈現(xiàn)的有大有小,很不合理。當(dāng)然,設(shè)置這樣的一個約定不但不會影響簽名板的視覺呈現(xiàn)(畢竟名字線條單一沒那么復(fù)雜),也會很方便整個流程的開發(fā)。可是還是想讓IOS的高清屏可以顯示的更加清晰。

方案1、

由于項目采用的是手淘的flexible布局,這里,我們把上面的那個meta標(biāo)簽注釋掉,如果你畫布的寬度是這樣定義的:

this.verticalWidth = screen.width。// 因為我會將這個verticalWidth賦值給canvas.width,見resize()方法。

如果你的是安卓機(jī),那么畫布沒有變化,因為flexible默認(rèn)設(shè)置的安卓dpr = 1,也就是initial-scale=1/dpr=1,和上面meta標(biāo)簽的配置是一樣的。所以不會有變化。

如果你的是IPhone,那么你會發(fā)現(xiàn)你的畫布大小變小了。這是因為initial-scale不再是1了,而是1/dpr。這時候,我們只需要在設(shè)置屏幕寬度的時候再乘上當(dāng)前iPhone手機(jī)的dpr即可還原畫布大小。

this.verticalWidth = screen.width*dpr  // dpr需要自己獲取

下面曬圖,采用高清的IPhone的3像素的簽名和不采用高清適配的簽名對比

高清.png
非高清.png

好吧,在簽名這個功能處高清是真的沒什么優(yōu)勢。。。。但是可能在其他的canvas功能中,這種高清適配會讓canvas內(nèi)容更加清晰。

對于安卓來說,暫時的想法就是將dpr為整數(shù)的安卓機(jī)在flexible中設(shè)置為其本身的dpr(dpr>=3 設(shè)置為3),這樣就也可以讓一大部分dpr比較正常的安卓機(jī)也可以像IPhone一樣使用這種高清。

優(yōu)化6、兼容性問題

在網(wǎng)上拜讀了一位大佬對于orientation事件兼容性處理的方法后 文章,在簽名板上也加入了orientation兼容性處理。

由于部分低端手機(jī)不支持orientation事件,所以要讓他們也可以橫屏簽名,那么就要做一些改動。

解決方案:

保留原有方法不變,加上判斷語句,如下代碼:

var isOrientation = ('orientation' in window && 'onorientationchange' in window);
    if (isOrientation) {
        // 注冊橫豎屏事件,方案1;
        window.onorientationchange = this.orientationChange;
        this.orientationChange();            
    }

對于不支持orientation事件的機(jī)型,做resize事件處理(為了防止與我自己寫的resize方法沖突,所以我把自己在前面寫的resize方法修改成了resizeCanvas):

      else {
        // 使用 resize 來做監(jiān)聽機(jī)制。方案2:
        window.addEventListener('resize',function(e){
            var orientation=(window.innerWidth > window.innerHeight)? "landscape":"portrait"; // 判斷橫豎屏
            if(orientation === 'portrait'){
                // 豎屏 do something ……
                that.screenCtrl = true;
                setTimeout(function(){
                    that.height = that.getWarnHeight();                
                },500)
                that.resizeCanvas(that.verticalWidth, that.verticalHeight, true);
                return;
            } 
            else {
                // 橫屏 do something else ……
                that.screenCtrl = false;
                setTimeout(function(){
                    that.height = that.getBtnAreaHeight();
                    that.crossHeight = window.innerHeight - that.height;
                    that.resizeCanvas(that.crossWidth, that.crossHeight, false);              
                },500)
                that.resizeCanvas(that.crossWidth, that.crossHeight, false);  
            }
        },false)
    }

在加上上面的兼容處理后,基本所有的機(jī)型都可以使用了。

這一塊會不定時更新如何優(yōu)化,我還在學(xué)習(xí)怎樣使canvas內(nèi)容更加清晰。

額外補(bǔ)充

從上圖可以看出,canvas畫布左上角相對于頁面而言不一定是(0,0)的位置。那么如果在drawing方法中不做些改變的話,在簽名的時候筆跡和畫出來的線條位置會不一樣,這時候就需要計算出canvas左上角相對于頁面左上角直接的差值,并改變drawing方法內(nèi)的代碼。補(bǔ)充代碼,獲取提示區(qū)域高度:

getWarnHeight() {
        var warn = document.getElementById('warn');
        var height = warn.offsetHeight;
        warn = null;
        return height;
    },

總結(jié)

簽名板 JS代碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,001評論 25 709
  • 本文首發(fā)于我的個人博客:http://cherryblog.site/github項目地址:https://git...
    sunshine小小倩閱讀 2,116評論 1 8
  • ‘你在哪里,我很擔(dān)心’ 最近在一個廣告里看到這句話,一個妻子發(fā)給他加班的丈夫??吹街笸蝗辉谙脒@句話真正在表達(dá)的意...
    簡心安閱讀 274評論 0 0
  • 最舒服的狀態(tài),躺在床上,靜靜的聽著窗外瀝瀝的雨聲,慢慢享受這獨處的時光。安靜,舒適,沒有其他嘈雜的聲音,任憑大腦放...
    誰與光閱讀 216評論 0 0
  • 急聘人事助理一名(朝陽區(qū)朝陽路) 【應(yīng)聘要求】 2年以上人事工作經(jīng)驗(員工關(guān)系、招聘經(jīng)驗為佳) 本科及以上學(xué)歷,人...
    愚鴻說閱讀 498評論 1 0

友情鏈接更多精彩內(nèi)容