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.

Goodme Frontend Team
Goodme Frontend Team
Goodme Frontend Team
How to Build a Signature Pad in DingTalk Mini Programs with Taro & React

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.

frontendCanvassignaturemini-program
Goodme Frontend Team
Written by

Goodme Frontend Team

Regularly sharing the team's insights and expertise in the frontend field

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.