smooth-signature H5 comes with pen tip handwriting signature and supports PC/mobile use
mini-stores mini program multi-state management library, supports the use of multi-platform mini programs
Affected by the epidemic, paperless processes and electronic contracts have become popular, the demand for electronic signatures has also continued to increase, and the signature experience has gradually improved, from drawing lines on a simple canvas at the beginning, to pursuing silky and round lines, and then requiring Japanese paper. The pen edge effect is the same as writing on it, etc. There are many ready-made open source signature libraries on the Internet. Among them, the signature_pad stroke effect is better, but you will still find an obvious jagged feeling when using it. So I used my spare time to implement another solution based on my own understanding. At the same time, We also developed a version of the mini program and shared it with students in need.
npm install mini-smooth-signature
# 或
yarn add mini-smooth-signature
Supports WeChat/Alipay/DingTalk/QQ mini programs. The following is the test code for the DingTalk platform. Please see Examples for the sample codes of each platform. Mini programs on other platforms can refer to the existing samples to verify their use.
< 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 ( ) ;
} ,
} ) ;
All configuration items are optional
const signature = new Signature ( ctx , {
width : 300 ,
height : 600 ,
scale : 2 ,
minWidth : 4 ,
maxWidth : 10 ,
color : '#1890ff' ,
bgColor : '#efefef' ,
} ) ;
options.width
The width of the canvas actually rendered on the page (px)
number
options.height
The height of the canvas actually rendered on the page (px)
number
options.scale
Canvas zoom
number
options.color
brush color
string
options.bgColor
Canvas background color
string
options.getImagePath
Promise function that generates temporary pictures for saving history. If this item is not configured, the undo function is unavailable.
promise
options.toDataURL
Generate base64 image function
function
options.requestAnimationFrame
Executed the next time a redraw occurs. Used to improve painting performance and reduce lag and unsmoothness
function
options.openSmooth
Whether to enable the pen edge effect, enabled by default
boolean
options.minWidth
The minimum width of the brush (px), the minimum width of the brush when the pen tip is turned on
number
options.maxWidth
The maximum width of the brush (px), the maximum width of the brush when the brush edge is turned on, or the normal width of the brush when the brush edge is not turned on
number
options.minSpeed
The minimum speed (px/ms) required for the brush to reach the minimum width. The value range is 1.0-10.0. The smaller the value, the easier it is for the brush to become thinner, and the brush tip effect will be more obvious. You can adjust the viewing effect yourself and choose a value you are satisfied with.
number
options.maxWidthDiffRate
The maximum percentage increase (decrease) in the width of two adjacent lines. The value range is 1-100. In order to achieve the stroke effect, the brush width will change with the brush speed. If the width difference between two adjacent lines is too large, the transition effect will be abrupt. , use maxWidthDiffRate to limit the width difference to make the transition effect more natural. You can adjust the viewing effect yourself and choose a value you are satisfied with.
number
options.maxHistoryLength
Limit the number of history records, that is, the maximum number that can be undone. Passing 0 will turn off the history record function.
number
// 画布上下文context
signature . ctx
// 清屏
signature . clear ( )
// 撤销,如果未配置getImagePath,则不可用
signature . undo ( )
// 获取base64图片,若未配置toDataURL,则不可用
signature . toDataURL ( )
// 是否为空
signature . isEmpty ( )
We usually write on paper. If you look closely, you will find that the thickness of the strokes is uneven. This is caused by the different pressing force and moving speed of the pen during the writing process. On computer and mobile browsers, although we cannot obtain the pressure of touch, we can use the speed of the brush movement to achieve uneven stroke effects, making the font look as sharp as writing on paper. , the specific implementation process is introduced below (the code shown below is only for ease of understanding, not the final implementation code).
Collect the coordinates of the moved points by listening to the canvas move event, record the current time, and then save it to the points array.
function onTouchMove ( x , y ) {
const point = { x , y , t : Date . now ( ) }
points . push ( point ) ;
} ;
Calculate the distance between the two points through the coordinates of the two points, and then divide it by the time difference to get the moving speed.
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 ) ;
Get the moving speed between two points, and then calculate the width of the line through a simple algorithm, where maxWidth, minWidth, and minSpeed are configuration items
const addWidth = ( maxWidth - minWidth ) * speed / minSpeed ;
const lineWidth = Math . min ( Math . max ( maxWidth - addWidth , minWidth ) , maxWidth ) ;
In addition, in order to prevent the width difference between two adjacent lines from being too large and causing an abrupt transition effect, restrictions need to be made, where maxWidthDiffRate is a configuration item and preLineWidth is the width of the previous line.
const rate = ( lineWidth - preLineWidth ) / preLineWidth ;
const maxRate = maxWidthDiffRate / 100 ;
if ( Math . abs ( rate ) > maxRate ) {
const per = rate > 0 ? maxRate : - maxRate ;
lineWidth = preLineWidth * ( 1 + per ) ;
}
Now that you know the width of the line between every two points, the next step is to draw the line. In order to make the lines look rounded and the transition between line thicknesses more natural, I averaged the line between two points into three segments, where:
Start drawing the line, first look at the first segment of the line, because the first segment of the line intersects with the previous line, in order to ensure a smooth transition between the two lines, a quadratic Bezier curve is used, and the starting point is the starting point of the third segment of the previous line. (pre_x2, pre_y2)
ctx . lineWidth = lineWidth1
ctx . beginPath ( ) ;
ctx . moveTo ( pre_x2 , pre_y2 ) ;
ctx . quadraticCurveTo ( x0 , y0 , x1 , y1 ) ;
ctx . stroke ( ) ;
The second segment of line is the transition line connecting the first segment and the third segment. Since the line widths of the first segment and the third segment are different, the second segment of the line is filled with a trapezoid to make the transition effect more natural.
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 ( ) ;
Repeat the above operation when drawing the next line in the third paragraph.