├── 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 |
--------------------------------------------------------------------------------