uni-app小程序使用canvas绘制图片、在图片上涂鸦、撤销上一步、保存图片到相册

image.png

需求

  • 在canvas插入图片当做背景图
  • 在图片上进行涂鸦、图画
  • 可以更换画笔粗细、颜色
  • 可以实现撤销上一步操作
  • 最后可以同图片一起保存至本地相册或者上传至服务器

功能注意点

  • 使用uni.getSystemInfo({})获取设备的宽高作为canvas区域及绘图区域
  • 绘图时,不可以直接使用网络图片,使用wx.getImageInfo({})获取图片信息做为绘图路径

撤销操作逻辑

  • 绘图步骤start->move->end
  • 在每次start的时候调用保存图片的方法,将当前画布的图片保存在本地的数组中
  • 点击撤销的时候,删除保存数组中的最后一个图片地址,并重画这个地址的图片(drawImage)

以下为全部代码

HTML

<template>
    <view class="container">
        <!--画布区域-->
        <cover-view class="topBox">
            <button class="backStep" type="default" @click="backStep" size="mini">撤销</button>
            <button class="saveBtn" type="warn" @click="saveimg" size="mini">保存</button>
        </cover-view>
        <view class="canvas_area">
            <canvas
                canvas-id="myCanvas"
                class="myCanvas"
                disable-scroll="false"
                @touchstart="touchStart"
                @touchmove="touchMove"
                @touchend="touchEnd"
                :style="{ width: w + 'px', height: h + 'px' }"
            ></canvas>
        </view>
        <!--画布工具区域-->
        <view class="canvas_tools">
            <view class="box box1" @click="penSelect(3)"></view>
            <view class="box box2" @click="penSelect(15)"></view>
            <view class="box box3" @click="colorSelect('#cc0033')"></view>
            <view class="box box4" @click="colorSelect('#ff9900')"></view>
            <view class="box box5" @click="colorSelect('#00CD00')"></view>
        </view>
    </view>
</template>

JS

<script>
export default {
    data() {
        return {
            imgurl: '',
            pen: 3, //画笔粗细默认值
            color: '#cc0033', //画笔颜色默认值
            startX: 0, //保存X坐标轴变量
            startY: 0, //保存X坐标轴变量
            w: '', //canvas宽高区域
            h: '',
            ctx: wx.createContext('myCanvas', this),
            allDrawWorksPath: [] //图片路径 用于撤销
        };
    },
    onLoad(option) {
        //真实环境为后端返回图片路径
        this.imgurl = 'https://ss3.baidu.com/9fo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/72f082025aafa40f2982756baa64034f78f0193b.jpg';
        this.getSystemInfo(); //获取设备信息
    },
    onReady(option) {
        var that = this;
        console.log('imgurl', that.imgurl);
        wx.getImageInfo({
            src: that.imgurl,
            success: function(ress) {
                console.log('图片信息', ress);
                that.ctx.drawImage(ress.path, 0, 0, that.w, that.h);
                that.ctx.stroke();
                wx.drawCanvas({
                    canvasId: 'myCanvas',
                    reserve: true,
                    actions: that.ctx.getActions() // 获取绘图动作数组
                });
            },
            fail(err) {
                console.log('err', err);
            }
        });
    },
    methods: {
        // 保存图片
        saveimg() {
            var _this = this;
            this.ctx.draw();
            setTimeout(function() {
                _this.drawAfter();
            }, 500);
        },
        // 保存
        drawAfter() {
            let that = this;
            wx.canvasToTempFilePath(
                {
                    width: that.w, //686
                    height: that.h,
                    canvasId: 'myCanvas',
                    success: function success(res) {
                        console.log('保存图片', res);
                        var tempFilePath = res.tempFilePath;
                        console.log(tempFilePath);
                        //把图片保存到相册
                        wx.saveImageToPhotosAlbum({
                            filePath: tempFilePath
                        });
                        //把图片保存到相册
                        //进行文件的拷贝
                        //上传
                    }
                },
                this
            );
        },
        // 开始
        touchStart(e) {
            console.log('开始绘制');
            //得到触摸点的坐标
            this.startX = e.changedTouches[0].x;
            this.startY = e.changedTouches[0].y;
            console.log('画笔颜色', this.color, this.pen);
            this.ctx.setStrokeStyle(this.color); //画笔颜色
            this.ctx.setLineWidth(this.pen); //线条宽度
            this.ctx.setLineCap('round'); // 让线条圆润
            this.ctx.beginPath();
            this.saveCurrentDrawWorks(); //记录每一步步骤-撤销
        },
        //手指触摸后移动
        touchMove(e) {
            console.log('开始移动中');
            var startX1 = e.changedTouches[0].x;
            var startY1 = e.changedTouches[0].y;
            this.ctx.moveTo(this.startX, this.startY);
            this.ctx.lineTo(startX1, startY1);
            this.ctx.stroke();
            this.startX = startX1;
            this.startY = startY1;

            //只是一个记录方法调用的容器,用于生成记录绘制行为的actions数组。
            // context跟<canvas/>不存在对应关系,一个context生成画布的绘制动作数组可以应用于多个<canvas/>
            wx.drawCanvas({
                canvasId: 'myCanvas',
                reserve: true,
                actions: this.ctx.getActions() // 获取绘图动作数组
            });
        },
        //手指触摸动作结束
        touchEnd() {
            console.log('停止');
        },
        //更改画笔大小的方法
        penSelect(e) {
            console.log(e);
            this.pen = e;
        },
        //更改画笔颜色的方法
        colorSelect(e) {
            console.log(e);
            this.color = e;
        },
        // 保存每一步操作
        saveCurrentDrawWorks() {
            let that = this;
            wx.canvasToTempFilePath({
                x: 0,
                y: 0,
                width: 0,
                height: 0,
                canvasId: 'myCanvas',
                success: function(res) {
                    var imgPath = res.tempFilePath;
                    that.allDrawWorksPath = [...that.allDrawWorksPath, imgPath];
                    console.log('步骤', that.allDrawWorksPath);
                },
                fail: res => {
                    console.log('获取画布图片失败', res);
                }
            });
        },
        // 撤销一步
        backStep() {
            var allDrawWorksPath = this.allDrawWorksPath;
            if (allDrawWorksPath == null || allDrawWorksPath.length == 0 || allDrawWorksPath == undefined) {
                uni.showToast({
                    icon: 'none',
                    title: '已经撤销到起始位置'
                });
                this.allDrawWorksPath = [];
                return;
            }
            var privWorksPath = allDrawWorksPath.pop();
            this.allDrawWorksPath = allDrawWorksPath;

            this.ctx.drawImage(privWorksPath, 0, 0, this.w, this.h);
            this.ctx.stroke();
            let that = this;
            wx.drawCanvas({
                canvasId: 'myCanvas',
                reserve: true,
                actions: that.ctx.getActions() // 获取绘图动作数组
            });
        },
        // 获取设备信息
        getSystemInfo() {
            let that = this;
            uni.getSystemInfo({
                success(res) {
                    console.log('设备信息', res);
                    that.h = res.windowHeight; //若加底部操作区域 需-60
                    that.w = res.windowWidth;
                }
            });
        }
    }
};
</script>

CSS

<style lang="scss">
.container {
    position: relative;
    width: 100%;
    height: 100%;
    .topBox {
        position: fixed;
        z-index: 99999;
        width: 100%;
        .backStep {
            width: 50px;
            top: 2px;
            left: 2px;
        }
        .saveBtn {
            width: 50px;
            position: absolute;
            top: 2px;
            right: 2px;
        }
    }
    .canvas_tools {
        position: fixed;
        left: 0;
        bottom: 0;
        width: 100%;
        height: 60px;
        display: flex;
        flex-direction: row;
        justify-content: space-around;
        background-color: #eee;
        align-items: center;
    }
    .box {
        width: 100rpx;
        height: 100rpx;
        line-height: 100rpx;
        border-radius: 50%;
        background-color: rebeccapurple;
    }
    .box1 {
        background-color: #99cccc;
        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgCAYAAAAbifjMAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH3gUEFTU3owPJ+gAAArpJREFUSMeVlU9IVFEUxn/nvjdTaTQZYyaaZlEpIfhvkVSMSAmBFRZpYYugbcs2QbWpXUFKC3chLdpFUFHQpqxE2rQ0ahEp5BRm6TgqODP3tpg78nzznukHF+7lnfOd73zn8q7gw8jISGFbCzQCLvAGmANIJBKr4l3CcRqIAhVAFngRFKRCqu8ALtkCMeAyEPHFFBN40Am0Ajm7TgDNayrwMEeBi4ADGLu2ARf+24JFE9AR4kmdv40ggnPWAz/2Ad2BCjyMVbZSGHqtqSs5fgVdwIE1CFqBo6sUeKpvAc4TPplCTJ81uEhBM9Ae0KKf8DjQUGjD+7EH2G73WeAV8A54DoySHydAJXDGr6DK5/AjK/Uj8NKSP/EVi3sJOoH9dv8XGALmPQnTwCCQsudGIFEg2Ex+9gVj5m2CH0kgbfdRa3hEAW3AEU9gHDgUQFAPlHnOHUCLIn/v454PJcANOxUFiJV83Y6xgAqg3wVOBlRrB54BY4AGDpP/wfjRrYAHHnO8qLZ99oUkp4EhZd29AyyzfuSAe8B9ZSUOAgN2vx48BO4CWac1vsTS3HSurffKh9mpHzsRaRFRIqIIWko5T8t2H7w6/np4Pjk+itw4tRdgl85lmsqq6ytr2rpuupFNdcba74XOZWeS42O3k5/Hvjpu9AvwTW6dbcAY0yMij0VEKaUcUcoxBrRe3ZExWhutsyLiGGOuiciAW3WsH7RRynGiNbW1amtpKQALi4tMTkz4SRT5W4gx2lHKwS0prwOjcdyIKd9TTywWAyCVSvF7SRWpWFGjNcpxcTPpGYwxOI5L+s9PJLOQH3I6zXJ6Bp3TxWYYMMYSTL4dBmMQEabeRxClVipkMpnQORoDogQ3szAL1nH/TRLCYQAl4IoIxs5srYRABgE3p/OPj5gNpeefLA3up++/CueNwgD8A0fm3rIoh3Y0AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTA5LTE3VDE1OjIwOjM4KzA4OjAwDAwhawAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNC0wNS0wNFQyMTo1Mzo1NSswODowMMTKtgsAAABNdEVYdHNvZnR3YXJlAEltYWdlTWFnaWNrIDcuMC4xLTYgUTE2IHg4Nl82NCAyMDE2LTA5LTE3IGh0dHA6Ly93d3cuaW1hZ2VtYWdpY2sub3Jn3dmlTgAAABh0RVh0VGh1bWI6OkRvY3VtZW50OjpQYWdlcwAxp/+7LwAAABl0RVh0VGh1bWI6OkltYWdlOjpIZWlnaHQAMTA2Mx0uiBoAAAAXdEVYdFRodW1iOjpJbWFnZTo6V2lkdGgANTM3XiCV0QAAABl0RVh0VGh1bWI6Ok1pbWV0eXBlAGltYWdlL3BuZz+yVk4AAAAXdEVYdFRodW1iOjpNVGltZQAxMzk5MjExNjM1VZFQ9AAAABJ0RVh0VGh1bWI6OlNpemUAMTguNEtCh1extQAAAF90RVh0VGh1bWI6OlVSSQBmaWxlOi8vL2hvbWUvd3d3cm9vdC9zaXRlL3d3dy5lYXN5aWNvbi5uZXQvY2RuLWltZy5lYXN5aWNvbi5jbi9zcmMvMTE2MDIvMTE2MDI0NC5wbmcC2t7tAAAAAElFTkSuQmCC);
        background-repeat: no-repeat;
        background-position: center;
    }
    .box2 {
        background-color: #0099cc;
        background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAeCAMAAAAvtQ9FAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAACf1BMVEUAAAB8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiN8TiNINiWHUyJ+TyN9TyN9TyN9TyN9TyN+TyOPVyJBMyYoKCgpKChaPyVkQyVjQyVjQyVjQyVjQyVTPCYmJygoKCgfHx8eHh4fHx4gHx4gHx4gHx4gHx4gHx4eHh4eHh4fHx8TExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExITExIHBwcHBwcHBwcHBwd8TiMTExITExITExITExIHBwf////3ECwAAAAAznRSTlMAAAAARX4EAAAAAFLkqwIAAAAAVOv/vQkAAAA54v//4CEAAAARvP////1qAAAAAGj9/////9o1AAAAFMb/////2WgXAE70//////7BLI///////6O+/////+fY///84f///dz///HI/////9Wl/////6hy/f///G056////+YxDr7/////sggAcv////1hAAAg3P/////JEwBMTLj//v7//pxKS+j7+//////++vvi1f///////////83V///O1f//ztX//87V///O1f//zSidjOsAAAABYktHRNQJuwuFAAAAB3RJTUUH3gUEFTU21AT5bAAAAS9JREFUGNNjYGBgZGJmYWVjZwABDk4ubh5ePn4QW0BQSFhEVExcAsSRlJKWkZWTV1BUAnKUVVTV1DU0tbR1dBkY9PQNDI2MTUzNzC0sGaysbWzPnbOzd3B0cmZwcXU7d+6cu4enl7cPg6+f/zkQCAgMCmYICQ0Dc86FR0QyREWfg4KYWIa4eBgnIZEhKRnGSUllSEvPgLAzs7IZcnLzIJz8gkKGouISCKe0rJyhorIKzK6uqa1jqG9obAJxmlta2xjaOzq7QBLdPb19DP0TJk6aPHnylKnTps9gmDlr9py58+bNX7Bw0WKGJUuXLV+xcuWq1WvWrmNYv2Hjps1btmzdtn3HToZdu89DwZ69DPv2X4CCAwfxcA4dvggFR44yHDt+CQpOnGQ4dfoyFJw5CwBZdsIQZAP16AAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNi0wOS0xN1QxNToyMDozOCswODowMAwMIWsAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTQtMDUtMDRUMjE6NTM6NTQrMDg6MDBivb2/AAAATXRFWHRzb2Z0d2FyZQBJbWFnZU1hZ2ljayA3LjAuMS02IFExNiB4ODZfNjQgMjAxNi0wOS0xNyBodHRwOi8vd3d3LmltYWdlbWFnaWNrLm9yZ93ZpU4AAAAYdEVYdFRodW1iOjpEb2N1bWVudDo6UGFnZXMAMaf/uy8AAAAZdEVYdFRodW1iOjpJbWFnZTo6SGVpZ2h0ADEwNjMdLogaAAAAF3RFWHRUaHVtYjo6SW1hZ2U6OldpZHRoADQyNaj3r4sAAAAZdEVYdFRodW1iOjpNaW1ldHlwZQBpbWFnZS9wbmc/slZOAAAAF3RFWHRUaHVtYjo6TVRpbWUAMTM5OTIxMTYzNCKWYGIAAAASdEVYdFRodW1iOjpTaXplADEyLjdLQs+hF00AAABfdEVYdFRodW1iOjpVUkkAZmlsZTovLy9ob21lL3d3d3Jvb3Qvc2l0ZS93d3cuZWFzeWljb24ubmV0L2Nkbi1pbWcuZWFzeWljb24uY24vc3JjLzExNjAyLzExNjAyNDMucG5nsPoC/QAAAABJRU5ErkJggg==);
        background-repeat: no-repeat;
        background-position: center;
    }
    .box3 {
        background-color: #cc0033;
    }
    .box4 {
        background-color: #ff9900;
    }
    .box5 {
        background-color: #00cd00;
    }
    .box6 {
        text-align: center;
        color: #fff;
    }
}
</style>
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容