How to Build a Signature Pad in DingTalk Mini Programs with Taro & React
Learn step‑by‑step how to create a signature board for DingTalk mini‑programs using Taro and React, covering canvas creation, drawing logic, size adjustments, image export, undo functionality, landscape mode handling, and common pitfalls, so you can add reliable boss‑signed reports to your app.
Introduction
Guming has nearly ten thousand stores and needs a boss‑signature feature in its DingTalk mini‑program to confirm inspection reports. This guide shows how to implement a signature board using the Canvas component.
Implementation Design
Create Canvas
We first create a canvas element on the page.
import { Canvas } from '@tarojs/components';
const SignaturePad = () => {
return (
<Canvas
id="signature"
canvasId="signature"
className="canvas"
width="343"
height="180"
/>
)
}
/* SignaturePad.less */
.canvas {
background: #fff;
}Adjust Canvas Size
To obtain the correct display size we set the style width to 100% and height to 360 px.
.canvas {
background: #fff;
width: 100%;
height: 360px;
}Initialize
After the canvas is created we set up the drawing context (CanvasContext) and preset brush attributes.
let ctx = null;
let startX = 0;
let startY = 0;
const SignaturePad = () => {
const initCanvas = () => {
ctx = Taro.createCanvasContext('signature');
ctx.setStrokeStyle('#000000');
ctx.setLineWidth(4);
ctx.setLineCap('round');
ctx.setLineJoin('round');
};
useEffect(() => {
initCanvas();
return () => {
ctx = null;
};
}, []);
// ...
}Drawing
The canvas coordinate system starts at the top‑left corner (0,0). Touch events are used to draw lines.
Canvas related properties
We use onTouchStart to record the start point, onTouchMove to draw the path, and onTouchEnd to finish.
const isPaint = useRef(false);
const canvasStart = (e) => {
startX = e.touches[0].x;
startY = e.touches[0].y;
ctx.beginPath();
};
const canvasMove = (e) => {
if (startX !== 0 && !isPaint.current) {
isPaint.current = true;
}
const { x, y } = e.touches[0];
ctx.moveTo(startX, startY);
ctx.lineTo(x, y);
ctx.stroke();
ctx.draw(true);
startX = x;
startY = y;
};
const canvasEnd = () => {
ctx.closePath();
};
return (
<Canvas
id="signature"
canvasId="signature"
className="canvas"
onTouchStart={canvasStart}
onTouchMove={canvasMove}
onTouchEnd={canvasEnd}
onTouchCancel={canvasEnd}
width="343"
height="180"
disableScroll
/>
);Add Operations
Saving the signature as an image and clearing the canvas.
const createImg = async () => {
if (!isPaint.current) {
Taro.showToast({
title: '签名内容不能为空!',
icon: 'none',
});
return false;
}
const { filePath } = await ctx.toTempFilePath();
// further processing with filePath
};Clear operation:
let canvasw = 0;
let canvash = 0;
const getCanvasSize = () => {
nextTick(() => {
const query = Taro.createSelectorQuery();
query
.select('#signature')
.boundingClientRect()
.exec(([rect]) => {
canvasw = rect.width;
canvash = rect.height;
});
});
};
useEffect(() => {
getCanvasSize();
}, []);
const clearDraw = () => {
startX = 0;
startY = 0;
ctx.clearRect(0, 0, canvasw, canvash);
ctx.draw(true);
setIsPaint(false);
};Optimization
Undo
We keep a history stack of image data to allow undoing the last stroke.
const history = useRef([]);
const canvasEnd = async () => {
ctx.closePath();
const res = await ctx.getImageData({ x: 0, y: 0, width: canvasw, height: canvash });
history.current.push(res);
};
const revoke = () => {
if (!history.current.length) return;
history.current.pop();
if (!history.current.length) {
ctx.clearRect(0, 0, canvasw, canvash);
ctx.draw(true);
return;
}
ctx.putImageData(history.current[history.current.length - 1]);
};Landscape Mode
Rotate the signature pad to landscape using CSS transforms and adjust canvas dimensions.
const [full, setFull] = useState(false);
const toggleSize = () => {
setFull(!full);
};
return (
<View className="signature-pad-wrap">
<View className={`signature-pad ${full ? 'full-screen' : ''}`}>
{/* canvas */}
...
</View>
</View>
); .signature-pad {
box-sizing: border-box;
width: 100%;
padding: 32px 32px 30px;
transform-origin: top left;
transition: transform 0.3s;
.canvas {
width: 686px;
height: 360px;
background: #fff;
}
&.full-screen {
width: 100vh;
height: 100vw;
transform: rotate(90deg) translate(0, -756px);
.canvas {
width: 1386px;
height: 630px;
}
}
}After rotation the original clearRect no longer matches the canvas size, so we recalculate the dimensions when switching to full‑screen.
const toggleSize = () => {
setFull(!full);
setTimeout(() => {
getCanvasSize();
}, 200);
};Conclusion
The signature board is built primarily with the Canvas API; the same approach works for H5 with minor differences for DingTalk mini‑programs. Although the implementation is straightforward, various quirks appear, and solving them adds to a developer’s satisfaction.
Goodme Frontend Team
Regularly sharing the team's insights and expertise in the frontend field
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
