微信小程序
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

signature_pad.js 12 KiB

3 anos atrás
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*!
  2. * Signature Pad v2.2.0
  3. * https://github.com/szimek/signature_pad
  4. *
  5. * Copyright 2017 Szymon Nowak
  6. * Released under the MIT license
  7. *
  8. * The main idea and some parts of the code (e.g. drawing variable width Bézier curve) are taken from:
  9. * http://corner.squareup.com/2012/07/smoother-signatures.html
  10. *
  11. * Implementation of interpolation using cubic Bézier curves is taken from:
  12. * http://benknowscode.wordpress.com/2012/09/14/path-interpolation-using-cubic-bezier-and-control-point-estimation-in-javascript
  13. *
  14. * Algorithm for approximated length of a Bézier curve is taken from:
  15. * http://www.lemoda.net/maths/bezier-length/index.html
  16. *
  17. */
  18. (function(global, factory) {
  19. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  20. typeof define === 'function' && define.amd ? define(factory) :
  21. (global.SignaturePad = factory());
  22. }(this, (function() {
  23. 'use strict';
  24. function Point(x, y, time) {
  25. this.x = x;
  26. this.y = y;
  27. this.time = time || new Date().getTime();
  28. }
  29. Point.prototype.velocityFrom = function(start) {
  30. return this.time !== start.time ? this.distanceTo(start) / (this.time - start.time) : 1;
  31. };
  32. Point.prototype.distanceTo = function(start) {
  33. return Math.sqrt(Math.pow(this.x - start.x, 2) + Math.pow(this.y - start.y, 2));
  34. };
  35. function Bezier(startPoint, control1, control2, endPoint) {
  36. this.startPoint = startPoint;
  37. this.control1 = control1;
  38. this.control2 = control2;
  39. this.endPoint = endPoint;
  40. }
  41. // Returns approximated length.
  42. Bezier.prototype.length = function() {
  43. var steps = 10;
  44. var length = 0;
  45. var px = void 0;
  46. var py = void 0;
  47. for (var i = 0; i <= steps; i += 1) {
  48. var t = i / steps;
  49. var cx = this._point(t, this.startPoint.x, this.control1.x, this.control2.x, this.endPoint.x);
  50. var cy = this._point(t, this.startPoint.y, this.control1.y, this.control2.y, this.endPoint.y);
  51. if (i > 0) {
  52. var xdiff = cx - px;
  53. var ydiff = cy - py;
  54. length += Math.sqrt(xdiff * xdiff + ydiff * ydiff);
  55. }
  56. px = cx;
  57. py = cy;
  58. }
  59. return length;
  60. };
  61. /* eslint-disable no-multi-spaces, space-in-parens */
  62. Bezier.prototype._point = function(t, start, c1, c2, end) {
  63. return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + 3.0 * c2 * (1.0 - t) * t * t + end * t * t * t;
  64. };
  65. /* eslint-disable */
  66. // http://stackoverflow.com/a/27078401/815507
  67. function throttle(func, wait, options) {
  68. var context, args, result;
  69. var timeout = null;
  70. var previous = 0;
  71. if (!options) options = {};
  72. var later = function later() {
  73. previous = options.leading === false ? 0 : Date.now();
  74. timeout = null;
  75. result = func.apply(context, args);
  76. if (!timeout) context = args = null;
  77. };
  78. return function() {
  79. var now = Date.now();
  80. if (!previous && options.leading === false) previous = now;
  81. var remaining = wait - (now - previous);
  82. context = this;
  83. args = arguments;
  84. if (remaining <= 0 || remaining > wait) {
  85. if (timeout) {
  86. clearTimeout(timeout);
  87. timeout = null;
  88. }
  89. previous = now;
  90. result = func.apply(context, args);
  91. if (!timeout) context = args = null;
  92. } else if (!timeout && options.trailing !== false) {
  93. timeout = setTimeout(later, remaining);
  94. }
  95. return result;
  96. };
  97. }
  98. function SignaturePad(canvas, options) {
  99. var self = this;
  100. var opts = options || {};
  101. this.velocityFilterWeight = opts.velocityFilterWeight || 0.7;
  102. this.minWidth = opts.minWidth || 0.5;
  103. this.maxWidth = opts.maxWidth || 2.5;
  104. this.throttle = 'throttle' in opts ? opts.throttle : 16; // in miliseconds
  105. if (this.throttle) {
  106. this._strokeMoveUpdate = throttle(SignaturePad.prototype._strokeUpdate, this.throttle);
  107. } else {
  108. this._strokeMoveUpdate = SignaturePad.prototype._strokeUpdate;
  109. }
  110. this.dotSize = opts.dotSize || function() {
  111. return (this.minWidth + this.maxWidth) / 2;
  112. };
  113. this.penColor = opts.penColor || 'black'; //签字板的颜色
  114. this.backgroundColor = opts.backgroundColor || 'rgba(0,0,0,0)';
  115. this.onBegin = opts.onBegin;
  116. this.onEnd = opts.onEnd;
  117. this.devicePixelRatio = opts.devicePixelRatio || 1;
  118. this.lineWidth = opts.lineWidth || 1; //签字板的粗细
  119. this._canvas = canvas;
  120. this._ctx = canvas;
  121. this.clear();
  122. // We need add these inline so they are available to unbind while still having
  123. // access to 'self' we could use _.bind but it's not worth adding a dependency.
  124. this._handleTouchStart = function(event, data) {
  125. self.penColor = data.penColor || self.penColor;
  126. self.lineWidth = data.lineWidth || self.lineWidth;
  127. if (event.touches.length === 1) {
  128. var touch = event.changedTouches[0];
  129. self._strokeBegin(touch);
  130. }
  131. };
  132. this._handleTouchMove = function(event) {
  133. // Prevent scrolling.
  134. var touch = event.touches[0];
  135. self._strokeMoveUpdate(touch);
  136. };
  137. this._handleTouchEnd = function(event) {
  138. self._strokeEnd(event);
  139. };
  140. this.clear = function() {
  141. var ctx = this._ctx;
  142. var canvas = this._canvas;
  143. ctx.fillStyle = this.backgroundColor;
  144. ctx.clearRect(0, 0, canvas.width, canvas.height);
  145. ctx.fillRect(0, 0, canvas.width, canvas.height);
  146. ctx.draw()
  147. this._data = [];
  148. this._reset();
  149. this._isEmpty = true;
  150. };
  151. }
  152. // Public methods
  153. SignaturePad.prototype.clear = function() {
  154. var ctx = this._ctx;
  155. var canvas = this._canvas;
  156. ctx.fillStyle = this.backgroundColor;
  157. ctx.clearRect(0, 0, canvas.width, canvas.height);
  158. ctx.fillRect(0, 0, canvas.width, canvas.height);
  159. this._data = [];
  160. this._reset();
  161. this._isEmpty = true;
  162. };
  163. SignaturePad.prototype.isEmpty = function() {
  164. return this._isEmpty;
  165. };
  166. // Private methods
  167. SignaturePad.prototype._strokeBegin = function(event) {
  168. this._data.push([]);
  169. this._reset();
  170. this._strokeUpdate(event);
  171. if (typeof this.onBegin === 'function') {
  172. this.onBegin(event);
  173. }
  174. };
  175. SignaturePad.prototype._strokeUpdate = function(event) {
  176. var x = event.x;
  177. var y = event.y;
  178. var point = this._createPoint(x, y);
  179. var _addPoint = this._addPoint(point),
  180. curve = _addPoint.curve,
  181. widths = _addPoint.widths;
  182. if (curve && widths) {
  183. this._drawCurve(curve, widths.start, widths.end);
  184. }
  185. this._data[this._data.length - 1].push({
  186. x: point.x,
  187. y: point.y,
  188. time: point.time,
  189. color: this.penColor
  190. });
  191. };
  192. SignaturePad.prototype._strokeEnd = function(event) {
  193. var canDrawCurve = this.points.length > 2;
  194. var point = this.points[0];
  195. if (!canDrawCurve && point) {
  196. this._drawDot(point);
  197. }
  198. if (typeof this.onEnd === 'function') {
  199. this.onEnd(event);
  200. }
  201. };
  202. SignaturePad.prototype._reset = function() {
  203. this.points = [];
  204. this._lastVelocity = 0;
  205. this._lastWidth = (this.minWidth + this.maxWidth) / 2;
  206. this._ctx.fillStyle = this.penColor;
  207. };
  208. SignaturePad.prototype._createPoint = function(x, y, time) {
  209. var rect = {
  210. left: 10,
  211. top: 10
  212. };
  213. return new Point(x - rect.left, y - rect.top, time || new Date().getTime());
  214. };
  215. SignaturePad.prototype._addPoint = function(point) {
  216. var points = this.points;
  217. var tmp = void 0;
  218. points.push(point);
  219. if (points.length > 2) {
  220. // To reduce the initial lag make it work with 3 points
  221. // by copying the first point to the beginning.
  222. if (points.length === 3) points.unshift(points[0]);
  223. tmp = this._calculateCurveControlPoints(points[0], points[1], points[2]);
  224. var c2 = tmp.c2;
  225. tmp = this._calculateCurveControlPoints(points[1], points[2], points[3]);
  226. var c3 = tmp.c1;
  227. var curve = new Bezier(points[1], c2, c3, points[2]);
  228. var widths = this._calculateCurveWidths(curve);
  229. // Remove the first element from the list,
  230. // so that we always have no more than 4 points in points array.
  231. points.shift();
  232. return { curve: curve, widths: widths };
  233. }
  234. return {};
  235. };
  236. SignaturePad.prototype._calculateCurveControlPoints = function(s1, s2, s3) {
  237. var dx1 = s1.x - s2.x;
  238. var dy1 = s1.y - s2.y;
  239. var dx2 = s2.x - s3.x;
  240. var dy2 = s2.y - s3.y;
  241. var m1 = { x: (s1.x + s2.x) / 2.0, y: (s1.y + s2.y) / 2.0 };
  242. var m2 = { x: (s2.x + s3.x) / 2.0, y: (s2.y + s3.y) / 2.0 };
  243. var l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
  244. var l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
  245. var dxm = m1.x - m2.x;
  246. var dym = m1.y - m2.y;
  247. var k = l2 / (l1 + l2);
  248. var cm = { x: m2.x + dxm * k, y: m2.y + dym * k };
  249. var tx = s2.x - cm.x;
  250. var ty = s2.y - cm.y;
  251. return {
  252. c1: new Point(m1.x + tx, m1.y + ty),
  253. c2: new Point(m2.x + tx, m2.y + ty)
  254. };
  255. };
  256. SignaturePad.prototype._calculateCurveWidths = function(curve) {
  257. var startPoint = curve.startPoint;
  258. var endPoint = curve.endPoint;
  259. var widths = { start: null, end: null };
  260. var velocity = this.velocityFilterWeight * endPoint.velocityFrom(startPoint) + (1 - this.velocityFilterWeight) * this._lastVelocity;
  261. var newWidth = this._strokeWidth(velocity);
  262. widths.start = this._lastWidth;
  263. widths.end = newWidth;
  264. this._lastVelocity = velocity;
  265. this._lastWidth = newWidth;
  266. return widths;
  267. };
  268. SignaturePad.prototype._strokeWidth = function(velocity) {
  269. return Math.max(this.maxWidth / (velocity + 1), this.minWidth);
  270. };
  271. SignaturePad.prototype._drawPoint = function(x, y, size) {
  272. var ctx = this._ctx;
  273. var lineWidth = this.lineWidth;
  274. ctx.moveTo(x, y);
  275. ctx.arc(x, y, size * lineWidth, 0, 2 * Math.PI, false);
  276. this._isEmpty = false;
  277. };
  278. SignaturePad.prototype._drawCurve = function(curve, startWidth, endWidth) {
  279. var ctx = this._ctx;
  280. var widthDelta = endWidth - startWidth;
  281. var drawSteps = Math.floor(curve.length());
  282. ctx.beginPath();
  283. for (var i = 0; i < drawSteps; i += 1) {
  284. // Calculate the Bezier (x, y) coordinate for this step.
  285. var t = i / drawSteps;
  286. var tt = t * t;
  287. var ttt = tt * t;
  288. var u = 1 - t;
  289. var uu = u * u;
  290. var uuu = uu * u;
  291. var x = uuu * curve.startPoint.x;
  292. x += 3 * uu * t * curve.control1.x;
  293. x += 3 * u * tt * curve.control2.x;
  294. x += ttt * curve.endPoint.x;
  295. var y = uuu * curve.startPoint.y;
  296. y += 3 * uu * t * curve.control1.y;
  297. y += 3 * u * tt * curve.control2.y;
  298. y += ttt * curve.endPoint.y;
  299. var width = startWidth + ttt * widthDelta;
  300. this._drawPoint(x, y, width);
  301. }
  302. var penColor = this.penColor;
  303. ctx.closePath();
  304. ctx.setStrokeStyle(penColor);
  305. ctx.setFillStyle(penColor);
  306. ctx.fill();
  307. ctx.stroke();
  308. ctx.draw(true)
  309. };
  310. SignaturePad.prototype._drawDot = function(point) {
  311. var ctx = this._ctx;
  312. var width = typeof this.dotSize === 'function' ? this.dotSize() : this.dotSize;
  313. var penColor = this.penColor;
  314. ctx.beginPath();
  315. this._drawPoint(point.x, point.y, width);
  316. ctx.closePath();
  317. ctx.setStrokeStyle(penColor);
  318. ctx.setFillStyle(penColor);
  319. ctx.fill();
  320. ctx.stroke();
  321. ctx.draw(true)
  322. };
  323. SignaturePad.prototype.toData = function() {
  324. return this._data;
  325. };
  326. return SignaturePad;
  327. })));