From 02b0861055e30ed0acbe92bb9e43321276380231 Mon Sep 17 00:00:00 2001
From: Tevin <tingquanren@163.com>
Date: Sat, 15 Mar 2025 16:06:27 +0800
Subject: [PATCH] 小程序手写签名组件,采用新版 Canvas API 重写,解决不能签名的问题

---
 forms/userSignature/CSignatureLayer.vue |  182 ++++++++++++++++++++++++++-------------------
 1 files changed, 106 insertions(+), 76 deletions(-)

diff --git a/forms/userSignature/CSignatureLayer.vue b/forms/userSignature/CSignatureLayer.vue
index 0d9c676..b5d71d1 100644
--- a/forms/userSignature/CSignatureLayer.vue
+++ b/forms/userSignature/CSignatureLayer.vue
@@ -16,10 +16,10 @@
             <canvas
                 class="drawing"
                 ref="drawing"
-                :canvasId="cavId"
-                :width="cavWidth"
-                :height="cavHeight"
+                type="2d"
+                :id="cavId"
                 :disableScroll="true"
+                v-if="cavShow"
                 @touchstart="evt => handleWriteStart(evt)"
                 @touchmove="evt => handleWriteMove(evt)"
                 @touchend="evt => handleWriteEnd(evt)"
@@ -45,8 +45,8 @@
 import Taro from '@tarojs/taro';
 import { $ } from '@tarojs/extend';
 import { AtFloatLayout, AtButton } from 'taro-ui-vue';
-import './cSignatureLayer.scss';
 import { Tools } from '@components/common/Tools';
+import './cSignatureLayer.scss';
 
 export default {
     name: 'CSignatureLayer',
@@ -59,8 +59,7 @@
         return {
             cavId: 'signCanvas-' + Date.now() + '-' + parseInt(Math.random() * 10000),
             layerOpened: false,
-            cavWidth: 0,
-            cavHeight: 0,
+            cavShow: false,
             curPoint: {},
             lastPoint: {},
             curLine: [],
@@ -70,40 +69,63 @@
             chirography: [],
             // 初始画圆的半径
             radius: 1,
+            canvasContext: null,
         };
     },
     methods: {
         _initDraw() {
-            this.canvasContext = Taro.createCanvasContext(this.cavId, this);
-            const $container = $(this.$refs.drawing).parent();
-            setTimeout(() => {
-                $container.width().then(w => (this.cavWidth = w));
-                $container.height().then(h => (this.cavHeight = h));
-            }, 0);
+            const query = Taro.createSelectorQuery();
+            query
+                .select(`#${this.cavId}`)
+                .fields({ node: true, size: true })
+                .exec(res => {
+                    // Canvas 对象
+                    const canvas = res[0].node;
+                    // Canvas 画布的实际绘制宽高
+                    const renderWidth = res[0].width;
+                    const renderHeight = res[0].height;
+                    // Canvas 绘制上下文
+                    this.canvasContext = canvas.getContext('2d');
+                    this.canvas = canvas;
+                    // 初始化画布大小
+                    const dpr = Taro.getSystemInfoSync().pixelRatio;
+                    canvas.width = renderWidth * dpr;
+                    canvas.height = renderHeight * dpr;
+                    this.canvasContext.scale(dpr, dpr);
+                    // 初始化变量
+                    this.handleRestDraw();
+                });
         },
         $onDraw(callback) {
-            this._initDraw();
             this.layerOpened = true;
             this._callback = callback;
-            setTimeout(() => {
-                this.handleRestDraw();
-            }, 10);
+            this.$nextTick(() => {
+                setTimeout(() => {
+                    this.cavShow = true;
+                    setTimeout(() => {
+                        this._initDraw();
+                    }, 100);
+                }, 300);
+            });
         },
         handleRestDraw() {
             this.firstTouch = true;
             this.curLine = [];
             this.chirography = [];
-            this.canvasContext.clearRect(0, 0, this.cavWidth, this.cavHeight);
-            this.canvasContext.draw();
+            this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
         },
         handleClose() {
             this.layerOpened = false;
+            this.cavShow = false;
+            setTimeout(() => {
+                this.canvasContext.clearRect(0, 0, this.canvas.width, this.canvas.height);
+            }, 100);
         },
         handleWriteStart(evt) {
-            if (evt.type != 'touchstart') {
-                return false;
+            if (evt.type !== 'touchstart' || !this.canvasContext) {
+                return;
             }
-            this.canvasContext.setFillStyle('#1A1A1A');
+            this.canvasContext.fillStyle = '#111111';
             const point = {
                 x: evt.touches[0].x,
                 y: evt.touches[0].y,
@@ -126,8 +148,8 @@
             this._pointToLine(this.curLine);
         },
         handleWriteMove(evt) {
-            if (evt.type != 'touchmove') {
-                return false;
+            if (evt.type !== 'touchmove') {
+                return;
             }
             if (evt.cancelable) {
                 // 判断默认行为是否已经被禁用
@@ -151,8 +173,8 @@
             this._pointToLine(this.curLine);
         },
         handleWriteEnd(evt) {
-            if (evt.type != 'touchend') {
-                return 0;
+            if (evt.type !== 'touchend') {
+                return;
             }
             const point = {
                 x: evt.changedTouches[0].x,
@@ -181,14 +203,14 @@
             if (point.x > this.cutArea.right) {
                 this.cutArea.right = point.x;
             }
-            if (this.cavWidth - point.x <= 0) {
-                this.cutArea.right = this.cavWidth;
+            if (this.canvas.width - point.x <= 0) {
+                this.cutArea.right = this.canvas.width;
             }
             if (point.y > this.cutArea.bottom) {
                 this.cutArea.bottom = point.y;
             }
-            if (this.cavHeight - point.y <= 0) {
-                this.cutArea.bottom = this.cavHeight;
+            if (this.canvas.height - point.y <= 0) {
+                this.cutArea.bottom = this.canvas.height;
             }
             if (point.x < this.cutArea.left) {
                 this.cutArea.left = point.x;
@@ -279,27 +301,28 @@
                 r2 = (line[1].r + line[0].r) / 2;
             }
             let n = 5;
-            let point = [];
+            let points = [];
             for (let i = 0; i < n; i++) {
                 let t = i / (n - 1);
                 let x = (1 - t) * (1 - t) * x0 + 2 * t * (1 - t) * x1 + t * t * x2;
                 let y = (1 - t) * (1 - t) * y0 + 2 * t * (1 - t) * y1 + t * t * y2;
                 let r = lastRadius + ((this.radius - lastRadius) / n) * i;
-                point.push({ x: x, y: y, r: r });
-                if (point.length == 3) {
-                    let a = this._ctaCalc(
-                        point[0].x,
-                        point[0].y,
-                        point[0].r,
-                        point[1].x,
-                        point[1].y,
-                        point[1].r,
-                        point[2].x,
-                        point[2].y,
-                        point[2].r,
+                points.push({ x: x, y: y, r: r });
+                if (points.length == 3) {
+                    this._bethelDraw(
+                        this._ctaCalc(
+                            points[0].x,
+                            points[0].y,
+                            points[0].r,
+                            points[1].x,
+                            points[1].y,
+                            points[1].r,
+                            points[2].x,
+                            points[2].y,
+                            points[2].r,
+                        ),
                     );
-                    this._bethelDraw(a, true);
-                    point = [{ x: x, y: y, r: r }];
+                    points = [{ x: x, y: y, r: r }];
                 }
             }
             this.curLine = line;
@@ -382,64 +405,75 @@
             }
             return a;
         },
-        _bethelDraw(point, isFill) {
+        _bethelDraw(points) {
             const ctx = this.canvasContext;
             ctx.beginPath();
-            ctx.moveTo(point[0].mx, point[0].my);
-            for (let i = 1; i < point.length; i++) {
+            ctx.moveTo(points[0].mx, points[0].my);
+            for (let i = 1; i < points.length; i++) {
                 ctx.bezierCurveTo(
-                    point[i].c1x,
-                    point[i].c1y,
-                    point[i].c2x,
-                    point[i].c2y,
-                    point[i].ex,
-                    point[i].ey,
+                    points[i].c1x,
+                    points[i].c1y,
+                    points[i].c2x,
+                    points[i].c2y,
+                    points[i].ex,
+                    points[i].ey,
                 );
             }
+            ctx.closePath();
             ctx.stroke();
-            if (isFill !== undefined) {
-                // 后绘制的图形会覆盖前面的图形, 绘制时注意先后顺序
-                ctx.fill();
-            }
-            ctx.draw(true);
+            ctx.fill();
         },
         _brushingGround(callback) {
             Taro.canvasToTempFilePath({
-                canvasId: this.cavId,
+                canvas: this.canvas,
                 x: 0,
                 y: 0,
-                width: Math.ceil(this.cavWidth),
-                height: Math.ceil(this.cavHeight),
-                destWidth: Math.ceil(this.cavWidth),
-                destHeight: Math.ceil(this.cavHeight),
+                width: Math.ceil(this.canvas.width),
+                height: Math.ceil(this.canvas.height),
+                destWidth: Math.ceil(this.canvas.width),
+                destHeight: Math.ceil(this.canvas.height),
                 quality: 1,
                 fileType: 'png',
                 success: res => {
                     const ctx = this.canvasContext;
-                    ctx.setFillStyle('#ffffff');
-                    ctx.fillRect(0, 0, this.cavWidth, this.cavHeight);
-                    ctx.drawImage(res.tempFilePath, 0, 0);
-                    ctx.draw(false, () => {
+                    ctx.fillStyle = '#ffffff';
+                    ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
+                    // 重新绘制
+                    const image = this.canvas.createImage();
+                    image.onload = () => {
+                        const dpr = Taro.getSystemInfoSync().pixelRatio;
+                        ctx.drawImage(
+                            image,
+                            0,
+                            0,
+                            this.canvas.width / dpr,
+                            this.canvas.height / dpr,
+                        );
                         callback();
-                    });
+                    };
+                    image.src = res.tempFilePath;
                 },
             });
         },
         handleSaveDraw() {
             if (this.firstTouch) {
                 Tools.toast('请书写签名!');
+                return;
             }
             this._brushingGround(() => {
                 const delta = 20;
                 const clipArea = { x: 0, y: 0, w: 0, h: 0 };
                 clipArea.x = Math.max(this.cutArea.left - delta, 0);
                 clipArea.y = Math.max(this.cutArea.top - delta, 0);
-                const realRight = Math.min(this.cutArea.right + delta, this.cavWidth);
-                const realBottom = Math.min(this.cutArea.bottom + delta, this.cavHeight);
+                const realRight = Math.min(this.cutArea.right + delta, this.canvas.width);
+                const realBottom = Math.min(
+                    this.cutArea.bottom + delta,
+                    this.canvas.height,
+                );
                 clipArea.w = realRight - clipArea.x;
                 clipArea.h = realBottom - clipArea.y;
                 Taro.canvasToTempFilePath({
-                    canvasId: this.cavId,
+                    canvas: this.canvas,
                     x: clipArea.x,
                     y: clipArea.y,
                     width: clipArea.w,
@@ -456,10 +490,6 @@
             });
         },
     },
-    mounted() {
-        this.$nextTick(() => {
-            this._initDraw();
-        });
-    },
+    mounted() {},
 };
 </script>
\ No newline at end of file

--
Gitblit v1.9.1