diff --git a/src/assets/images/signature-undo.svg b/src/assets/images/signature-undo.svg new file mode 100644 index 0000000..518a37f --- /dev/null +++ b/src/assets/images/signature-undo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/signature/Signature.ts b/src/components/signature/Signature.ts index ad9d33c..e045d8d 100644 --- a/src/components/signature/Signature.ts +++ b/src/components/signature/Signature.ts @@ -16,33 +16,36 @@ export interface ISignatureOptions { } export class Signature { - private x: number - private y: number - private isDrawing: boolean + private readonly MAX_RECORD_COUNT = 1000 + private undoStack: Array = [] + private x = 0 + private y = 0 + private isDrawing = false + private isDrawn = false private canvasWidth: number private canvasHeight: number private options: ISignatureOptions private mask: HTMLDivElement private container: HTMLDivElement private trashContainer: HTMLDivElement + private undoContainer: HTMLDivElement private canvas: HTMLCanvasElement private ctx: CanvasRenderingContext2D constructor(options: ISignatureOptions) { - this.x = 0 - this.y = 0 - this.isDrawing = false this.options = options this.canvasWidth = options.width || 390 this.canvasHeight = options.height || 180 - const { mask, container, trashContainer, canvas } = this._render() + const { mask, container, trashContainer, undoContainer, canvas } = this._render() this.mask = mask this.container = container this.trashContainer = trashContainer + this.undoContainer = undoContainer this.canvas = canvas this.ctx = canvas.getContext('2d') this.ctx.lineCap = 'round' this._bindEvent() + this._clearUndoFn() } private _render() { @@ -79,11 +82,21 @@ export class Signature { // 操作区 const operationContainer = document.createElement('div') operationContainer.classList.add('signature-operation') + // 撤销 + const undoContainer = document.createElement('div') + undoContainer.classList.add('signature-operation__undo') + const undoIcon = document.createElement('i') + const undoLabel = document.createElement('span') + undoLabel.innerText = '撤销' + undoContainer.append(undoIcon) + undoContainer.append(undoLabel) + operationContainer.append(undoContainer) + // 清空画布 const trashContainer = document.createElement('div') trashContainer.classList.add('signature-operation__trash') const trashIcon = document.createElement('i') const trashLabel = document.createElement('span') - trashLabel.innerText = '清空画布' + trashLabel.innerText = '清空' trashContainer.append(trashIcon) trashContainer.append(trashLabel) operationContainer.append(trashContainer) @@ -137,18 +150,44 @@ export class Signature { mask, canvas, container, - trashContainer + trashContainer, + undoContainer } } private _bindEvent() { this.trashContainer.onclick = this._clearCanvas.bind(this) + this.undoContainer.onclick = this._undo.bind(this) this.canvas.onmousedown = this._startDraw.bind(this) this.canvas.onmousemove = this._draw.bind(this) this.container.onmouseup = this._stopDraw.bind(this) } + private _undo() { + if (this.undoStack.length > 1) { + this.undoStack.pop() + if (this.undoStack.length) { + this.undoStack[this.undoStack.length - 1]() + } + } + } + + private _saveUndoFn(fn: Function) { + this.undoStack.push(fn) + while (this.undoStack.length > this.MAX_RECORD_COUNT) { + this.undoStack.shift() + } + } + + private _clearUndoFn() { + const clearFn = () => { + this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight) + } + this.undoStack = [clearFn] + } + private _clearCanvas() { + this._clearUndoFn() this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasHeight) } @@ -168,10 +207,20 @@ export class Signature { this.ctx.stroke() this.x = offsetX this.y = offsetY + this.isDrawn = true } private _stopDraw() { this.isDrawing = false + if (this.isDrawn) { + const imageData = this.ctx.getImageData(0, 0, this.canvasWidth, this.canvasHeight) + const self = this + this._saveUndoFn(function () { + self.ctx.clearRect(0, 0, self.canvasWidth, self.canvasHeight) + self.ctx.putImageData(imageData, 0, 0) + }) + this.isDrawn = false + } } private _toDataURL() { diff --git a/src/components/signature/signature.css b/src/components/signature/signature.css index e7121d8..98050eb 100644 --- a/src/components/signature/signature.css +++ b/src/components/signature/signature.css @@ -50,7 +50,7 @@ background: url(../../assets/images/close.svg); } -.signature-operation__trash { +.signature-operation>div { cursor: pointer; display: inline-flex; align-items: center; @@ -58,30 +58,36 @@ user-select: none; } -.signature-operation__trash:hover { +.signature-operation>div:hover { color: #6e7175; } -.signature-operation__trash i { +.signature-operation>div i { width: 24px; height: 24px; display: inline-block; +} + +.signature-operation__undo { + background: url(../../assets/images/signature-undo.svg) no-repeat; +} + +.signature-operation__trash { background: url(../../assets/images/trash.svg) no-repeat; } -.signature-operation__trash span { +.signature-operation>div span { font-size: 12px; - margin-left: 5px; + margin: 0 5px; } .signature-canvas { margin: 15px 0; - border: 1px solid #e9e9e9; user-select: none; } .signature-canvas canvas { - background: #fbfbfb; + background: #f3f5f7; } .signature-menu {