需求
- 在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>