UNPKG

smooth-signature-v

Version:

前端H5带笔锋手写签名,支持PC端和移动端,无框架限制,Vue、React等均可使用

516 lines (396 loc) 15.9 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.SmoothSignature = factory()); }(this, (function () { 'use strict'; function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var SmoothSignature = /*#__PURE__*/function () { function SmoothSignature(_canvas, options) { var _this = this; _classCallCheck(this, SmoothSignature); this.canvas = {}; this.ctx = {}; this.width = 320; this.height = 200; this.scale = window.devicePixelRatio || 1; this.color = 'black'; this.bgColor = ''; this.canDraw = false; this.openSmooth = true; this.minWidth = 2; this.maxWidth = 6; this.minSpeed = 1.5; this.maxWidthDiffRate = 20; this.points = []; this.canAddHistory = true; this.historyList = []; this.maxHistoryLength = 20; this.onStart = function () {}; this.onEnd = function () {}; this.addListener = function () { _this.removeListener(); _this.canvas.style.touchAction = 'none'; if ('ontouchstart' in window || navigator.maxTouchPoints) { _this.canvas.addEventListener('touchstart', _this.onDrawStart); _this.canvas.addEventListener('touchmove', _this.onDrawMove); document.addEventListener('touchcancel', _this.onDrawEnd); document.addEventListener('touchend', _this.onDrawEnd); } else { _this.canvas.addEventListener('mousedown', _this.onDrawStart); _this.canvas.addEventListener('mousemove', _this.onDrawMove); document.addEventListener('mouseup', _this.onDrawEnd); } }; this.removeListener = function () { _this.canvas.style.touchAction = 'auto'; _this.canvas.removeEventListener('touchstart', _this.onDrawStart); _this.canvas.removeEventListener('touchmove', _this.onDrawMove); document.removeEventListener('touchend', _this.onDrawEnd); document.removeEventListener('touchcancel', _this.onDrawEnd); _this.canvas.removeEventListener('mousedown', _this.onDrawStart); _this.canvas.removeEventListener('mousemove', _this.onDrawMove); document.removeEventListener('mouseup', _this.onDrawEnd); }; this.onDrawStart = function (e) { e.preventDefault(); _this.canDraw = true; _this.canAddHistory = true; _this.ctx.strokeStyle = _this.color; _this.initPoint(e); _this.onStart && _this.onStart(e); }; this.onDrawMove = function (e) { e.preventDefault(); if (!_this.canDraw) return; _this.initPoint(e); if (_this.points.length < 2) return; _this.addHistory(); var point = _this.points.slice(-1)[0]; var prePoint = _this.points.slice(-2, -1)[0]; if (window.requestAnimationFrame) { window.requestAnimationFrame(function () { return _this.onDraw(prePoint, point); }); } else { _this.onDraw(prePoint, point); } }; this.onDraw = function (prePoint, point) { if (_this.openSmooth) { _this.drawSmoothLine(prePoint, point); } else { _this.drawNoSmoothLine(prePoint, point); } }; this.onDrawEnd = function (e) { if (!_this.canDraw) return; _this.canDraw = false; _this.canAddHistory = true; _this.points = []; _this.onEnd && _this.onEnd(e); }; this.getLineWidth = function (speed) { var minSpeed = _this.minSpeed > 10 ? 10 : _this.minSpeed < 1 ? 1 : _this.minSpeed; var addWidth = (_this.maxWidth - _this.minWidth) * speed / minSpeed; var lineWidth = Math.max(_this.maxWidth - addWidth, _this.minWidth); return Math.min(lineWidth, _this.maxWidth); }; this.getRadianData = function (x1, y1, x2, y2) { var dis_x = x2 - x1; var dis_y = y2 - y1; if (dis_x === 0) { return { val: 0, pos: -1 }; } if (dis_y === 0) { return { val: 0, pos: 1 }; } var val = Math.abs(Math.atan(dis_y / dis_x)); if (x2 > x1 && y2 < y1 || x2 < x1 && y2 > y1) { return { val: val, pos: 1 }; } return { val: val, pos: -1 }; }; this.getRadianPoints = function (radianData, x, y, halfLineWidth) { if (radianData.val === 0) { if (radianData.pos === 1) { return [{ x: x, y: y + halfLineWidth }, { x: x, y: y - halfLineWidth }]; } return [{ y: y, x: x + halfLineWidth }, { y: y, x: x - halfLineWidth }]; } var dis_x = Math.sin(radianData.val) * halfLineWidth; var dis_y = Math.cos(radianData.val) * halfLineWidth; if (radianData.pos === 1) { return [{ x: x + dis_x, y: y + dis_y }, { x: x - dis_x, y: y - dis_y }]; } return [{ x: x + dis_x, y: y - dis_y }, { x: x - dis_x, y: y + dis_y }]; }; this.initPoint = function (event) { var t = Date.now(); var prePoint = _this.points.slice(-1)[0]; if (prePoint && prePoint.t === t) { return; } var rect = _this.canvas.getBoundingClientRect(); var e = event.touches && event.touches[0] || event; var x = e.clientX - rect.left; var y = e.clientY - rect.top; if (prePoint && prePoint.x === x && prePoint.y === y) { return; } var point = { x: x, y: y, t: t }; if (_this.openSmooth && prePoint) { var prePoint2 = _this.points.slice(-2, -1)[0]; point.distance = Math.sqrt(Math.pow(point.x - prePoint.x, 2) + Math.pow(point.y - prePoint.y, 2)); point.speed = point.distance / (point.t - prePoint.t || 0.1); point.lineWidth = _this.getLineWidth(point.speed); if (prePoint2 && prePoint2.lineWidth && prePoint.lineWidth) { var rate = (point.lineWidth - prePoint.lineWidth) / prePoint.lineWidth; var maxRate = _this.maxWidthDiffRate / 100; maxRate = maxRate > 1 ? 1 : maxRate < 0.01 ? 0.01 : maxRate; if (Math.abs(rate) > maxRate) { var per = rate > 0 ? maxRate : -maxRate; point.lineWidth = prePoint.lineWidth * (1 + per); } } } _this.points.push(point); _this.points = _this.points.slice(-3); }; this.drawSmoothLine = function (prePoint, point) { var dis_x = point.x - prePoint.x; var dis_y = point.y - prePoint.y; if (Math.abs(dis_x) + Math.abs(dis_y) <= _this.scale) { point.lastX1 = point.lastX2 = prePoint.x + dis_x * 0.5; point.lastY1 = point.lastY2 = prePoint.y + dis_y * 0.5; } else { point.lastX1 = prePoint.x + dis_x * 0.3; point.lastY1 = prePoint.y + dis_y * 0.3; point.lastX2 = prePoint.x + dis_x * 0.7; point.lastY2 = prePoint.y + dis_y * 0.7; } point.perLineWidth = (prePoint.lineWidth + point.lineWidth) / 2; if (typeof prePoint.lastX1 === 'number') { _this.drawCurveLine(prePoint.lastX2, prePoint.lastY2, prePoint.x, prePoint.y, point.lastX1, point.lastY1, point.perLineWidth); if (prePoint.isFirstPoint) return; if (prePoint.lastX1 === prePoint.lastX2 && prePoint.lastY1 === prePoint.lastY2) return; var data = _this.getRadianData(prePoint.lastX1, prePoint.lastY1, prePoint.lastX2, prePoint.lastY2); var points1 = _this.getRadianPoints(data, prePoint.lastX1, prePoint.lastY1, prePoint.perLineWidth / 2); var points2 = _this.getRadianPoints(data, prePoint.lastX2, prePoint.lastY2, point.perLineWidth / 2); _this.drawTrapezoid(points1[0], points2[0], points2[1], points1[1]); } else { point.isFirstPoint = true; } }; this.drawNoSmoothLine = function (prePoint, point) { point.lastX = prePoint.x + (point.x - prePoint.x) * 0.5; point.lastY = prePoint.y + (point.y - prePoint.y) * 0.5; if (typeof prePoint.lastX === 'number') { _this.drawCurveLine(prePoint.lastX, prePoint.lastY, prePoint.x, prePoint.y, point.lastX, point.lastY, _this.maxWidth); } }; this.drawCurveLine = function (x1, y1, x2, y2, x3, y3, lineWidth) { _this.ctx.lineWidth = Number(lineWidth.toFixed(1)); _this.ctx.beginPath(); _this.ctx.moveTo(Number(x1.toFixed(1)), Number(y1.toFixed(1))); _this.ctx.quadraticCurveTo(Number(x2.toFixed(1)), Number(y2.toFixed(1)), Number(x3.toFixed(1)), Number(y3.toFixed(1))); _this.ctx.stroke(); }; this.drawTrapezoid = function (point1, point2, point3, point4) { _this.ctx.beginPath(); _this.ctx.moveTo(Number(point1.x.toFixed(1)), Number(point1.y.toFixed(1))); _this.ctx.lineTo(Number(point2.x.toFixed(1)), Number(point2.y.toFixed(1))); _this.ctx.lineTo(Number(point3.x.toFixed(1)), Number(point3.y.toFixed(1))); _this.ctx.lineTo(Number(point4.x.toFixed(1)), Number(point4.y.toFixed(1))); _this.ctx.fillStyle = _this.color; _this.ctx.fill(); }; this.drawBgColor = function () { if (!_this.bgColor) return; _this.ctx.fillStyle = _this.bgColor; _this.ctx.fillRect(0, 0, _this.width, _this.height); }; this.drawByImageUrl = function (url) { var image = new Image(); image.onload = function () { _this.ctx.clearRect(0, 0, _this.width, _this.height); _this.ctx.drawImage(image, 0, 0, _this.width, _this.height); }; image.crossOrigin = 'anonymous'; image.src = url; }; this.addHistory = function () { if (!_this.maxHistoryLength || !_this.canAddHistory) return; _this.canAddHistory = false; _this.historyList.push(_this.canvas.toDataURL()); _this.historyList = _this.historyList.slice(-_this.maxHistoryLength); }; this.clear = function () { _this.ctx.clearRect(0, 0, _this.width, _this.height); _this.drawBgColor(); _this.historyList.length = 0; }; this.undo = function () { var dataUrl = _this.historyList.splice(-1)[0]; dataUrl && _this.drawByImageUrl(dataUrl); }; this.toDataURL = function () { var type = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'image/png'; var quality = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1; if (_this.canvas.width === _this.width) { return _this.canvas.toDataURL(type, quality); } var canvas = document.createElement('canvas'); canvas.width = _this.width; canvas.height = _this.height; var ctx = canvas.getContext('2d'); ctx.drawImage(_this.canvas, 0, 0, canvas.width, canvas.height); return canvas.toDataURL(type, quality); }; this.getPNG = function () { return _this.toDataURL(); }; this.getJPG = function () { var quality = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.8; return _this.toDataURL('image/jpeg', quality); }; this.isEmpty = function () { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); canvas.width = _this.canvas.width; canvas.height = _this.canvas.height; if (_this.bgColor) { ctx.fillStyle = _this.bgColor; ctx.fillRect(0, 0, canvas.width, canvas.height); } else if (_this.scale !== 1) { ctx.scale(_this.scale, _this.scale); } return canvas.toDataURL() === _this.canvas.toDataURL(); }; this.getRotateCanvas = function () { var degree = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 90; if (degree > 0) { degree = degree > 90 ? 180 : 90; } else { degree = degree < -90 ? 180 : -90; } var canvas = document.createElement('canvas'); var w = _this.width; var h = _this.height; if (degree === 180) { canvas.width = w; canvas.height = h; } else { canvas.width = h; canvas.height = w; } var ctx = canvas.getContext('2d'); ctx.rotate(degree * Math.PI / 180); if (degree === 90) { // 顺时针90度 ctx.drawImage(_this.canvas, 0, -h, w, h); } else if (degree === -90) { // 逆时针90度 ctx.drawImage(_this.canvas, -w, 0, w, h); } else if (degree === 180) { ctx.drawImage(_this.canvas, -w, -h, w, h); } return canvas; }; this.init(_canvas, options); } _createClass(SmoothSignature, [{ key: "init", value: function init(canvas) { var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (!canvas) return; this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.width = options.width || canvas.clientWidth || this.width; this.height = options.height || canvas.clientHeight || this.height; this.scale = options.scale || this.scale; this.color = options.color || this.color; this.bgColor = options.bgColor || this.bgColor; this.openSmooth = options.openSmooth === undefined ? this.openSmooth : !!options.openSmooth; this.minWidth = options.minWidth || this.minWidth; this.maxWidth = options.maxWidth || this.maxWidth; this.minSpeed = options.minSpeed || this.minSpeed; this.maxWidthDiffRate = options.maxWidthDiffRate || this.maxWidthDiffRate; this.maxHistoryLength = options.maxHistoryLength || this.maxHistoryLength; this.onStart = options.onStart; this.onEnd = options.onEnd; if (this.scale > 0) { this.canvas.height = this.height * this.scale; this.canvas.width = this.width * this.scale; if (this.scale !== 1) { this.canvas.style.width = this.width + 'px'; this.canvas.style.height = this.height + 'px'; this.ctx.scale(this.scale, this.scale); } } this.ctx.lineCap = 'round'; this.drawBgColor(); this.addListener(); } }]); return SmoothSignature; }(); return SmoothSignature; })));