├── README.md ├── index.html └── plugin └── canvas-sign.js /README.md: -------------------------------------------------------------------------------- 1 | # canvas-sign 2 | 打造canvas签名组件 3 | 4 | ## Use 5 | ```js 6 | npm install canvas-sign-online --save 7 | ``` 8 | 支持pc和移动端,核心代码plugin/canvas-sign.js 9 | 第一步初始化并且设置容器大小: 10 | ```js 11 | import CanvasSign from "canvas-sign-online"; 12 | 13 | const cv = document.getElementById("canvas"); 14 | const canvas = new CanvasSign(cv); 15 | /** 注意:canvas的宽高需要在属性上设置,而不是style */ 16 | cv.width = '500'; 17 | cv.height = '500'; 18 | ``` 19 | 第二步画笔设置样式: 20 | ```js 21 | canvas.setLineStyle({ 22 | strokeStyle: "black", 23 | lineWidth: 2, 24 | shadowBlur: '1', 25 | lineCap: 'round', 26 | shadowColor: 'black', 27 | lineJoin: 'round', 28 | }) 29 | ``` 30 | 接下来就是自由绘制了。 31 | 绘制完成过后导出: 32 | ```js 33 | const img = document.createElement("a"); 34 | img.href = canvas.getPNGImage(); 35 | img.download = "canvas-sign.png"; 36 | img.style.display = "none"; 37 | document.body.appendChild(img); 38 | img.click(); 39 | document.body.removeChild(img); 40 | ``` 41 | 导入json: 42 | ```js 43 | canvas.loadJson(dataJson); 44 | ``` 45 | api列表: 46 | 47 | api | Description | Note 48 | --- | --- | --- 49 | `getPNGImage` | png格式导出 | 50 | `getJPGImage` | jpg格式导出 | 51 | `clear` | 清除画板 | 52 | `setLineStyle` | 设置画笔样式 | 同原生canvas 样式和颜色 53 | `toJson` | 导出json | 54 | `loadJson` | 加载已经导出的json | toJson().dataJson 55 | 56 | 57 | 58 | 最后,祝使用愉快! 59 | ## Realization idea 60 | 实现思路以及细节参考:[canvas在线签名插件](https://juejin.cn/post/6989985162599596063/) 61 | 如果有什么问题或好的想法欢迎在参与讨论。 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 在线canvas签名 7 | 8 | 9 | 55 | 56 | 57 |
58 |

在线签字

59 | 60 | 61 | 62 | 68 |
69 |
70 | 71 |
72 | 73 | 74 | 115 | 116 | -------------------------------------------------------------------------------- /plugin/canvas-sign.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 在线签字插件 3 | * @author weilang 4 | * @since 2021-7 5 | */ 6 | const isType = (val) =>{ 7 | return Object.prototype.toString.call(val).slice(8, -1).toLowerCase(); 8 | } 9 | const isObject = (val)=> { 10 | return isType(val) === 'object'; 11 | } 12 | const isArray = (val) => { 13 | return isType(val) === 'array'; 14 | } 15 | const isMobile = () => { 16 | return /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i.test(window.navigator.userAgent); 17 | } 18 | const getElement = (el)=> { 19 | if (el && typeof el === 'object' && el.nodeType === 1 && typeof el.nodeName === 'string') return el; 20 | let canvas; 21 | try { 22 | canvas = document.querySelector(el); 23 | } catch (error) { 24 | throw new Error("Canvas reference not found!") 25 | } 26 | return canvas; 27 | } 28 | const defaultConfig = { 29 | //防止引用之间的值传递 30 | dataJson: () => { 31 | return { 32 | lineStyle: {}, 33 | position: [] 34 | } 35 | }, 36 | defaultLineStyle: { 37 | strokeStyle: "black", 38 | lineWidth: 2, 39 | shadowBlur: '1', 40 | lineCap: 'round', 41 | shadowColor: 'black', // 边缘颜色 42 | lineJoin: 'round', 43 | }, 44 | version: 'v1.0' 45 | } 46 | class canvasSign { 47 | constructor(el, options = {}) { 48 | this.canvas = getElement(el); 49 | this.ctx = this.canvas.getContext("2d"); 50 | this.isMouseDown = false; 51 | this.eventBus = {}; 52 | this.lineStyle = { 53 | ...defaultConfig.defaultLineStyle, 54 | ...options 55 | } 56 | this.setLineStyle(); 57 | this.darw(); 58 | this.dataJson = []; 59 | } 60 | darw() { 61 | const arr = isMobile() ? ['touchstart', 'touchmove', 'touchend'] : ['mousedown', 'mousemove', 'mouseup', 'mouseleave']; 62 | arr.forEach(item => { 63 | this.eventMan(item); 64 | }) 65 | } 66 | /** 67 | * position (x,y)点位置 68 | * type 1:非导入 2:导入 69 | */ 70 | creat(position = { x, y }, type = 1) { 71 | const creatC = () => { 72 | let ctx = this.ctx; 73 | ctx.lineTo(position.x, position.y); 74 | ctx.stroke(); 75 | } 76 | if (type === 2) { 77 | creatC(); 78 | return; 79 | } 80 | creatC(); 81 | this.setJson(position, 'moving'); 82 | } 83 | handleEvent() { 84 | const { left, top } = this.canvas.getBoundingClientRect(); 85 | const mobile = isMobile(); 86 | let drawend = (e) => { 87 | if (this.isMouseDown || isMobile()) { 88 | this.emit('drawend', e); 89 | this.setJson({ ...this.lineStyle }, 'end'); 90 | } 91 | } 92 | const handleMove = (e) => { 93 | //判断pc端是否左键按下,移动端不需要做判断 94 | if ((!this.isMouseDown || e.which != 1) && !mobile) return; 95 | e = mobile ? e.touches[0] : e; 96 | this.emit('mousemoving', e); 97 | this.creat({ x: e.offsetX + 0.5, y: e.offsetY + 0.5 }); 98 | } 99 | const handleLeave = (e) => { 100 | //鼠标按下时候移出canvas中并且松开 101 | drawend(e); 102 | this.isMouseDown = false; 103 | this.emit('mouseleave', e); 104 | } 105 | const handleUp = (e) => { 106 | drawend(e); 107 | this.isMouseDown = false; 108 | this.emit("mouseup", e); 109 | } 110 | const handleDown = (e) => { 111 | this.emit('mousedown', e); 112 | e = isMobile() ? e.touches[0] : e; 113 | this.setJson(defaultConfig.dataJson(), 'start'); 114 | //按下键时候重置画笔 115 | this.ctx.beginPath(); 116 | this.isMouseDown = true; 117 | const position = { x: e.offsetX + 0.5, y: e.offsetY + 0.5 }; 118 | this.ctx.moveTo(position.x,position.y) 119 | this.creat({ x: position.x, y: position.y }); 120 | } 121 | return { handleMove, handleDown, handleLeave, handleUp }; 122 | } 123 | getPNGImage(canvas = this.canvas) { 124 | return canvas.toDataURL('image/png'); 125 | } 126 | getJPGImage(canvas = this.canvas) { 127 | return canvas.toDataURL('image/jpeg'); 128 | } 129 | eventMan(type, fn) { 130 | const handleEvent = this.handleEvent(); 131 | const raf = window.requestAnimationFrame; 132 | const move = raf?(e)=>{ 133 | raf(() => { 134 | handleEvent.handleMove(e); 135 | }); 136 | }:handleEvent.handleMove; 137 | const allFn = { 138 | mousedown: handleEvent.handleDown, 139 | mouseleave: handleEvent.handleLeave, 140 | mouseup: handleEvent.handleUp, 141 | mousemove: move, 142 | //移动端 143 | touchmove: move, 144 | touchstart: handleEvent.handleDown, 145 | touchend: handleEvent.handleLeave 146 | } 147 | this.canvas.addEventListener(type, fn || allFn[type], false) 148 | } 149 | clear() { 150 | let width = this.canvas.width; 151 | let height = this.canvas.height; 152 | this.ctx.clearRect(0, 0, width, height); 153 | this.dataJson = []; 154 | return this; 155 | } 156 | /** 157 | * style 线条样式 158 | * isSaveLineStyle 是否设置更新当前画笔样式 159 | */ 160 | setLineStyle(style = {},isSaveLineStyle = true) { 161 | const ctx = this.ctx; 162 | const lineStyle = isObject(style)? { ...this.lineStyle, ...style } : this.lineStyle; 163 | if(isSaveLineStyle){ 164 | this.lineStyle = lineStyle; 165 | } 166 | Object.keys(lineStyle).forEach(key => { 167 | ctx[key] = lineStyle[key]; 168 | }); 169 | ctx.beginPath(); 170 | return this; 171 | } 172 | /** 173 | * json json格式 174 | * type moving:绘制中 end.已结束绘制 start.开始绘制 175 | */ 176 | setJson(value, type) { 177 | const dataJson = this.dataJson; 178 | const jsonLength = dataJson.length - 1; 179 | switch (type) { 180 | case 'moving': 181 | dataJson[jsonLength].position.push(value); 182 | break; 183 | case 'end': 184 | dataJson[jsonLength].lineStyle = value; 185 | break; 186 | case "start": 187 | dataJson.push(value); 188 | break; 189 | } 190 | } 191 | toJson(json) { 192 | if (json) this.dataJson = json; 193 | return { 194 | version: defaultConfig.version, 195 | dataJson: this.dataJson 196 | } 197 | } 198 | loadJson(arr = []) { 199 | this.dataJson = arr; 200 | console.log(isArray(arr)); 201 | if (!isArray(arr)) throw new Error("loadjson requires an array!"); 202 | for (let i = 0; i < arr.length; i++) { 203 | const item = arr[i]; 204 | this.setLineStyle(item.lineStyle,false); 205 | item.position.map(point => { 206 | this.creat(point, 2); 207 | }) 208 | }; 209 | this.setLineStyle(); 210 | return this; 211 | } 212 | drawImage(...arg) { 213 | this.ctx.drawImage(...arg); 214 | return this; 215 | } 216 | /**监听事件 217 | * mousemoving 鼠标移动(手指)时 218 | * mouseup 鼠标(手指)松开按键 219 | * mousedown 鼠标(手指)按下 220 | * mouseleave 鼠标移出容器 221 | * drawend 每一笔绘制结束 222 | */ 223 | emit(name, target) { 224 | let arr = this.eventBus[name]; 225 | if (arr) arr.map(cb => { cb(target); }); 226 | } 227 | on(name, cb) { 228 | if (this.eventBus[name]) { 229 | this.eventBus[name].push(cb); 230 | return; 231 | } 232 | this.eventBus[name] = [cb]; 233 | return this; 234 | } 235 | } 236 | if (typeof module !== 'undefined' && module.exports) { 237 | module.exports = canvasSign; 238 | }else{ 239 | window.canvasSign = canvasSign; 240 | } 241 | // export default canvasSign; 242 | --------------------------------------------------------------------------------