Day 29 - 用 canvas 与 fabricjs 做文件签名(下)

接续

昨天完成了材料建立,今天就用 fabricjs 来做两者的合并

fabricjs

使用 fabricjs 可以很快速地让我们的签名可以改动位置,进行缩放,

核心

除了需要注意签名的缩放外,背景的缩放控制也很重要,详见 code

CodesendBox

import React, { useEffect, useState, useRef } from "react";
import { useAtom } from "jotai";
import { bgFileAtom, signAtom } from "../../data";
import { Wrapper, Canvas, Main } from "./style";
import { fabric } from "fabric";

const canvasOriginalHeight = 800;
const canvasOriginalWidth = 800;

const Output = () => {
  const [signData] = useAtom(signAtom);
  const [bgFileData] = useAtom(bgFileAtom);

  const mainRef = useRef(null);
  const [canvas, setCanvas] = useState(null);

  /** 建立主要的 canvas */
  useEffect(() => {
    const c = new fabric.Canvas(mainRef.current);
    setCanvas(c);
  }, [mainRef]);

  /** 填上签名 */
  useEffect(() => {
    if (canvas && signData) {
      fabric.Image.fromURL(signData, (img) => {
        img.scaleToWidth(100);
        img.scaleToHeight(100);
        canvas.add(img).renderAll();
      });
    }
  }, [canvas, signData]);

  /** 填上背景档案 */
  useEffect(() => {
    if (canvas && bgFileData) {
      fabric.Image.fromURL(bgFileData, (img) => {
        canvas.setBackgroundImage(bgFileData).renderAll();
        canvas.setHeight(img.height);
        canvas.setWidth(img.width);
        scaleAndPositionImage(img);
      });
    }
  }, [canvas, bgFileData]);

  useEffect(() => {
    window.addEventListener("keydown", handleUserKeyPress);
    return () => {
      window.removeEventListener("keydown", handleUserKeyPress);
    };
  });

  /** 缩放 */
  const scaleAndPositionImage = (bgImage) => {
    const { canvasWidth, canvasHeight } = setCanvasZoom();

    const canvasAspect = canvasWidth / canvasHeight;
    const imgAspect = bgImage.width / bgImage.height;
    let left, top, scaleFactor;

    if (canvasAspect >= imgAspect) {
      scaleFactor = canvasWidth / bgImage.width;
      left = 0;
      top = -(bgImage.height * scaleFactor - canvasHeight) / 2;
    } else {
      scaleFactor = canvasHeight / bgImage.height;
      top = 0;
      left = -(bgImage.width * scaleFactor - canvasWidth) / 2;
    }

    canvas.setBackgroundImage(bgImage, canvas.renderAll.bind(canvas), {
      top: top,
      left: left,
      originX: "left",
      originY: "top",
      scaleX: scaleFactor,
      scaleY: scaleFactor
    });
  };

  const setCanvasZoom = () => {
    let canvasWidth = canvasOriginalWidth * 1;
    let canvasHeight = canvasOriginalHeight * 1;

    canvas.setWidth(canvasWidth);
    canvas.setHeight(canvasHeight);
    return { canvasWidth, canvasHeight };
  };

  /** 监听删除 */
  const handleUserKeyPress = (e) => {
    console.log(e, e.keyCode);
    if (e.keyCode === 8) {
      deleteSelectedObjectsFromCanvas();
    }
  };

  /** 删除选取物件 */
  const deleteSelectedObjectsFromCanvas = () => {
    console.log("canvas", canvas);
    if (canvas) {
      const activeObject = canvas.getActiveObject();
      const activeGroup = canvas.getActiveGroup();

      console.log("activeObject", activeObject);
      console.log("activeGroup", activeGroup);
      if (activeObject) {
        canvas.remove(activeObject);
      } else if (activeGroup) {
        const objectsInGroup = activeGroup.getObjects();
        canvas.discardActiveGroup();
        objectsInGroup.forEach(function (object) {
          canvas.remove(object);
        });
      }
    }
  };

  /** 下载 */
  const download = () => {
    const dataURL = canvas.toDataURL({ format: "png" });

    const link = document.createElement("a");
    link.download = "my-image.png";
    link.href = dataURL;
    link.target = "_blank";
    document.body.appendChild(link);
    link.click();
    link.parentNode.removeChild(link);
  };

  return (
    <Wrapper>
      <div>
        <button onClick={download}>下载</button>
      </div>
      <Main>
        <canvas ref={mainRef} style={{ border: `2px solid #000` }}></canvas>
      </Main>
    </Wrapper>
  );
};

export default Output;

输出的成果

成果

输出的档案

参考

resize
https://jsfiddle.net/whippet71/7s5obuk2/

network error
https://stackoverflow.com/questions/37135417/download-canvas-as-png-in-fabric-js-giving-network-error/37151835


<<:  Day 30:Ansible Role

>>:  30-29 之 DDD 战术篇 2 - Aggregate ( 未完成 )

[Day 23] 实作-搜寻表单 v-expansion-panels

昨天设计完介面了, 今天就是做前端啦,建立新页面跟放上面的header照片,之前都讲过了 Expan...

Day08-元件特性

元件是Vue最强大的功能之一,可以将重复的程序码封装,提高效率和维护性。 元件组织 把网页分成区块,...

那些被忽略但很好用的 Web API / Animation On Scroll

学以致用是最快乐的事情 昨天我们认识了 IntersectionObserver,知道它可以侦测到...

Day20:今天我们来聊一下如何使用bettercap工具来拦截 HTTP 流量

攻击者可以使用session hijacking来发起各种攻击,例如中间人(MITM)攻击。 在MI...

Day 13: 时间管理、预估、压力 (待改进中... )

CH9: 时间管理 「专业开发人员同样清楚会议的高昂成本,他们同样清楚自己的时间是宝贵的。所以,如果...