第一步:创建一个components组件
xml
<view class="signature">
<canvas type="2d" id="canvas" bindtouchmove="move" bindtouchstart="start" binderror="error" style="width:{{width}}px;height:{{height}}px;">
</canvas>
</view>
js
const app = getApp();
Component({
/**
* 组件的属性列表
*/
properties: {
//高度百分比
h: {
type: Number,
value: 0.2,
},
// 填充描述文字
fillText: {
type: String,
value: "请使用正楷",
},
},
/**
* 组件的初始数据
*/
data: {
canvas: "",
ctx: "",
pr: 0,
width: 0,
height: 0,
first: true,
},
attached: function () {
this.getSystemInfo();
this.createCanvas();
},
/**
* 组件的方法列表
*/
methods: {
start(e) {
if (this.data.first) {
this.clearClick();
this.setData({ first: false });
}
// 开始创建一个路径,如果不调用该方法,最后无法清除画布
this.data.ctx.beginPath();
// 把路径移动到画布中的指定点,不创建线条。用 stroke 方法来画线条
this.data.ctx.moveTo(e.changedTouches[0].x, e.changedTouches[0].y);
},
move(e) {
// 增加一个新点,然后创建一条从上次指定点到目标点的线。用 stroke 方法来画线条
this.data.ctx.lineTo(e.changedTouches[0].x, e.changedTouches[0].y);
this.data.ctx.stroke();
},
error: function (e) {
console.log("画布触摸错误" + e);
},
/**
* 初始化
*/
createCanvas() {
const pr = this.data.pr; // 像素比
const query = this.createSelectorQuery();
query
.select("#canvas")
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext("2d");
canvas.width = this.data.width * pr; // 画布宽度
canvas.height = this.data.height * pr; // 画布高度
ctx.scale(pr, pr); // 缩放比
ctx.lineGap = "round";
ctx.lineJoin = "round";
ctx.lineWidth = 4; // 字体粗细
ctx.font = "40px Arial"; // 字体大小,
ctx.fillStyle = "#ecf0ef"; // 填充颜色
ctx.fillText(
this.data.fillText,
this.data.width / 2 - 100,
this.data.height / 2
);
this.setData({ ctx, canvas });
});
},
// 获取系统信息,宽,高,像素比
getSystemInfo() {
let _that = this;
wx.getSystemInfo({
success(res) {
_that.setData({
pr: res.pixelRatio,
width: res.windowWidth,
height: res.windowHeight * _that.data.h - 70,
});
},
});
},
//重签
clearClick: function () {
//清除画布
this.data.first = true;
this.data.ctx.clearRect(0, 0, this.data.width, this.data.height);
},
//保存图片
saveClick: function (cb) {
if (this.data.first) {
wx.showToast({
title: "签名数据为空!",
icon: "none",
});
return false;
}
console.log(this.data, cb)
// 获取临时文件路径
wx.canvasToTempFilePath({
x: 0,
y: 0,
width: this.data.width,
height: this.data.height,
destWidth: this.data.width * this.data.pr,
destHeight: this.data.height * this.data.pr,
canvasId: "canvas",
canvas: this.data.canvas,
fileType: "png",
success: (res) => {
// 文件转base64
wx.getFileSystemManager().readFile({
filePath: res.tempFilePath,
encoding: "base64",
success: (val) => {
cb && cb(val, res);
// 转换成功派发事件
this.triggerEvent("success", val.data);
},
});
},
});
},
},
});
.signature {
padding-top: 30px;
}
canvas {
background-color: white;
}
第二步引入组件并使用
"usingComponents": {
"signature": "../../components/signature/signature"
},
<!-- 第一行解决弹窗签名时页面滚动问题 -->
<page-meta page-style="{{ show ? 'overflow: hidden;' : '' }}" />
<view class="page-section">
<movable-area scale-area>
<movable-view direction="all" bindchange="onChange" bindscale="onScale" scale scale-min="0.5" scale-max="4" scale-value="1">
<view bindtap="test" style="border:1rpx dashed #867a7a;color:rgb(2, 2, 2);padding: 10rpx;">点击签名</view>
<!-- 生成的图片(后续可以根据后端参数判断) -->
<!-- <image src="/images/test.jpg" mode='widthFix' style="width:750rpx;height: 500rpx"></image> -->
</movable-view>
</movable-area>
</view>
<van-button type="info" size="small" bindtap="ImgPDF">预览</van-button>
<van-popup closeable show="{{ show }}" round position="bottom" custom-style="height: 50%" bind:close="onClose">
<view>
<signature h="{{0.5}}" class="signature" />
</view>
<view class="signature-btn">
<van-row>
<van-col span="12">
<van-button type="info" size="small" bindtap="reset">重置</van-button>
</van-col>
<van-col span="12">
<van-button type="info" size="small" bindtap="save">确认</van-button>
</van-col>
</van-row>
</view>
</van-popup>
Page({
data: {
show: false,
tempFilePath: '',
x:'',
y:''
},
test() {
this.setData({
show: true
})
},
// 保存
save() {
let signature = this.selectComponent('.signature');
signature.saveClick((res, url) => {
// res:base64数据,url:临时文件url
console.log(res, url);
var urlBase64 = 'data:image/png;base64,' + res.data.replace(/[\r\n]/g, '')
this.reset()
})
},
// 重置
reset() {
let signature = this.selectComponent('.signature');
signature.clearClick()
},
onClose() {
this.reset()
this.setData({
show: false
})
},
onChange(e) {
this.setData({
x:e.detail.x,
y:e.detail.y
})
},
onScale(e) {
console.log(e.detail)
},
onLoad(options) {
},
// 生成合并图片
ImgPDF(){
wx.navigateTo({
url: '/entry/mergeImg/mergeImg?x='+this.data.x+'&y='+this.data.y
})
},
})
movable-view {
display: flex;
align-items: center;
justify-content: center;
height: 100rpx;
width: 300rpx;
color: #fff;
}
movable-area {
background: #fff;
height: 1000rpx;
width: 100%;
overflow: hidden;
}
.page-section {
width: 99%;
margin-bottom: 60rpx;
}
.signature-btn {
text-align: center;
width: 100%;
}
第三步合并图片
<view>
<canvas id="myCanvas" type="2d" class="mergeCanvas"/>
</view>
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
wx.createSelectorQuery()
.select('#myCanvas') // 在 WXML 中填入的 id
.fields({ node: true, size: true })
.exec((res) => {
// Canvas 对象
const canvas = res[0].node
// 渲染上下文
const ctx = canvas.getContext('2d')
// Canvas 画布的实际绘制宽高
const width = res[0].width
const height = res[0].height
// 初始化画布大小
const dpr = wx.getWindowInfo().pixelRatio
canvas.width = width * dpr
canvas.height = height * dpr
ctx.scale(dpr, dpr)
// 图片对象
const image = canvas.createImage()//本地背景图
const imgs = canvas.createImage()//接口返回签名图片
// 图片加载完成回调
image.onload = () => {
// 将图片绘制到 canvas 上
ctx.drawImage(image, 0, 0,340,200)
ctx.drawImage(imgs,options.x,options.y,300,100)
}
// 设置图片src
image.src = '/static/images/avatar.png'
imgs.src = '/static/images/us.png'
//没加定时器之前合成的图片是一片灰色,加了之后才有图片,测试了一下最少需要400毫秒
setTimeout(()=>{
wx.canvasToTempFilePath({
canvas: canvas,
success: res => {
// 生成的图片临时文件路径
this.setData({
img:res.tempFilePath
})
},
})
},1000)
})
},
})
/* wxss */
.mergeCanvas {
border: 1px solid red;
width: 99%;
height: 1000rpx;
background: #fff;
z-index: 11;
}