smooth-signature H5搭配筆鋒手寫簽名,支援PC/行動端使用
mini-stores 小程式多狀態管理庫,支援多平台小程式使用
受疫情的影響,無紙化流程和電子合約開始普及,電子簽名需求也不斷增加,簽名體驗也逐漸改善,從一開始簡單的canvas畫線,到追求線條絲滑圓潤,再到要求和紙上寫字一樣的筆鋒效果等等。網路上不少現成開源的簽名庫,其中signature_pad筆鋒效果實現比較好,但具體使用還是會發現有明顯的鋸齒感,於是利用工作之餘,根據自身理解換了另一種方案實現了一套,同時也為小程式開發了一版,一起分享給有需要的同學。
npm install mini-smooth-signature
# 或
yarn add mini-smooth-signature
支援微信/支付寶/釘釘/QQ小程式。以下展示為釘釘平台測試程式碼,各平台範例程式碼請查看Examples,其他平台小程式可參考現有範例自行驗證使用。
< view >
< canvas
id = " signature "
width = " {{width * scale}} "
height = " {{height * scale}} "
style = " width:{{width}}px;height:{{height}}px; "
disable-scroll = " {{true}} "
onTouchStart = " handleTouchStart "
onTouchMove = " handleTouchMove "
onTouchCancel = " handleTouchEnd "
onTouchEnd = " handleTouchEnd "
/>
</ view >
import Signature from 'mini-smooth-signature' ;
Page ( {
data : {
width : 320 ,
height : 200 ,
scale : 2 ,
} ,
onReady ( ) {
this . initSignature ( )
} ,
// 初始化
initSignature ( ) {
const ctx = dd . createCanvasContext ( 'signature' ) ;
this . signature = new Signature ( ctx , {
width : this . data . width ,
height : this . data . height ,
scale : this . data . scale ,
getImagePath : ( ) => {
return new Promise ( ( resolve ) => {
ctx . toTempFilePath ( {
success : res => resolve ( res . filePath ) ,
} )
} )
}
} ) ;
} ,
// 绑定touchstart事件
handleTouchStart ( e ) {
const pos = e . touches [ 0 ] ;
this . signature . onDrawStart ( pos . x , pos . y ) ;
} ,
// 绑定touchmove事件
handleTouchMove ( e ) {
const pos = e . touches [ 0 ] ;
this . signature . onDrawMove ( pos . x , pos . y ) ;
} ,
// 绑定touchend/touchcancel事件
handleTouchEnd ( ) {
this . signature . onDrawEnd ( ) ;
} ,
} ) ;
所有配置項均是可選的
const signature = new Signature ( ctx , {
width : 300 ,
height : 600 ,
scale : 2 ,
minWidth : 4 ,
maxWidth : 10 ,
color : '#1890ff' ,
bgColor : '#efefef' ,
} ) ;
options.width
畫佈在頁面實際渲染的寬度(px)
number
options.height
畫佈在頁面實際渲染的高度(px)
number
options.scale
畫布縮放
number
options.color
畫筆顏色
string
options.bgColor
畫布背景顏色
string
options.getImagePath
產生臨時圖片的Promise函數,用於保存歷史記錄,如該項目未配置,則撤銷功能不可用
promise
options.toDataURL
生成base64圖片函數
function
options.requestAnimationFrame
下次進行重繪時執行。用於提高繪畫性能,減少卡頓不流暢
function
options.openSmooth
是否開啟筆鋒效果,預設為開啟
boolean
options.minWidth
畫筆最小寬度(px),開啟筆電時畫筆最小寬度
number
options.maxWidth
畫筆最大寬度(px),開啟筆鋒時畫筆最大寬度,或未開啟筆鋒時畫筆正常寬度
number
options.minSpeed
畫筆達到最小寬度所需最小速度(px/ms),取值範圍1.0-10.0,數值越小,畫筆越容易變細,筆鋒效果會比較明顯,可以自行調整查看效果,選出自己滿意的值。
number
options.maxWidthDiffRate
相鄰兩線寬度增(減)量最大百分比,取值範圍1-100,為了達到筆鋒效果,畫筆寬度會隨畫筆速度而改變,如果相鄰兩線寬度差太大,過渡效果就會很突兀,使用maxWidthDiffRate限制寬度差,讓過渡效果更自然。可以自行調整查看效果,選出自己滿意的數值。
number
options.maxHistoryLength
限制歷史記錄數,即最大可撤銷數,傳入0則關閉歷史記錄功能
number
// 画布上下文context
signature . ctx
// 清屏
signature . clear ( )
// 撤销,如果未配置getImagePath,则不可用
signature . undo ( )
// 获取base64图片,若未配置toDataURL,则不可用
signature . toDataURL ( )
// 是否为空
signature . isEmpty ( )
我們平時紙上寫字,細看會發現筆畫的粗細是不均勻的,這是寫字過程中,筆的按壓力度和移動速度不同而形成的。而在電腦手機瀏覽器上,雖然我們無法獲得到觸摸的壓力,但可以透過畫筆移動的速度來實現不均勻的筆畫效果,讓字體看起來和紙上寫字一樣有「筆鋒」。 ,以下介紹具體實作過程(以下展示程式碼只為方便理解,非最終實作程式碼)。
透過監聽畫布move事件來擷取移動經過的點座標,並記錄目前時間,然後儲存到points陣列中。
function onTouchMove ( x , y ) {
const point = { x , y , t : Date . now ( ) }
points . push ( point ) ;
} ;
透過兩點座標計算出兩點距離,再除以時間差,即可得到移動速度。
const distance = Math . sqrt ( Math . pow ( end . x - start . x , 2 ) + Math . pow ( end . y - start . y , 2 ) ) ;
const speed = distance / ( end . t - start . t ) ;
得到兩點間移動速度,接下來透過簡單演算法計算出線的寬度,其中maxWidth、minWidth、minSpeed為配置項
const addWidth = ( maxWidth - minWidth ) * speed / minSpeed ;
const lineWidth = Math . min ( Math . max ( maxWidth - addWidth , minWidth ) , maxWidth ) ;
另外,為了防止相鄰兩條線寬度差太大,而出現突兀的過渡效果,需要做下限制,其中maxWidthDiffRate為配置項,preLineWidth為上一條線的寬度
const rate = ( lineWidth - preLineWidth ) / preLineWidth ;
const maxRate = maxWidthDiffRate / 100 ;
if ( Math . abs ( rate ) > maxRate ) {
const per = rate > 0 ? maxRate : - maxRate ;
lineWidth = preLineWidth * ( 1 + per ) ;
}
現在已經知道每兩點間線的寬度,接下來就是畫線了。為了讓線條看起來圓潤以及線粗細過渡更自然,我把兩點之間的線平均成三段,其中:
開始畫線,先來看第一段線,因為第一段線和上一條線相交,為了確保兩條線過渡比較圓潤,採用二次貝塞爾曲線,起點為上一條線的第三段起點(pre_x2, pre_y2)
ctx . lineWidth = lineWidth1
ctx . beginPath ( ) ;
ctx . moveTo ( pre_x2 , pre_y2 ) ;
ctx . quadraticCurveTo ( x0 , y0 , x1 , y1 ) ;
ctx . stroke ( ) ;
第二段線為承接第一段和第三段的過渡線,由於第一段和第三段線寬有差異,所以第二段線使用梯形填充,讓過渡效果更自然。
ctx . beginPath ( ) ;
ctx . moveTo ( point1 . x , point1 . y ) ;
ctx . lineTo ( point2 . x , point2 . y ) ;
ctx . lineTo ( point3 . x , point3 . y ) ;
ctx . lineTo ( point4 . x , point4 . y ) ;
ctx . fill ( ) ;
第三段等畫下一條線時重複上述操作即可。