簽名板介紹
在最近的一個(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處枣宫,如圖:
這樣也是非常的方便婆誓。
優(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ù)贯溅,如圖所示:
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ì)比
好吧弥臼,在簽名這個(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代碼地址