traojs项目中使用canvas封装一个签名画板
封装:index.tsx
import { useEffect, useRef, useState, forwardRef, ForwardedRef, useImperativeHandle } from 'react';
import * as raf from 'raf';
import Taro from '@tarojs/taro';
import { View, Canvas, ViewProps } from '@tarojs/components';
import classNames from 'classnames';
import styles from './index.module.less';
export interface ISignatureProps extends ViewProps {
/**
* @description 画布元素id
* @default spcanvas
*/
canvasId?: string;
/**
* @description 获取图片的类型
* @default `png`
*/
type?: 'jpg' | 'png';
/**
* @description 线条的宽度
* @default `3`
*/
lineWidth?: number;
/**
* @description 绘图颜色
* @default `#000`
*/
strokeStyle?: string;
/**
* @description 样式名
*/
className?: string;
/**
* 用于判断是有发“画”过
*/
handlerTouchEnd?: () => void;
}
/**
* @title 组件实例
*/
export interface ISignatureInstance {
/**
* @description 获取绘制生成的图片相关数据,tempFilePath在h5为base64,小程序为临时图片,由于canvasToTempFilePath在部分小程序IDE无法调试
*/
getImage: () => Promise<{
base64: string;
tempFilePath: string;
canvas: HTMLCanvasElement;
}>;
/**
* @description 清除画布方法
*/
clear: () => void;
}
export function requestAnimationFrame(cb: any) {
if (window.requestAnimationFrame) {
return window.requestAnimationFrame(cb);
}
// @ts-ignore
return raf.default(cb);
}
const defaultProps: ISignatureProps = {
canvasId: 'spcanvas',
type: 'png',
lineWidth: 2,
strokeStyle: '#000',
className: '',
};
let componentIndex = 0;
function Signature(props: ISignatureProps, ref: ForwardedRef<ISignatureInstance>) {
const { canvasId, lineWidth, strokeStyle, className, ...rest } = {
...defaultProps,
...props,
};
const canvasRef = useRef<HTMLCanvasElement>(null);
const wrapRef = useRef<HTMLDivElement>(null);
const [canvasHeight, setCanvasHeight] = useState(0);
const [canvasWidth, setCanvasWidth] = useState(0);
const ctx = useRef<any>(null);
const [compIndex] = useState(componentIndex++);
const startEventHandler = () => {
if (ctx.current) {
ctx.current.beginPath();
ctx.current.lineWidth = lineWidth as number;
ctx.current.strokeStyle = strokeStyle as string;
}
};
const moveEventHandler = (event: any) => {
if (ctx.current) {
requestAnimationFrame(() => {
const evt = event.changedTouches[0];
let mouseX = evt.x || evt.clientX;
let mouseY = evt.y || evt.clientY;
if (Taro.getEnv() === 'WEB' && canvasRef.current) {
const coverPos = canvasRef.current.getBoundingClientRect();
mouseX = evt.clientX - coverPos.left;
mouseY = evt.clientY - coverPos.top;
}
ctx.current?.lineTo(mouseX, mouseY);
ctx.current?.stroke();
});
}
};
const endEventHandler = () => {
props.handlerTouchEnd?.();
};
const handleClear = () => {
if (ctx.current) {
ctx.current.clearRect(0, 0, canvasWidth, canvasHeight);
ctx.current.closePath();
}
};
const getImage = (): Promise<{
base64: string;
tempFilePath: string;
canvas: HTMLCanvasElement;
}> => {
return new Promise((resolve, reject) => {
const base64 = ctx.current?.canvas?.toDataURL(`image/${props.type}`, 0.8);
Taro.createSelectorQuery()
.select(`#${canvasId}${compIndex}`)
.fields({
node: true,
size: true,
})
.exec(res => {
if (
process.env.NODE_ENV === 'development' &&
['alipay', 'tt', 'swan', 'kwai', 'dd'].includes(process.env.TARO_ENV)
) {
console.warn(
`signature组件调用了canvasToTempFilePath, 当前IDE不支持调试,须在真机上调试`,
);
}
Taro.canvasToTempFilePath({
canvas: res[0].node,
fileType: props.type,
canvasId: `${canvasId}${compIndex}`,
success: res => {
resolve({
tempFilePath: res.tempFilePath,
base64,
canvas: ctx.current?.canvas,
});
},
fail: err => {
console.error(`[signature 转换图片失败]`, err);
reject(err);
},
});
});
});
};
const canvasSetting = (canvasDom: any, width: number, height: number) => {
if (canvasDom) {
const canvas = canvasDom;
canvas.current = canvas;
ctx.current = canvas.getContext('2d');
setCanvasWidth(width);
setCanvasHeight(height);
canvas.width = width;
canvas.height = height;
if (ctx.current) {
ctx.current.clearRect(0, 0, width, height);
ctx.current.beginPath();
ctx.current.lineWidth = lineWidth as number;
ctx.current.strokeStyle = strokeStyle as string;
}
}
};
const initCanvas = () => {
Taro.nextTick(() => {
setTimeout(() => {
if (process.env.TARO_ENV !== 'h5') {
Taro.createSelectorQuery()
.select(`#${canvasId}${compIndex}`)
.fields(
{
node: true,
size: true,
},
res => {
const { node, width, height } = res;
canvasSetting(node, width, height);
},
)
.exec();
} else {
const canvasDom: HTMLElement | null = document.getElementById(`${canvasId}${compIndex}`);
let canvas: HTMLCanvasElement = canvasDom as HTMLCanvasElement;
if (canvasDom?.tagName !== 'CANVAS') {
canvas = canvasDom?.getElementsByTagName('canvas')[0] as HTMLCanvasElement;
}
canvasSetting(
canvas,
canvasDom?.offsetWidth as number,
canvasDom?.offsetHeight as number,
);
}
}, 1000);
});
};
useEffect(() => {
initCanvas();
}, []);
useImperativeHandle(ref, () => {
return {
getImage,
clear: handleClear,
};
});
return (
<View className={classNames(styles.signature, className)} {...(rest as any)}>
<View className={classNames(styles.signatureInner, styles.spcanvas_WEAPP)} ref={wrapRef}>
<Canvas
className={styles.signatureCanvas}
id={`${canvasId}${compIndex}`}
ref={canvasRef}
canvasId={`${canvasId}${compIndex}`}
disableScroll
type="2d"
onTouchStart={startEventHandler}
onTouchMove={moveEventHandler}
onTouchEnd={endEventHandler}
/>
</View>
</View>
);
}
export default forwardRef(Signature);
index.modules.less
/* stylelint-disable-next-line no-descending-specificity */
.signature {
transform: translateZ(0);
z-index: 5;
}
.spcanvas_WEAPP {
width: 100%;
height: 100%;
transform: translateZ(0);
}
.signatureCanvas {
width: 100%;
height: 100%;
transform: translateZ(0);
z-index: 5;
canvas {
background-color: #ffffff;
width: 100%;
border: 1px solid #dadada;
height: 300px;
z-index: 5;
transform: translateZ(0);
}
}
.signatureInner {
background-color: #ffffff;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
transform: translateZ(0);
}
使用:
页面样式
.Sign {
height: 100%;
width: 100%;
border: 1px solid #f5f5f5;
border-radius: 20px;
z-index: 5;
}
const instance = useRef<ISignatureInstance>();
res= await instance.current?.getImage() //获取签名图片信息
res结构:
base64: string;
tempFilePath: string;
canvas: HTMLCanvasElement;
instance.current?.clear() //清除画板内容
<Signature
type="png"
className={styles.Sign}
// @ts-ignore
ref={instance}
/>

浙公网安备 33010602011771号