前言
目前项目要用到签字版这个功能,因为之前没有接触过,之前没怎么接触过小程序的画板,然后在网上也找了相对应的文章,最后整理了一下,感觉可能比较易懂一点吧,就想着发出来给大家伙做个参考
正文
实现的大概思路:你在画板上触摸的时候都有相对应的事件,这些事件都会返回相对的坐标,用画板的API把坐标连起来,渲染颜色,最后在渲染到画板上即可
技术
技术:React+Taro+TS+less
代码(解释都在代码里的注释)
tsx
tsx
import { Component } from "react";
import Taro, { Config } from "@tarojs/taro";
import { View, Button, Canvas } from "@tarojs/components";
import "./index.less";
/** 创建画板的实例 */
let ctx: any = Taro.createCanvasContext("canvas", this);
/** 坐标 */
let startX = 0;
let startY = 0;
/** 宽高 */
let canvasW = 0;
let canvasH = 0;
export default class Signature extends Component<any, any> {
state = {
isPaint: false,
tempFilePath: "",
color: "#000",
colorList: [
{ name: "red", value: "#ff1132" },
{ name: "green", value: "#4fcd56" },
{ name: "yellow", value: "#ffd767" },
{ name: "blue", value: "#0883d8" },
{ name: "black", value: "#000" },
{ name: "pink", value: "#ff6899" },
],
};
componentDidMount() {
/** 获取画板的宽高,因为要获取DOM,要等到DOM渲染完成,所以给了个延迟定时器 */
setTimeout(() => {
this.getCanvasSize();
}, 200);
/** 初始化 */
this.initCanvas();
}
/** 销毁阶段 */
componentWillUnmount() {
ctx = null;
}
/** 初始化 */
initCanvas() {
/** 获取画板 */
ctx = Taro.createCanvasContext("canvas", this);
/** 线条颜色 */
ctx.setStrokeStyle(this.state.color);
/** 线条粗细 */
ctx.setLineWidth(4);
/** 线条结束的样式 选择值可参考官方文档 */
ctx.setLineCap("round");
/** 线条交点的样式 选择值可参考官方文档 */
ctx.setLineJoin("round");
}
/** 开始触摸 */
canvasStart(e) {
/** 获取坐标然后赋值 */
startX = e.changedTouches[0].x;
startY = e.changedTouches[0].y;
/** 丢弃任何当前定义的路径并且开始一条新的路径。 它把当前的点设置为0.0,需要调用 fill 或者 stroke 才会使用路径进行填充或描边 */
/** 具体可看官方文档或者百度 */
ctx.beginPath();
}
/** 触摸中 */
canvasMove(e) {
/** 判断是否有值 */
if (startX !== 0) {
this.setState({
isPaint: true,
});
}
/** 获取坐标 */
let x = e.changedTouches[0].x;
let y = e.changedTouches[0].y;
/** 把路径移动到画布中的指定点,不创建线条。用 stroke 方法来画线条*/
/** 具体可看官方文档 */
ctx.moveTo(startX, startY);
/** 增加一个新点,然后创建一条从上次指定点到目标点的线。用 stroke 方法来画线条 */
/** 创建新坐标到老坐标之间的线 */
/** 具体可看官方文档 */
ctx.lineTo(x, y);
/** 画出当前路径的边框。默认颜色色为黑色 */
/** 开始给线条颜色 */
/** 具体可看官方文档 */
ctx.stroke();
/** 将之前在绘图上下文中的描述(路径、变形、样式)画到 canvas 中 */
/** 把之前的操作成果展示到画布上 */
/** 具体可看官方文档 */
ctx.draw(true);
/** 赋值新的坐标 */
startX = x;
startY = y;
}
/** 触摸结束 */
canvasEnd(e) {
// console.log("结束");
}
// 清除
clearDraw = () => {
/** 赋值 */
this.setState(
{
isPaint: false,
tempFilePath: "",
},
() => {
/** 清零 */
startX = 0;
startY = 0;
/** 清除画布上在该矩形区域内的内容*/
/** 参数 前两个时坐标,后两个时画布的宽高 */
ctx.clearRect(0, 0, canvasW, canvasH);
/** 把之前的操作成果展示到画布上 */
ctx.draw(true);
}
);
};
/** 确认按钮 */
createImg() {
/** 判断画布是否为空 */
if (!this.state.isPaint) {
Taro.showToast({
title: "签名内容不能为空!",
icon: "none",
});
return false;
}
/** 生成图片 */
/** 把当前画布指定区域的内容导出生成指定大小的图片。 在 draw() 回调里调用该方法才能保证图片导出成功。暂不支持离屏 canvas */
/** 具体可查看官方文档 */
Taro.canvasToTempFilePath({
canvasId: "canvas",
success: (res) => {
console.log(res.tempFilePath);
this.setState({
tempFilePath: res.tempFilePath,
});
// this.uploadToAliyun(res.tempFilePath)
},
fail(err) {
console.log(err);
},
});
}
// 获取 canvas 的尺寸(宽高)
getCanvasSize() {
/** createSelectorQuery具体可看官方文档 */
const query = Taro.createSelectorQuery();
query
/** 要获取画板的id */
.select("#canvas")
.boundingClientRect((res) => {
/** 赋值 */
canvasW = res.width;
canvasH = res.height;
})
.exec();
}
/** 选择颜色 */
colorEvent(value) {
console.log("传过来的颜色", value);
/** 因为setState 时异步操作 所以必须等赋值完成之后在执行初始化 */
this.setState(
{
color: value,
},
() => {
this.initCanvas();
console.log("赋值完的颜色", this.state.color);
}
);
}
render() {
return (
<View className='signature'>
<View className='canvas-box'>
<Canvas
id='canvas'
canvasId='canvas'
className='canvas'
disableScroll
/** 触摸开始 */
onTouchStart={this.canvasStart.bind(this)}
/** 触摸中 */
onTouchMove={this.canvasMove.bind(this)}
/** 触摸结束 */
onTouchEnd={this.canvasEnd.bind(this)}
/** 触摸被中断 */
onTouchCancel={this.canvasEnd.bind(this)}
></Canvas>
</View>
<View className='color'>
{this.state.colorList.map((item) => {
return (
<View
key={item.value}
className='item'
style={{ backgroundColor: item.value }}
onClick={() => {
this.colorEvent(item.value);
}}
></View>
);
})}
</View>
<View>
当前颜色:
<View
style={{
backgroundColor: this.state.color,
width: "60px",
height: "60px",
}}
></View>
</View>
<View className='layout-flex buttons'>
<Button className='cancel' onClick={this.clearDraw}>
清除
</Button>
<Button className='confirm' onClick={this.createImg.bind(this)}>
提交
</Button>
</View>
<View>图片路径:</View>
<View className='word-break'>{this.state.tempFilePath}</View>
</View>
);
}
}
less
less
.signature {
height: 100%;
width: 100%;
}
.canvas-box {
border: 1Px solid #e4e4e4;
margin-bottom: 30Px;
position: relative;
}
.canvas {
width: 100%;
height: 345Px;
}
.buttons {
display: flex;
justify-content: space-between;
margin-bottom: 20Px;
margin-top: 20Px;
}
Button {
width: 320px;
height: 80px;
line-height: 80px;
margin: 0;
text-align: center;
border: 1Px solid #F8AF18;
font-size: 30px;
}
.color{
width: 100%;
display: flex;
justify-content: space-evenly;
.item{
width: 60px;
height: 60px;
}
}
.confirm {
color: #fff;
background: #F8AF18;
}
.cancel {
color: #F8AF18;
background: #fff;
}
.word-break {
word-break: break-all;
}