uniapp小程序签名板/手绘

实现过程

  1. 透明背景的canvas画布,监听touch事件(开始,移动,结束),移动时计算两点之间的距离绘制图像
  2. 获取名字和米字格绝对定位做背景
  3. 导出

总html代码

    <view class="handCenter">
        <view class="background-box">
          <view class="name-item" v-for="item in name" :key="item">
            <text>{{ item }}</text>
            <image
              v-if="name.length <= 3"
              class="name-border"
              :src="米字格图片"
              mode="scaleToFill"
            />
          </view>
        </view>
        <canvas
          class="handWriting"
          :disable-scroll="true"
          @touchstart="uploadScaleStart"
          @touchmove="uploadScaleMove"
          @touchend="uploadScaleEnd"
          canvas-id="handWriting"
        ></canvas>
    </view>
    <view class="bottom-group">
        <u-button :customStyle="{width: '80rpx', height: '30rpx', margin: '0 0 0 10rpx'}" color="#cbcbcb" @click="retDraw" text="重写"></u-button>
        <u-button :customStyle="{width: '80rpx', height: '30rpx', margin: '0 0 0 10rpx'}" color="#346EFF" @click="setBgColor" text="保存"></u-button>
    </view>

绘制图像
通过touchstart,touchmove, touchend监听绘制笔迹

        // 笔迹开始
        uploadScaleStart(e) {
            if (e.type != 'touchstart') return false;
            let ctx = this.ctx;
            ctx.setFillStyle(this.lineColor); // 初始线条设置颜色
            ctx.setGlobalAlpha(this.transparent); // 设置半透明
            let currentPoint = {
                x: e.touches[0].x,
                y: e.touches[0].y
            };
            let currentLine = this.currentLine;
            currentLine.unshift({
                time: new Date().getTime(),
                dis: 0,
                x: currentPoint.x,
                y: currentPoint.y
            });
            this.currentPoint = currentPoint;
            if (this.firstTouch) {
                this.cutArea = { top: currentPoint.y, right: currentPoint.x, bottom: currentPoint.y, left: currentPoint.x };
                this.firstTouch = false;
            }
            this.pointToLine(currentLine);
        },
        // 笔迹移动
        uploadScaleMove(e) {
            if (e.type != 'touchmove') return false;
            if (e.cancelable) {
                // 判断默认行为是否已经被禁用
                if (!e.defaultPrevented) {
                    e.preventDefault();
                }
            }
            let point = {
                x: e.touches[0].x,
                y: e.touches[0].y
            };

            //测试裁剪
            if (point.y < this.cutArea.top) {
                this.cutArea.top = point.y;
            }
            if (point.y < 0) this.cutArea.top = 0;

            if (point.x > this.cutArea.right) {
                this.cutArea.right = point.x;
            }
            if (this.canvasWidth - point.x <= 0) {
                this.cutArea.right = this.canvasWidth;
            }
            if (point.y > this.cutArea.bottom) {
                this.cutArea.bottom = point.y;
            }
            if (this.canvasHeight - point.y <= 0) {
                this.cutArea.bottom = this.canvasHeight;
            }
            if (point.x < this.cutArea.left) {
                this.cutArea.left = point.x;
            }
            if (point.x < 0) this.cutArea.left = 0;

            this.lastPoint = this.currentPoint;
            this.currentPoint = point;

            let currentLine = this.currentLine;
            currentLine.unshift({
                time: new Date().getTime(),
                dis: this.distance(this.currentPoint, this.lastPoint),
                x: point.x,
                y: point.y
            });

            this.pointToLine(currentLine);
        },
        // 笔迹结束
        uploadScaleEnd(e) {
            if (e.type != 'touchend') return 0;
            let point = {
                x: e.changedTouches[0].x,
                y: e.changedTouches[0].y
            };
            this.lastPoint = this.currentPoint;
            this.currentPoint = point;

            let currentLine = this.currentLine;
            currentLine.unshift({
                time: new Date().getTime(),
                dis: this.distance(this.currentPoint, this.lastPoint),
                x: point.x,
                y: point.y
            });

            if (currentLine.length > 2) {
                var info = (currentLine[0].time - currentLine[currentLine.length - 1].time) / currentLine.length;
                //$("#info").text(info.toFixed(2));
            }
            //一笔结束,保存笔迹的坐标点,清空,当前笔迹
            //增加判断是否在手写区域;
            this.pointToLine(currentLine);
            var currentChirography = {
                lineSize: this.lineSize,
                lineColor: this.lineColor
            };
            var chirography = this.chirography;
            chirography.unshift(currentChirography);
            this.chirography = chirography;

            var linePrack = this.linePrack;
            linePrack.unshift(this.currentLine);
            this.linePrack = linePrack;
            this.currentLine = [];
        },
        retDraw() {
            this.ctx.clearRect(0, 0, 700, 730);
            this.ctx.draw();
        },
        //画两点之间的线条;参数为:line,会绘制最近的开始的两个点;
        pointToLine(line) {
            this.calcBethelLine(line);
            return;
        },
        //计算插值的方式;
        calcBethelLine(line) {
            if (line.length <= 1) {
                line[0].r = this.radius;
                return;
            }
            let x0,
                x1,
                x2,
                y0,
                y1,
                y2,
                r0,
                r1,
                r2,
                len,
                lastRadius,
                dis = 0,
                time = 0,
                curveValue = 0.5;
            if (line.length <= 2) {
                x0 = line[1].x;
                y0 = line[1].y;
                x2 = line[1].x + (line[0].x - line[1].x) * curveValue;
                y2 = line[1].y + (line[0].y - line[1].y) * curveValue;
                //x2 = line[1].x;
                //y2 = line[1].y;
                x1 = x0 + (x2 - x0) * curveValue;
                y1 = y0 + (y2 - y0) * curveValue;
            } else {
                x0 = line[2].x + (line[1].x - line[2].x) * curveValue;
                y0 = line[2].y + (line[1].y - line[2].y) * curveValue;
                x1 = line[1].x;
                y1 = line[1].y;
                x2 = x1 + (line[0].x - x1) * curveValue;
                y2 = y1 + (line[0].y - y1) * curveValue;
            }
            //从计算公式看,三个点分别是(x0,y0),(x1,y1),(x2,y2) ;(x1,y1)这个是控制点,控制点不会落在曲线上;实际上,这个点还会手写获取的实际点,却落在曲线上
            len = this.distance({ x: x2, y: y2 }, { x: x0, y: y0 });
            lastRadius = this.radius;
            for (let n = 0; n < line.length - 1; n++) {
                dis += line[n].dis;
                time += line[n].time - line[n + 1].time;
                if (dis > this.smoothness) break;
            }

            this.radius = Math.min((time / len) * this.pressure + this.lineMin, this.lineMax) * this.lineSize;
            line[0].r = this.radius;
            //计算笔迹半径;
            if (line.length <= 2) {
                r0 = (lastRadius + this.radius) / 2;
                r1 = r0;
                r2 = r1;
                //return;
            } else {
                r0 = (line[2].r + line[1].r) / 2;
                r1 = line[1].r;
                r2 = (line[1].r + line[0].r) / 2;
            }
            let n = 5;
            let point = [];
            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);
                    a[0].color = this.lineColor;
                    // let bethelPoint = this.bethelPoint;
                    // bethelPoint = bethelPoint.push(a);
                    this.bethelDraw(a, 1);
                    point = [{ x: x, y: y, r: r }];
                }
            }
            this.currentLine = line;
        },
        //求两点之间距离
        distance(a, b) {
            let x = b.x - a.x;
            let y = b.y - a.y;
            return Math.sqrt(x * x + y * y);
        },
        ctaCalc(x0, y0, r0, x1, y1, r1, x2, y2, r2) {
            let a = [],
                vx01,
                vy01,
                norm,
                n_x0,
                n_y0,
                vx21,
                vy21,
                n_x2,
                n_y2;
            vx01 = x1 - x0;
            vy01 = y1 - y0;
            norm = Math.sqrt(vx01 * vx01 + vy01 * vy01 + 0.0001) * 2;
            vx01 = (vx01 / norm) * r0;
            vy01 = (vy01 / norm) * r0;
            n_x0 = vy01;
            n_y0 = -vx01;
            vx21 = x1 - x2;
            vy21 = y1 - y2;
            norm = Math.sqrt(vx21 * vx21 + vy21 * vy21 + 0.0001) * 2;
            vx21 = (vx21 / norm) * r2;
            vy21 = (vy21 / norm) * r2;
            n_x2 = -vy21;
            n_y2 = vx21;
            a.push({ mx: x0 + n_x0, my: y0 + n_y0, color: '#1A1A1A' });
            a.push({ c1x: x1 + n_x0, c1y: y1 + n_y0, c2x: x1 + n_x2, c2y: y1 + n_y2, ex: x2 + n_x2, ey: y2 + n_y2 });
            a.push({ c1x: x2 + n_x2 - vx21, c1y: y2 + n_y2 - vy21, c2x: x2 - n_x2 - vx21, c2y: y2 - n_y2 - vy21, ex: x2 - n_x2, ey: y2 - n_y2 });
            a.push({ c1x: x1 - n_x2, c1y: y1 - n_y2, c2x: x1 - n_x0, c2y: y1 - n_y0, ex: x0 - n_x0, ey: y0 - n_y0 });
            a.push({ c1x: x0 - n_x0 - vx01, c1y: y0 - n_y0 - vy01, c2x: x0 + n_x0 - vx01, c2y: y0 + n_y0 - vy01, ex: x0 + n_x0, ey: y0 + n_y0 });
            a[0].mx = a[0].mx.toFixed(1);
            a[0].mx = parseFloat(a[0].mx);
            a[0].my = a[0].my.toFixed(1);
            a[0].my = parseFloat(a[0].my);
            for (let i = 1; i < a.length; i++) {
                a[i].c1x = a[i].c1x.toFixed(1);
                a[i].c1x = parseFloat(a[i].c1x);
                a[i].c1y = a[i].c1y.toFixed(1);
                a[i].c1y = parseFloat(a[i].c1y);
                a[i].c2x = a[i].c2x.toFixed(1);
                a[i].c2x = parseFloat(a[i].c2x);
                a[i].c2y = a[i].c2y.toFixed(1);
                a[i].c2y = parseFloat(a[i].c2y);
                a[i].ex = a[i].ex.toFixed(1);
                a[i].ex = parseFloat(a[i].ex);
                a[i].ey = a[i].ey.toFixed(1);
                a[i].ey = parseFloat(a[i].ey);
            }
            return a;
        },
        bethelDraw(point, is_fill, color) {
            let ctx = this.ctx;
            ctx.beginPath();
            ctx.moveTo(point[0].mx, point[0].my);
            if (undefined != color) {
                ctx.setFillStyle(color);
                ctx.setStrokeStyle(color);
            } else {
                ctx.setFillStyle(point[0].color);
                ctx.setStrokeStyle(point[0].color);
            }
            for (let i = 1; i < point.length; i++) {
                ctx.bezierCurveTo(point[i].c1x, point[i].c1y, point[i].c2x, point[i].c2y, point[i].ex, point[i].ey);
            }
            ctx.stroke();
            if (undefined != is_fill) {
                ctx.fill(); //填充图形 ( 后绘制的图形会覆盖前面的图形, 绘制时注意先后顺序 )
            }
            ctx.draw(true);
        },

清空画布

retDraw() {
            // 清空矩形的宽高只可大不可小
            this.ctx.clearRect(0, 0, 700, 730);
            this.ctx.draw();
        },

导出图片

exportImg() {
            wx.canvasToTempFilePath({
                canvasId: 'handWriting',
                fileType: 'png',
                quality: 1, //图片质量
                success(res) {
                    wx.saveImageToPhotosAlbum({
                        filePath: res.tempFilePath,
                        success(res) {
                            wx.showToast({
                                title: '已保存到相册',
                                duration: 2000
                            });
                        }
                    });
                }
            });
        },

实现效果如图
tip: 小程序设置页面横屏 "pageOrientation": "landscape"

68e5dd930955692c489e2b0e35251f6.jpg

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容