UNPKG

6.8 kBJavaScriptView Raw
1const wrapper = document.getElementById("signature-pad");
2const canvasWrapper = document.getElementById("canvas-wrapper");
3const clearButton = wrapper.querySelector("[data-action=clear]");
4const changeBackgroundColorButton = wrapper.querySelector("[data-action=change-background-color]");
5const changeColorButton = wrapper.querySelector("[data-action=change-color]");
6const changeWidthButton = wrapper.querySelector("[data-action=change-width]");
7const undoButton = wrapper.querySelector("[data-action=undo]");
8const redoButton = wrapper.querySelector("[data-action=redo]");
9const savePNGButton = wrapper.querySelector("[data-action=save-png]");
10const saveJPGButton = wrapper.querySelector("[data-action=save-jpg]");
11const saveSVGButton = wrapper.querySelector("[data-action=save-svg]");
12const saveSVGWithBackgroundButton = wrapper.querySelector("[data-action=save-svg-with-background]");
13const openInWindowButton = wrapper.querySelector("[data-action=open-in-window]");
14let undoData = [];
15const canvas = wrapper.querySelector("canvas");
16const signaturePad = new SignaturePad(canvas, {
17 // It's Necessary to use an opaque color when saving image as JPEG;
18 // this option can be omitted if only saving as PNG or SVG
19 backgroundColor: 'rgb(255, 255, 255)'
20});
21
22function randomColor() {
23 const r = Math.round(Math.random() * 255);
24 const g = Math.round(Math.random() * 255);
25 const b = Math.round(Math.random() * 255);
26 return `rgb(${r},${g},${b})`;
27}
28
29// Adjust canvas coordinate space taking into account pixel ratio,
30// to make it look crisp on mobile devices.
31// This also causes canvas to be cleared.
32function resizeCanvas() {
33 // When zoomed out to less than 100%, for some very strange reason,
34 // some browsers report devicePixelRatio as less than 1
35 // and only part of the canvas is cleared then.
36 const ratio = Math.max(window.devicePixelRatio || 1, 1);
37
38 // This part causes the canvas to be cleared
39 canvas.width = canvas.offsetWidth * ratio;
40 canvas.height = canvas.offsetHeight * ratio;
41 canvas.getContext("2d").scale(ratio, ratio);
42
43 // This library does not listen for canvas changes, so after the canvas is automatically
44 // cleared by the browser, SignaturePad#isEmpty might still return false, even though the
45 // canvas looks empty, because the internal data of this library wasn't cleared. To make sure
46 // that the state of this library is consistent with visual state of the canvas, you
47 // have to clear it manually.
48 //signaturePad.clear();
49
50 // If you want to keep the drawing on resize instead of clearing it you can reset the data.
51 signaturePad.fromData(signaturePad.toData());
52}
53
54// On mobile devices it might make more sense to listen to orientation change,
55// rather than window resize events.
56window.onresize = resizeCanvas;
57resizeCanvas();
58
59window.addEventListener("keydown", (event) => {
60 switch (true) {
61 case event.key === "z" && event.ctrlKey:
62 undoButton.click();
63 break;
64 case event.key === "y" && event.ctrlKey:
65 redoButton.click();
66 break;
67 }
68});
69
70function download(dataURL, filename) {
71 const blob = dataURLToBlob(dataURL);
72 const url = window.URL.createObjectURL(blob);
73
74 const a = document.createElement("a");
75 a.style = "display: none";
76 a.href = url;
77 a.download = filename;
78
79 document.body.appendChild(a);
80 a.click();
81
82 window.URL.revokeObjectURL(url);
83}
84
85// One could simply use Canvas#toBlob method instead, but it's just to show
86// that it can be done using result of SignaturePad#toDataURL.
87function dataURLToBlob(dataURL) {
88 // Code taken from https://github.com/ebidel/filer.js
89 const parts = dataURL.split(';base64,');
90 const contentType = parts[0].split(":")[1];
91 const raw = window.atob(parts[1]);
92 const rawLength = raw.length;
93 const uInt8Array = new Uint8Array(rawLength);
94
95 for (let i = 0; i < rawLength; ++i) {
96 uInt8Array[i] = raw.charCodeAt(i);
97 }
98
99 return new Blob([uInt8Array], { type: contentType });
100}
101
102signaturePad.addEventListener("endStroke", () => {
103 // clear undoData when new data is added
104 undoData = [];
105});
106
107clearButton.addEventListener("click", () => {
108 signaturePad.clear();
109});
110
111undoButton.addEventListener("click", () => {
112 const data = signaturePad.toData();
113
114 if (data && data.length > 0) {
115 // remove the last dot or line
116 const removed = data.pop();
117 undoData.push(removed);
118 signaturePad.fromData(data);
119 }
120});
121
122redoButton.addEventListener("click", () => {
123 if (undoData.length > 0) {
124 const data = signaturePad.toData();
125 data.push(undoData.pop());
126 signaturePad.fromData(data);
127 }
128});
129
130changeBackgroundColorButton.addEventListener("click", () => {
131 signaturePad.backgroundColor = randomColor();
132 const data = signaturePad.toData();
133 signaturePad.clear();
134 signaturePad.fromData(data);
135});
136
137changeColorButton.addEventListener("click", () => {
138 signaturePad.penColor = randomColor();
139});
140
141changeWidthButton.addEventListener("click", () => {
142 const min = Math.round(Math.random() * 100) / 10;
143 const max = Math.round(Math.random() * 100) / 10;
144
145 signaturePad.minWidth = Math.min(min, max);
146 signaturePad.maxWidth = Math.max(min, max);
147});
148
149savePNGButton.addEventListener("click", () => {
150 if (signaturePad.isEmpty()) {
151 alert("Please provide a signature first.");
152 } else {
153 const dataURL = signaturePad.toDataURL();
154 download(dataURL, "signature.png");
155 }
156});
157
158saveJPGButton.addEventListener("click", () => {
159 if (signaturePad.isEmpty()) {
160 alert("Please provide a signature first.");
161 } else {
162 const dataURL = signaturePad.toDataURL("image/jpeg");
163 download(dataURL, "signature.jpg");
164 }
165});
166
167saveSVGButton.addEventListener("click", () => {
168 if (signaturePad.isEmpty()) {
169 alert("Please provide a signature first.");
170 } else {
171 const dataURL = signaturePad.toDataURL('image/svg+xml');
172 download(dataURL, "signature.svg");
173 }
174});
175
176saveSVGWithBackgroundButton.addEventListener("click", () => {
177 if (signaturePad.isEmpty()) {
178 alert("Please provide a signature first.");
179 } else {
180 const dataURL = signaturePad.toDataURL('image/svg+xml', { includeBackgroundColor: true });
181 download(dataURL, "signature.svg");
182 }
183});
184
185openInWindowButton.addEventListener("click", () => {
186 var externalWin = window.open('', '', `width=${canvas.width / window.devicePixelRatio},height=${canvas.height / window.devicePixelRatio}`);
187 canvas.style.width = "100%";
188 canvas.style.height = "100%";
189 externalWin.onresize = resizeCanvas;
190 externalWin.document.body.style.margin = '0';
191 externalWin.document.body.appendChild(canvas);
192 canvasWrapper.classList.add("empty");
193 externalWin.onbeforeunload = () => {
194 canvas.style.width = "";
195 canvas.style.height = "";
196 canvasWrapper.classList.remove("empty");
197 canvasWrapper.appendChild(canvas);
198 resizeCanvas();
199 };
200})