"use strict"; (function (root, factory) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = factory() } else if (typeof define === "function" && define.amd) { define(factory) } else { root = root || self root.Signature = factory() } })(typeof self !== "undefined" ? self : this, function () { function isHexColor(color) { return typeof color === "string" && /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color) } function Signature(options) { var self = this var history = [] var drawing = false var currentPoint = null var activePointerId = null var canvas var context self.obj = options && !Array.isArray(options) ? options : {} self.option = { el: self.obj.el ? self.obj.el : "body", lineWidth: !isNaN(parseInt(self.obj.lineWidth, 10)) ? parseInt(self.obj.lineWidth, 10) : 5, lineColor: isHexColor(self.obj.lineColor) ? self.obj.lineColor : "#000000", overflow: typeof self.obj.overflow === "boolean" ? self.obj.overflow : true, background: isHexColor(self.obj.background) ? self.obj.background : "#ffffff" } self.status = history self.step = -1 self.firstStatus = "" canvas = document.querySelector(self.option.el) if (!canvas || canvas.tagName !== "CANVAS") { throw new Error("Target element must be canvas") } context = canvas.getContext("2d") canvas.style.backgroundColor = self.option.background canvas.style.touchAction = "none" function syncHistoryIndex() { self.step = history.length - 1 } function clearCanvas() { context.clearRect(0, 0, canvas.width, canvas.height) } function drawImage(dataUrl) { clearCanvas() if (!dataUrl) { return } var image = new Image() image.onload = function () { context.drawImage(image, 0, 0) } image.src = dataUrl } function saveSnapshot() { var dataUrl = canvas.toDataURL("image/png") if (history.length === 0 || history[history.length - 1] !== dataUrl) { history.push(dataUrl) syncHistoryIndex() } } function getPoint(event) { var source = event var rect = canvas.getBoundingClientRect() if (event.touches && event.touches.length > 0) { source = event.touches[0] } else if (event.changedTouches && event.changedTouches.length > 0) { source = event.changedTouches[0] } return { x: source.clientX - rect.left, y: source.clientY - rect.top } } function preventDefault(event) { if (event && event.cancelable) { event.preventDefault() } } function startDraw(event) { preventDefault(event) drawing = true currentPoint = getPoint(event) } function moveDraw(event) { if (!drawing || !currentPoint) { return } preventDefault(event) var nextPoint = getPoint(event) context.beginPath() context.moveTo(currentPoint.x, currentPoint.y) context.lineTo(nextPoint.x, nextPoint.y) context.closePath() context.stroke() currentPoint = nextPoint } function endDraw(event) { if (!drawing) { return } preventDefault(event) drawing = false currentPoint = null activePointerId = null saveSnapshot() } // Prefer capability detection so Harmony / modern WebView devices do not rely on UA strings. function bindEvents() { if (window.PointerEvent) { canvas.addEventListener("pointerdown", function (event) { activePointerId = event.pointerId if (canvas.setPointerCapture) { canvas.setPointerCapture(event.pointerId) } startDraw(event) }, { passive: false }) canvas.addEventListener("pointermove", function (event) { if (activePointerId !== null && event.pointerId !== activePointerId) { return } moveDraw(event) }, { passive: false }) canvas.addEventListener("pointerup", endDraw, { passive: false }) canvas.addEventListener("pointercancel", endDraw, { passive: false }) return } if ("ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0) { canvas.addEventListener("touchstart", startDraw, { passive: false }) canvas.addEventListener("touchmove", moveDraw, { passive: false }) canvas.addEventListener("touchend", endDraw, { passive: false }) canvas.addEventListener("touchcancel", endDraw, { passive: false }) return } canvas.addEventListener("mousedown", startDraw, false) canvas.addEventListener("mousemove", moveDraw, false) canvas.addEventListener("mouseup", endDraw, false) canvas.addEventListener("mouseleave", endDraw, false) } self.create = function (option) { var currentOption = option || self.option context.lineWidth = currentOption.lineWidth context.lineCap = "round" context.lineJoin = "round" context.strokeStyle = currentOption.lineColor if (currentOption.overflow === true && document.body) { document.body.style.overflow = "hidden" } self.firstStatus = canvas.toDataURL("image/png") history.length = 0 history.push(self.firstStatus) syncHistoryIndex() bindEvents() } self.getContentBase64 = function (rotate, callback) { if (!rotate) { var dataUrl = canvas.toDataURL("image/png") if (callback) { callback(dataUrl) } return dataUrl } var tempCanvas = document.createElement("canvas") tempCanvas.width = canvas.offsetHeight || canvas.height tempCanvas.height = canvas.offsetWidth || canvas.width var tempContext = tempCanvas.getContext("2d") var image = new Image() image.onload = function () { var centerX = Math.round(tempCanvas.width / 2) var centerY = Math.round(tempCanvas.height / 2) tempContext.save() tempContext.translate(centerX, centerY) tempContext.rotate(-90 * Math.PI / 180) tempContext.drawImage(image, -image.width / 2, -image.height / 2) tempContext.restore() if (callback) { callback(tempCanvas.toDataURL("image/png")) } } image.src = self.getContentBase64(false) } self.getBlob = function (dataUrl) { var content = dataUrl || self.getContentBase64() var parts = content.split(",") var mime = parts[0].match(/:(.*?);/)[1] var binary = atob(parts[1]) var length = binary.length var bytes = new Uint8Array(length) while (length--) { bytes[length] = binary.charCodeAt(length) } return new Blob([bytes], { type: mime }) } self.getFile = function (fileName, dataUrl) { var content = dataUrl || self.getContentBase64() var name = fileName || "file" var parts = content.split(",") var mime = parts[0].match(/:(.*?);/)[1] var suffix = mime.split("/")[1] var binary = atob(parts[1]) var length = binary.length var bytes = new Uint8Array(length) while (length--) { bytes[length] = binary.charCodeAt(length) } return new File([bytes], name + "." + suffix, { type: mime }) } self.reset = function () { if (!self.firstStatus) { return false } drawImage(self.firstStatus) history.length = 0 history.push(self.firstStatus) syncHistoryIndex() return true } self.undo = function (callback) { if (history.length <= 1 || self.step <= 0) { console.warn("no more undo") if (callback) { callback() } return } self.step -= 1 drawImage(history[self.step]) } self.redo = function (callback) { if (self.step >= history.length - 1) { console.warn("no more redo") if (callback) { callback() } return } self.step += 1 drawImage(history[self.step]) } } return Signature });