H5 Canvas 簽名板

簽名板介紹

在最近的一個(gè)項(xiàng)目中瘫析,最后的一個(gè)功能是實(shí)現(xiàn)一個(gè)簽名板供客戶簽名使用。這需要用到canvas來(lái)實(shí)現(xiàn)硼啤。

我將這個(gè)功能分成實(shí)現(xiàn)和優(yōu)化兩個(gè)階段质况。首先來(lái)看實(shí)現(xiàn)

實(shí)現(xiàn)

對(duì)于一個(gè)canvas簽名板,首先我們要完成最最最基本的功能莺丑,簽名著蟹!

步驟為下:

1、提供畫布:

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

2、定義畫筆

實(shí)際情況上是沒有畫筆這個(gè)東西的昏名,所以需要自己來(lái)假定一個(gè)畫筆涮雷。

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

mousePress:false  // 這個(gè)flag表示是否開始畫了仑扑,準(zhǔn)備畫的時(shí)候這個(gè)標(biāo)志位置為true览爵,畫完置為false。
beginDraw (e) { ... } // 對(duì)應(yīng)準(zhǔn)備畫的方法
drawing (e) {...} // 對(duì)應(yīng)畫的方法
endDraw (e) {...} // 對(duì)應(yīng)畫完的方法 镇饮。蜓竹。。 這三個(gè)方法的實(shí)現(xiàn)下面會(huì)具體講储藐,這里假定已經(jīng)實(shí)現(xiàn)了俱济。
3、實(shí)現(xiàn)簽名

現(xiàn)在距離實(shí)現(xiàn)簽名就差一步钙勃,那就是記錄下鼠標(biāo)每次經(jīng)過的位置并用ctx.stroke()方法將它畫出來(lái)即可蛛碌。
這里需要知道:畫過的路徑不會(huì)變,將要畫的路徑不知道肺缕,正在畫的地方能獲取左医。所以定義一個(gè)叫做last的對(duì)象來(lái)記錄上一次鼠標(biāo)畫過的點(diǎn)的位置并把它畫到現(xiàn)在的位置授帕,以此類推,就可以畫出一條鼠標(biāo)經(jīng)過的線路了「∩遥現(xiàn)在來(lái)具體實(shí)現(xiàn)畫畫這一過程:

a跛十、beginDraw

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

b、endDraw

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

c、drawing

  drawing (e) {
        e.preventDefault();
        if (!this.mousePress) {
            return; // 這個(gè)雖然不會(huì)觸發(fā)远豺,但是好像需要這個(gè)邏輯也就寫上去了
        }
        var xy = this.getCoordinate(e);  // 這個(gè)方法是獲取當(dāng)前的鼠標(biāo)的位置并將它賦值給一個(gè)叫做xy的對(duì)象
        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();
        }
        // 開始移動(dòng)奈偏,將坐標(biāo)賦值給last。那么下次再移動(dòng)就會(huì)通過上面的操作從上一個(gè)xy移動(dòng)到當(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語(yǔ)法多簡(jiǎn)潔
    },
4惊来、事件綁定

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

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

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

優(yōu)化

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

優(yōu)化1崔泵、移動(dòng)端的簽名:

首先秒赤,把移動(dòng)端的事件綁定到對(duì)應(yīng)的三個(gè)方法:

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

然后,在移動(dòng)端試了試管削,不行倒脓。那是因?yàn)樵趃etCoordinate 方法中獲取的當(dāng)前位置有問題,在移動(dòng)端含思,要用另一種方法來(lái)獲取。為了區(qū)分移動(dòng)端和PC甘晤,需要用一個(gè)控制語(yǔ)句來(lái)區(qū)分它們含潘。如下

getCoordinate(e) {
        var x, y;
        if (this.isTouch(e)) {  // 判斷當(dāng)前事件是移動(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)前事件是移動(dòng)端事件還是PC端事件
        var type = e.type;
        if (type.indexOf('touch') >= 0) {
            return true;
        } else {
            return false;
        }
    },

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

優(yōu)化2线婚、個(gè)性化簽名

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

優(yōu)化3饰抒、用Vue來(lái)實(shí)現(xiàn)肮砾。

用Vue的話,更方便地控制這個(gè)項(xiàng)目中需要用的變量等袋坑。具體改變就是把上述需要定義的變量放在vue的data中仗处,方法房子啊methods中。綁定事件在canvas處枣宫,如圖:

canvas.png

這樣也是非常的方便婆誓。

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

在PC端的話也颤,設(shè)定固定大小的畫布即可洋幻。但對(duì)于移動(dòng)端來(lái)說,畫布的寬度需要撐滿屏幕才勉強(qiáng)可以做到簽名翅娶。這時(shí)候鞋屈,需要實(shí)現(xiàn)將手機(jī)變成橫屏?xí)r,監(jiān)聽手機(jī)的變化故觅,改變畫布的大小厂庇。這里,初始化畫布寬度為手機(jī)屏幕的寬度输吏,高度為width*380/750权旷。

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

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

0表示正常拄氯,90和-90表示橫屏,180就是把手機(jī)倒過來(lái)它浅。然后把orientationChange方法下面開始方案译柏。

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

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

  /**
     * 重置畫布操作
     * @param {*} w 畫布寬 
     * @param {*} h 畫布高
     * @param {*} flag 橫豎屏標(biāo)志
     */  
    resize(w, h, flag) {
        var that = this;
        var nc = document.createElement("canvas"); // nc用來(lái)保留原有畫布數(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速址、高清屏適配。

通常對(duì)于canvas來(lái)說由驹,我們會(huì)在html中設(shè)置

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

這樣一串東西來(lái)規(guī)定這個(gè)頁(yè)面不能縮放芍锚,縮放比為1,這樣我們?cè)陂_發(fā)canvas的時(shí)候就不會(huì)因?yàn)椴煌謾C(jī)的dpr不同導(dǎo)致畫布呈現(xiàn)的有大有小蔓榄,很不合理并炮。當(dāng)然,設(shè)置這樣的一個(gè)約定不但不會(huì)影響簽名板的視覺呈現(xiàn)(畢竟名字線條單一沒那么復(fù)雜)甥郑,也會(huì)很方便整個(gè)流程的開發(fā)逃魄。可是還是想讓IOS的高清屏可以顯示的更加清晰澜搅。

方案1伍俘、

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

this.verticalWidth = screen.width。// 因?yàn)槲視?huì)將這個(gè)verticalWidth賦值給canvas.width赂蕴,見resize()方法。

如果你的是安卓機(jī)舶胀,那么畫布沒有變化概说,因?yàn)閒lexible默認(rèn)設(shè)置的安卓dpr = 1碧注,也就是initial-scale=1/dpr=1,和上面meta標(biāo)簽的配置是一樣的糖赔。所以不會(huì)有變化萍丐。

如果你的是IPhone,那么你會(huì)發(fā)現(xiàn)你的畫布大小變小了放典。這是因?yàn)閕nitial-scale不再是1了逝变,而是1/dpr。這時(shí)候奋构,我們只需要在設(shè)置屏幕寬度的時(shí)候再乘上當(dāng)前iPhone手機(jī)的dpr即可還原畫布大小壳影。

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

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

高清.png
非高清.png

好吧弥臼,在簽名這個(gè)功能處高清是真的沒什么優(yōu)勢(shì)宴咧。。径缅。掺栅。但是可能在其他的canvas功能中,這種高清適配會(huì)讓canvas內(nèi)容更加清晰纳猪。

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

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

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

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

解決方案:

保留原有方法不變谬以,加上判斷語(yǔ)句,如下代碼:

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

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

      else {
        // 使用 resize 來(lái)做監(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ī)型都可以使用了铭乾。

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

額外補(bǔ)充

從上圖可以看出炕檩,canvas畫布左上角相對(duì)于頁(yè)面而言不一定是(0,0)的位置。那么如果在drawing方法中不做些改變的話,在簽名的時(shí)候筆跡和畫出來(lái)的線條位置會(huì)不一樣笛质,這時(shí)候就需要計(jì)算出canvas左上角相對(duì)于頁(yè)面左上角直接的差值泉沾,并改變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)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跷究,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子敲霍,更是在濱河造成了極大的恐慌俊马,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肩杈,死亡現(xiàn)場(chǎng)離奇詭異柴我,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)锋恬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門屯换,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人与学,你說我怎么就攤上這事彤悔。” “怎么了索守?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵晕窑,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我卵佛,道長(zhǎng)杨赤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任截汪,我火速辦了婚禮疾牲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘衙解。我一直安慰自己阳柔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布蚓峦。 她就那樣靜靜地躺著舌剂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪暑椰。 梳的紋絲不亂的頭發(fā)上霍转,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音一汽,去河邊找鬼避消。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的沾谓。 我是一名探鬼主播委造,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼戳鹅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼均驶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起枫虏,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤妇穴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后隶债,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腾它,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年死讹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞒滴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赞警,死狀恐怖妓忍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情愧旦,我是刑警寧澤世剖,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站笤虫,受9級(jí)特大地震影響旁瘫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜琼蚯,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一酬凳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遭庶,春花似錦宁仔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至赐俗,卻和暖如春拉队,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阻逮。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工粱快, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓事哭,卻偏偏與公主長(zhǎng)得像漫雷,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子鳍咱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,506評(píng)論 25 707
  • 本文首發(fā)于我的個(gè)人博客:http://cherryblog.site/github項(xiàng)目地址:https://git...
    sunshine小小倩閱讀 1,973評(píng)論 1 8
  • ‘你在哪里谤辜,我很擔(dān)心’ 最近在一個(gè)廣告里看到這句話蓄坏,一個(gè)妻子發(fā)給他加班的丈夫〕竽睿看到之后突然在想這句話真正在表達(dá)的意...
    簡(jiǎn)心安閱讀 252評(píng)論 0 0
  • 最舒服的狀態(tài)涡戳,躺在床上,靜靜的聽著窗外瀝瀝的雨聲脯倚,慢慢享受這獨(dú)處的時(shí)光渔彰。安靜,舒適推正,沒有其他嘈雜的聲音恍涂,任憑大腦放...
    誰(shuí)與光閱讀 197評(píng)論 0 0
  • 急聘人事助理一名(朝陽(yáng)區(qū)朝陽(yáng)路) 【應(yīng)聘要求】 2年以上人事工作經(jīng)驗(yàn)(員工關(guān)系、招聘經(jīng)驗(yàn)為佳) 本科及以上學(xué)歷舔稀,人...
    愚鴻說閱讀 475評(píng)論 1 0