首先感谢作者https://ext.dcloud.net.cn/publisher?id=167918 (675***@qq.com) uniapp插件市场涂鸦示例作者,若作者发现此博客有不妥行为,可联系我进行删除。再此基础上增加涂鸦时对图片大放大缩小、橡皮擦功能功能,保存涂鸦部分使用html2canvas,原作者使用arraybuffer转base64,根据自己方式进行选择。
【主要功能】画笔,图纸的放大缩小,开始画笔进行绘制,关闭画笔进行双指放大、缩小移动,保存绘制截图
整个demo放在百度云盘。
链接:https://pan.baidu.com/s/1alpdzH6cJUTCq9aTOCxjxg
提取码:1024
--来自百度网盘超级会员V4的分享
页面代码index.vue
<template>
<view class="wrap">
<view class="wrap-head">
<text @click="penOpen" v-if="close">开启画笔</text>
<text @click="close=true" v-else class="wrap-head-close">关闭画笔</text>
<text @click="eraser">橡皮擦</text>
<text @click="subCanvas">保存涂鸦</text>
<text @click="renderScript.emitData">截图</text>
</view>
<movable-area :style="'height:100vh;width: 750rpx;top:0px'">
<movable-view :key="j" v-for="(i,j) in ImageInfo" class="handCenter"
:style="{width:i.cavWidth+'px',height:i.cavHeight+'px'}" :disabled="!close" inertia="false"
:scale="close" :scale-min="0.5" :scale-max="1.5" direction="all" @scale="onScale" :out-of-bounds='true'
id="poster">
<image class="cavImg" :style="{width:i.cavWidth+'px',height:i.cavHeight+'px'}" :src="i.url" />
<canvas class="myCanvas" disable-scroll="true" :data-id="j" @touchstart="penStart" @touchmove="penMove"
@touchend="penEnd" :canvas-id="'myCanvas'+j"></canvas>
<view v-show="close" class="wrap-index" :style="{width:i.cavWidth+'px',height:i.cavHeight+'px'}"></view>
</movable-view>
</movable-area>
<canvas v-show="false" class="picCut" id="picCut"></canvas>
<view class="poster-view" v-if="posterImg.length>0">
<image :src="posterImg" mode="scaleToFill"
:style="'width:'+(posterWidth-30)+'px;height:'+(posterHeight-50)+'px;'"></image>
</view>
<view v-if="!close&&open" class="marking-tag3">
<!-- 颜色 -->
<view class="color">
<block v-for="i in colorArr" :key="i.color">
<view :class="{colorSelection:i.active}">
<view @click="updateColor(i.color)" :style="{background: i.color}"></view>
</view>
</block>
</view>
<!-- 粗细 -->
<view class="thickness">
<view @tap="open=false" class="marking-tag4">收起</view>
<block v-for="i in thickness" :key="i.thickness">
<view :class="{colorSelection:i.active}">
<view @click="updateThickness(i.thickness)" :style="{height: i.thickness/5+'rpx'}"></view>
</view>
</block>
</view>
<!-- 擦除 -->
<view class="Erase">
<view @click="retDraw" style="color:red">清除</view>
</view>
</view>
<view v-else-if="!close&&!open" @tap="open=true" class="marking-tag2">展开</view>
</view>
</template>
<script>
import Mycanvas from "../../static/js/handwriting.js";
import {
base64ToPath,
pathToBase64
} from '@/utils/image-tools.js';
export default {
data() {
return {
close: true,
open: true,
colorArr: [
//画笔颜色
{
color: "#ff0000",
active: true
},
{
color: "#1c9d02",
active: false
},
{
color: "#000000",
active: false
},
{
color: "#006ce6",
active: false
},
{
color: "#efaa03",
active: false
},
{
color: "rgba(255, 255, 0, 0.5)",
active: false
},
],
thickness: [
//画笔粗细
{
thickness: 10,
active: false
},
{
thickness: 20,
active: false
},
{
thickness: 30,
active: true
},
{
thickness: 40,
active: false
},
{
thickness: 50,
active: false
}
],
//当前的图片路径
answerPhoto: ["/static/img/img3.jpg"],
// 保存图片、画布宽高
ImageInfo: [],
// new出来的实例
canvasInfo: [],
// 有涂鸦的图片的下标
imgIndex: [],
scale: 1,
isDraw: true,
activeColor: '',
posterImg: '',
posterHeight: 0,
posterWidth: 0,
};
},
onReady() {
// 获取图片信息
this.getImageInfo();
},
watch: {
// new出canvas的实例
ImageInfo(ImageInfo) {
this.ImageInfo.map((i, j) => {
this.canvasInfo.push(
new Mycanvas({
lineColor: this.lineColor,
lineSise: this.lineSise,
canvasName: "myCanvas" + j,
})
);
});
}
},
computed: {
//颜色
lineColor: {
get() {
let [color] = this.colorArr.filter(i => i.active);
return color.color;
}
},
//粗细
lineSise: {
get() {
let [thicknes] = this.thickness.filter(i => i.active);
return thicknes.thickness;
}
}
},
methods: {
penOpen() {
this.close = false;
this.isDraw = true;
this.updateColor('rgba(255, 0, 0, 1)')
},
eraser() {
this.close = false;
this.isDraw = false
this.updateColor('rgba(255, 255, 255, 0)')
// if (this.close) return;
// let index = event.target.dataset.id;
// //#ifdef H5
// index = event.currentTarget.dataset.id;
// //#endif
// // #ifdef MP-WEIXIN
// index = event.target.dataset.id;
// // #endif
// // 涂鸦后的下标
// this.imgIndex.push(index);
// this.canvasInfo[0].eraser();
// context.clearRect(0,0,50,50);
},
onMove: function(e) {
// if(this.isMoveTrue){
// this.showTis = false
// this.moveX = e.detail.x
// }
},
onScale(e) {
console.log(e)
this.scale = e.detail.scale
},
// 获取图片信息
async getImageInfo() {
let newArr = [];
for (let i = 0; i < this.answerPhoto.length; i++) {
let [, image] = await uni.getImageInfo({
src: this.answerPhoto[i]
});
newArr.push({
url: this.answerPhoto[i],
cavWidth: this.widths(image.width),
cavHeight: this.heights(image.height, image.width),
imgWidth: image.width,
imgHeight: image.height
});
}
this.ImageInfo = newArr;
},
//计算宽
widths(imgWidth) {
const res = uni.getSystemInfoSync();
//屏幕宽
if (res.windowWidth * 0.95 <= imgWidth) {
return res.windowWidth * 0.95; //rpx
} else {
return imgWidth;
}
},
//计算高
heights(imgHeight, imgWidth) {
const res = uni.getSystemInfoSync();
if (res.windowWidth * 0.95 <= imgWidth) {
let b = (res.windowWidth * 0.95) / imgWidth;
return b * imgHeight;
} else {
return imgHeight;
}
},
// 笔迹粗细滑块
updateThickness(value) {
this.canvasInfo.map(i => {
i.selectSlideValue(value);
});
this.thickness.map(i => {
if (i.thickness == value) {
i.active = true;
} else {
i.active = false;
}
return i;
});
},
// 选择画笔颜色
updateColor(color) {
this.canvasInfo.map(i => {
i.selectColorEvent(color);
});
this.colorArr.map(i => {
if (i.color == color) {
i.active = true;
} else {
i.active = false;
}
this.activeColor = i.color
return i;
});
},
// 清除画布
retDraw() {
this.canvasInfo.map(i => {
i.retDraw();
});
},
// 笔迹开始
penStart(event) {
if (this.close) return;
let index = event.target.dataset.id;
//#ifdef H5
index = event.currentTarget.dataset.id;
//#endif
// #ifdef MP-WEIXIN
index = event.target.dataset.id;
// #endif
// 涂鸦后的下标
this.imgIndex.push(index);
this.canvasInfo[index].penStart(event, this.scale, this.isDraw);
},
// 笔迹移动
penMove(event) {
if (this.close) return;
let index = event.target.dataset.id;
// #ifdef MP-WEIXIN
index = event.target.dataset.id;
// #endif
//#ifdef H5
index = event.currentTarget.dataset.id;
//#endif
this.canvasInfo[index].penMove(event, this.scale, this.isDraw);
},
// 笔迹结束
penEnd(event) {
if (this.close) return;
let index = event.target.dataset.id;
// #ifdef MP-WEIXIN
index = event.target.dataset.id;
// #endif
//#ifdef H5
index = event.currentTarget.dataset.id;
//#endif
this.canvasInfo[index].penEnd(event, this.scale, this.isDraw);
},
// 保存涂鸦的图片
async subCanvas() {
// let index = [...new Set(this.imgIndex)];
// if (index.length !== 0) {
// for (let i = 0; i < index.length; i++) {
// // h5环境下为base
// const data = await this.canvasInfo[index[i]].saveCanvas();
// console.log(data);
// }
// }
let that = this
let dom = null
const query = uni.createSelectorQuery().in(this);
query.select('.handCenter').boundingClientRect(data => {
console.log(data)
dom = data
}).exec();
query.select('.picCut').boundingClientRect(canvas => {
console.log(dom)
canvas.width = dom.width;
canvas.height = dom.height;
let ctx = uni.createCanvasContext("picCut")
console.log(ctx)
let img = ctx.drawImage(dom, 0, 0, dom.width, dom.width)
console.log(img)
let previewImg = canvas.toDataURL('image/png')
console.log(previewImg)
}).exec()
// query.select('.picCut').boundingClientRect(data => {
// console.log(data)
// canvas = data
// }).exec();
// let video = uni.createSelectorQuery().selectAll(".handCenter")
// console.log(video)
// let canvas = document.createElement('canvas')
// console.log("######",dom)
// console.log(canvas)
// // console.log(canvas)
// const ctx = canvas.getContext('2d')
// // console.log(ctx)
// ctx.drawImage(video, 0, 0, w, h)
// let previewImg = canvas.toDataURL('image/png')
// console.log(canvas.toDataURL('image/png'))
// this.cutPicUploadForm.imgStr = canvas.toDataURL('image/png')
},
async receiveRenderData(imgData) {
this.posterHeight = imgData.height;
this.posterWidth = imgData.width;
let imgPath = await base64ToPath(imgData.imgVal, '.jpeg');
console.log("海报宽度", this.posterWidth);
console.log("海报高度", this.posterHeight);
this.posterImg = imgPath;
// uni.hideLoading();
// uni.showToast({
// title:"success !",
// icon:'success'
// })
}
}
};
</script>
<script module="renderScript" lang="renderjs">
import html2canvas from 'html2canvas';
export default {
data() {
return {
}
},
//页面加载就生成图片
/* mounted() {
let that=this;
this.$ownerInstance.callMethod('showLoading',true);
setTimeout(function(){
that.emitData();
},5000);
}, */
methods: {
// 发送数据到逻辑层
emitData(e, ownerVm) {
// this.$ownerInstance.callMethod('showLoading',true);
const dom = document.getElementById('poster');
let width = dom.offsetWidth;
let height = dom.offsetHeight;
let windowHeight = uni.getSystemInfoSync().windowHeight;
let windowWidth = uni.getSystemInfoSync().windowWidth;
html2canvas(dom, {
width: width,
height: height,
//scale:2.5, // 缩放倍数
scrollY: 0, // html2canvas默认绘制视图内的页面,需要把scrollY,scrollX设置为0
scrollX: 0,
x: 0,
y: 0,
windowWidth: windowWidth,
windowHeight: windowHeight,
useCORS: true, //支持跨域,但好像没什么用
}).then((canvas) => {
let imgVal = canvas.toDataURL('image/png');
console.log(imgVal)
this.$ownerInstance.callMethod('receiveRenderData', {
imgVal: imgVal,
height: height,
width: width
});
});
}
}
};
</script>
<style lang="scss" scoped>
.colorSelection {
background: #ccc;
}
.wrap {
width: 100%;
height: 100%;
margin: 30upx 0;
overflow: hidden;
display: flex;
align-content: center;
flex-direction: column;
justify-content: center;
font-size: 28upx;
.marking-tag2 {
position: fixed;
right: 0;
z-index: 10;
bottom: 280rpx;
background-color: #fff;
padding: 5rpx 10rpx;
}
.marking-tag3 {
position: fixed;
right: 0;
bottom: 210rpx;
z-index: 10;
.Erase {
display: flex;
justify-content: space-between;
background: #fff;
>view {
padding: 15rpx 0;
text-align: center;
flex-grow: 1;
}
}
.color {
display: flex;
.colorSelection {
background: #ccc;
}
>view {
background: #fff;
padding: 15rpx 30rpx;
>view {
width: 25rpx;
height: 25rpx;
border-radius: 50%;
}
}
}
.thickness {
display: flex;
justify-content: space-between;
background: #fff;
position: relative;
>view {
flex-grow: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 20rpx 0;
>view {
width: 40rpx;
background: #666;
}
}
.marking-tag4 {
position: absolute;
background: #fff;
left: 0;
padding: 5rpx 15rpx;
transform: translateX(-100%);
}
}
}
.wrap-head {
display: flex;
padding: 0 5rpx;
justify-content: center;
.wrap-head-close {
background: #8799a3;
}
text {
background-color: #39b54a;
color: white;
border-radius: 5upx;
font-size: 30rpx;
padding: 10rpx 15rpx;
margin: 15rpx;
}
}
.myCanvas {
// background: transparent;
width: 100%;
height: 100%;
}
.handCenter {
border: 4upx dashed #e9e9e9;
flex: 5;
overflow: hidden;
background: transparent;
box-sizing: border-box;
max-width: 95%;
margin: 5rpx auto;
position: relative;
z-index: 2;
}
.cavImg {
position: absolute;
z-index: -1;
}
.wrap-index {
position: absolute;
z-index: 10;
top: 0;
background-color: transparent;
}
}
.poster-view {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
margin: 20px 0px;
}
</style>