iT邦幫忙

2021 iThome 鐵人賽

DAY 28
1
Modern Web

Canvas 小錦囊系列 第 28

Day 28 - 用 canvas 與 pdfjs 做文件簽名(上)

前述

今天用前面做過的小畫家相似功能,來完成一個可以在文件上面簽名的功能~
當然也會有新的東西可以玩。

思路

我們將文件簽名分成三個步驟

 <div className="App">
  <h1>步驟一</h1>
  <h3>繪製簽名檔</h3>
  <SignBox></SignBox>
  <h1>步驟二</h1>
  <h3>上傳文件</h3>
  <UploadFile></UploadFile>
  <h1>步驟三</h1>
  <h3>合併輸出</h3>
  <Output></Output>
</div>

今天先教學 繪製簽名檔上傳文件 的部分

繪製簽名檔

根據以前的操作,相信已經對 mouse 的事件滾瓜爛熟了,
這裡就不贅述囉。

code

import React, { useEffect, useRef, useState } from "react";
import getTouchPos from "../../utils/getTouchPos";
import getMousePos from "../../utils/getMousePos";

import { useAtom } from "jotai";
import { signAtom } from "../../data";

const canvasSize = 500;

const SignFile = () => {
  const canvasRef = useRef(null);
  const [canvas, setCanvas] = useState(null);
  const [ctx, setCtx] = useState(null);
  const [src, setSrc] = useState(null);

  const [drawing, setDrawing] = useState(false);

  const [_, setSignData] = useAtom(signAtom);

  useEffect(() => {
    const c = canvasRef.current;
    setCanvas(c);
    if (c) setCtx(c.getContext("2d"));
  }, [canvasRef]);

  /** 開始 */
  const handleTouchStart = (event) => {
    setDrawing(true);
    const touchPos = getTouchPos(canvas, event);
    ctx.beginPath(touchPos.x, touchPos.y);
    ctx.moveTo(touchPos.x, touchPos.y);
    event.preventDefault();
  };

  const handleMouseDown = (event) => {
    setDrawing(true);
    const mousePos = getMousePos(canvas, event);
    ctx.beginPath();
    ctx.moveTo(mousePos.x, mousePos.y);
    event.preventDefault();
  };

  /** 移動 */
  const handleTouchMove = (event) => {
    if (!drawing) return;
    const touchPos = getTouchPos(canvas, event);
    ctx.lineWidth = 2;
    ctx.lineCap = "round"; // 繪制圓形的結束線帽
    ctx.lineJoin = "round"; // 兩條線條交匯時,建立圓形邊角
    ctx.shadowBlur = 1; // 邊緣模糊,防止直線邊緣出現鋸齒
    ctx.shadowColor = "black"; // 邊緣顏色
    ctx.lineTo(touchPos.x, touchPos.y);
    ctx.stroke();
  };

  const handleMouseMove = (event) => {
    if (!drawing) return;
    const mousePos = getMousePos(canvas, event);
    ctx.lineWidth = 2;
    ctx.lineCap = "round"; // 繪制圓形的結束線帽
    ctx.lineJoin = "round"; // 兩條線條交匯時,建立圓形邊角
    ctx.shadowBlur = 1; // 邊緣模糊,防止直線邊緣出現鋸齒
    ctx.shadowColor = "black"; // 邊緣顏色
    ctx.lineTo(mousePos.x, mousePos.y);
    ctx.stroke();
  };

  /** 結束 */
  const handleTouchEnd = (event) => {
    setDrawing(false);
  };

  const handleMouseUp = (event) => {
    setDrawing(false);
  };

  /** 清除 */
  const handleClear = () => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  };

  /** 轉圖片 */
  const handleConvertToImage = () => {
    const image = canvas.toDataURL();
    setSignData(image);
    setSrc(image);
  };

  return (
    <div>
      <canvas
        style={{ background: "#EEE" }}
        ref={canvasRef}
        width={canvasSize}
        height={canvasSize}
        onTouchStart={handleTouchStart}
        onTouchMove={handleTouchMove}
        onTouchEnd={handleTouchEnd}
        onMouseDown={handleMouseDown}
        onMouseMove={handleMouseMove}
        onMouseUp={handleMouseUp}
      ></canvas>
      <div>
        <button onClick={handleClear}>清除</button>
        <button onClick={handleConvertToImage}>轉圖</button>
      </div>
      {src && (
        <img
          src={src}
          alt="signImage"
          style={{ color: "#FFF", border: "none" }}
        />
      )}
    </div>
  );
};

export default SignFile;

補充一下,這次跨組件都用 jotai ,想要用 recoil 之類的都是可行的。

迅速的就完成畫簽名啦,而且同時支援滑鼠觸控事件。

檢視

上傳檔案

做文件簽名當然也需要文件的支持啦,這段是本篇章的重點!

我們需要學習新的lib pdfjs ,迅速地讓他幫我們做到 pdf to canvas

pdfjs 文檔

react-pdf npm

至於怎麼使用,就請大家去翻翻文檔吧

import React, { useEffect, useRef, useState } from "react";
import getScaledDim from "../../utils/getScaledDim";
import { useAtom } from "jotai";
import { bgFileAtom } from "../../data";

import { pdfjs } from "react-pdf";
pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;

const canvasSize = 500;

const UploadFile = () => {
  const canvasRef = useRef(null);
  const [canvas, setCanvas] = useState(null);
  const [ctx, setCtx] = useState(null);
  const [src, setSrc] = useState(null);

  const [bgFileData, setBgFileData] = useAtom(bgFileAtom);

  useEffect(() => {
    const c = canvasRef.current;
    setCanvas(c);
    if (c) setCtx(c.getContext("2d"));
  }, [canvasRef]);

  /** image */
  const handleUploadImage = (event) => {
    const f = event.target.files[0];
    const ctx = canvasRef.current.getContext("2d");
    const img = new Image();
    img.onload = function () {
      const scaled = getScaledDim(img, canvasSize, canvasSize);
      // scale canvas to image
      ctx.width = scaled.width;
      ctx.height = scaled.height;
      // draw image
      ctx.drawImage(img, 0, 0, ctx.width, ctx.height);
    };
    img.src = URL.createObjectURL(f);
  };

  /** pdf */
  const handleUploadPdf = (event) => {
    const file = event.target.files[0];
    if (file.type === "application/pdf") {
      let fileReader = new FileReader();
      fileReader.onload = function () {
        const pdfData = new Uint8Array(this.result);
        // Using DocumentInitParameters object to load binary data.
        const loadingTask = pdfjs.getDocument({ data: pdfData });
        loadingTask.promise.then(
          function (pdf) {
            console.log("PDF loaded");
            // Fetch the first page
            const pageNumber = 1;
            pdf.getPage(pageNumber).then(function (page) {
              console.log("Page loaded");

              const scale = 1.5;
              const viewport = page.getViewport({ scale: scale });

              // Prepare canvas using PDF page dimensions
              canvas.height = viewport.height;
              canvas.width = viewport.width;
              // Render PDF page into canvas context
              const renderContext = {
                canvasContext: ctx,
                viewport: viewport
              };
              const renderTask = page.render(renderContext);
              renderTask.promise.then(function () {
                console.log("Page rendered");
              });
            });
          },
          function (reason) {
            // PDF loading error
            console.error(reason);
          }
        );
      };
      fileReader.readAsArrayBuffer(file);
    }
  };

  const handleConvertToImage = () => {
    const image = canvas.toDataURL();
    setBgFileData(image);
    setSrc(image);
  };

  return (
    <div>
      <div style={{ marginBottom: `1rem` }}>
        上傳 Image:
        <input type="file" onChange={handleUploadImage} />
      </div>
      <div>
        上傳 PDF:
        <input accept=".pdf" type="file" onChange={handleUploadPdf} />
      </div>

      <canvas ref={canvasRef} width={canvasSize} height={canvasSize}></canvas>
      <button onClick={handleConvertToImage}>轉圖</button>
      <img src={src} alt="imagePdf" />
    </div>
  );
};

export default UploadFile;

// https://mozilla.github.io/pdf.js/examples/index.html#interactive-examples

檢視

這樣材料就算是準備好了,明天來準備合併吧!!

codesendbox


上一篇
Day 27 - 用 canvas 模擬手機圖型解鎖
下一篇
Day 29 - 用 canvas 與 fabricjs 做文件簽名(下)
系列文
Canvas 小錦囊30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
juck30808
iT邦研究生 1 級 ‧ 2021-10-14 12:05:58

恭喜即將邁入完賽啦~

我要留言

立即登入留言