2

我正在尝试使用 React 和 konva 构建图像裁剪工具。我想更改裁剪矩形之外的不透明度以模糊图像的其余部分。

到目前为止,我尝试为矩形和图像设置不同的不透明度,但失败了。我查了一下,没有直接的方法可以做到这一点

这是我在这个答案的帮助下适应的裁剪功能

import React, { useState, useEffect, useRef } from "react";
import { render } from "react-dom";
import { Stage, Layer, Rect, Image } from "react-konva";
import Konva from "konva";

const App = () => {
  // Stage dims
  let sW = 720,
    sH = 720,
    sX = 0,
    sY = 0;

   let src = "https://dummyimage.com/720x720/e85de8/fff&text=SO Rocks!";
  let img = document.createElement("img");

  useEffect(() => {
    img.src = src;
    function loadStatus() {
      setloading(false);
    }
    img.addEventListener("load", loadStatus);
    return () => {
      img.removeEventListener("load", loadStatus);
    };
  }, [img, src]);

  let scale = 1;
  const [loading, setloading] = useState(true);
  const [posStart, setposStart] = useState({});
  const [posNow, setposNow] = useState({});
  const [mode, setmode] = useState("");

  /**
   * Sets the state of posStart and posNow for tracking the coordinates of the cropping rectangle
   * @param {Object} posIn - Coordinates of the pointer when MouseDown is fired
   */
  function startDrag(posIn) {
    setposStart({ x: posIn.x, y: posIn.y });
    setposNow({ x: posIn.x, y: posIn.y });
  }

  /**
   * Updates the state accordingly when the MouseMove event is fired
   * @param {Object} posIn - Coordiantes of the current position of the pointer
   */
  function updateDrag(posIn) {
    setposNow({ x: posIn.x, y: posIn.y });
    let posRect = reverse(posStart, posNow);
    r2.current.x(posRect.x1);
    r2.current.y(posRect.y1);
    r2.current.width(posRect.x2 - posRect.x1);
    r2.current.height(posRect.y2 - posRect.y1);
    r2.current.visible(true);
  }

  /**
   * Reverse coordinates if dragged left or up
   * @param {Object} r1 - Coordinates of the starting position of cropping rectangle
   * @param {Object} r2 - Coordinates of the current position of cropping rectangle
   */
  function reverse(r1, r2) {
    let r1x = r1.x,
      r1y = r1.y,
      r2x = r2.x,
      r2y = r2.y,
      d;
    if (r1x > r2x) {
      d = Math.abs(r1x - r2x);
      r1x = r2x;
      r2x = r1x + d;
    }
    if (r1y > r2y) {
      d = Math.abs(r1y - r2y);
      r1y = r2y;
      r2y = r1y + d;
    }
    return { x1: r1x, y1: r1y, x2: r2x, y2: r2y }; // return the corrected rect.
  }

  /**
   * Crops the image and saves it in jpeg format
   * @param {Konva.Rect} r - Ref of the cropping rectangle
   */
  function setCrop(r) {
    let jpeg = new Konva.Image({
      image: img,
      x: sX,
      y: sY
    });
    jpeg.cropX(r.x());
    jpeg.cropY(r.y());
    jpeg.cropWidth(r.width() * scale);
    jpeg.cropHeight(r.height() * scale);
    jpeg.width(r.width());
    jpeg.height(r.height());
    const url = jpeg.toDataURL({ mimeType: "image/jpeg", quality: "1.0" });
    const a = document.createElement("a");
    a.href = url;
    a.download = "cropped.jpg";
    a.click();
  }

  // Foreground rect to capture events
  const r1 = useRef();

  // Cropping rect
  const r2 = useRef();

  const image = useRef();

  return (
    <div className="container">
      <Stage width={sW} height={sH}>
        <Layer>
          {!loading && (
            <Image
              ref={image}
              {...{
                image: img,
                x: sX,
                y: sY
              }}
            />
          )}
          <Rect
            ref={r1}
            {...{
              x: 0,
              y: 0,
              width: sW,
              height: sH,
              fill: "white",
              opacity: 0
            }}
            onMouseDown={function (e) {
              setmode("drawing");
              startDrag({ x: e.evt.layerX, y: e.evt.layerY });
            }}
            onMouseMove={function (e) {
              if (mode === "drawing") {
                updateDrag({ x: e.evt.layerX, y: e.evt.layerY });
                image.current.opacity(0.5);
                r2.current.opacity(1);
              }
            }}
            onMouseUp={function (e) {
              setmode("");
              r2.current.visible(false);
              setCrop(r2.current);
              image.current.opacity(1);
            }}
          />
          <Rect
            ref={r2}
            listening={false}
            {...{
              x: 0,
              y: 0,
              width: 0,
              height: 0,
              stroke: "white",
              dash: [5, 5]
            }}
          />
        </Layer>
      </Stage>
    </div>
  );
};

render(<App />, document.getElementById("root"));

上述代码的演示

4

1 回答 1

2

可以使用 Konva.Group 及其 clip 属性来完成。向组中添加一个新的 Konva.Image 并将其裁剪位置和大小设置为与裁剪矩形相同。不要忘记将组的侦听属性设置为 false 否则会使事情复杂化。这是最终结果

import { render } from "react-dom";
import React, { useLayoutEffect, useRef, useState } from "react";
import { Stage, Layer, Image, Rect, Group } from "react-konva";

/**
 * Crops a portion of image in Konva stage and saves it in jpeg format
 * @param {*} props - Takes no props
 */
function Cropper(props) {
  // Stage dims
  let sW = 720,
    sH = 720,
    sX = 0,
    sY = 0;

  let src = "https://dummyimage.com/720x720/e85de8/fff&text=SO Rocks!";
  let img = new window.Image();
  useLayoutEffect(() => {
    img.src = src;
    function loadStatus() {
      // img.crossOrigin = "Anonymous";
      setloading(false);
    }
    img.addEventListener("load", loadStatus);
    return () => {
      img.removeEventListener("load", loadStatus);
    };
  }, [img, src]);
  let i = new Konva.Image({
    x: 0,
    y: 0,
    width: 0,
    height: 0
  });
  let scale = 1;
  const [loading, setloading] = useState(true);
  const [posStart, setposStart] = useState({});
  const [posNow, setposNow] = useState({});
  const [mode, setmode] = useState("");

  /**
   * Sets the state of posStart and posNow for tracking the coordinates of the cropping rectangle
   * @param {Object} posIn - Coordinates of the pointer when MouseDown is fired
   */
  function startDrag(posIn) {
    setposStart({ x: posIn.x, y: posIn.y });
    setposNow({ x: posIn.x, y: posIn.y });
  }

  /**
   * Updates the state accordingly when the MouseMove event is fired
   * @param {Object} posIn - Coordiantes of the current position of the pointer
   */
  function updateDrag(posIn) {
    setposNow({ x: posIn.x, y: posIn.y });
    let posRect = reverse(posStart, posNow);
    r2.current.x(posRect.x1);
    r2.current.y(posRect.y1);
    r2.current.width(posRect.x2 - posRect.x1);
    r2.current.height(posRect.y2 - posRect.y1);
    r2.current.visible(true);
    grp.current.clip({
      x: posRect.x1,
      y: posRect.y1,
      width: posRect.x2 - posRect.x1,
      height: posRect.y2 - posRect.y1
    });
    grp.current.add(i);
    i.image(img);
    i.width(img.width);
    i.height(img.height);
    i.opacity(1);
  }

  /**
   * Reverse coordinates if dragged left or up
   * @param {Object} r1 - Coordinates of the starting position of cropping rectangle
   * @param {Object} r2 - Coordinates of the current position of cropping rectangle
   */
  function reverse(r1, r2) {
    let r1x = r1.x,
      r1y = r1.y,
      r2x = r2.x,
      r2y = r2.y,
      d;
    if (r1x > r2x) {
      d = Math.abs(r1x - r2x);
      r1x = r2x;
      r2x = r1x + d;
    }
    if (r1y > r2y) {
      d = Math.abs(r1y - r2y);
      r1y = r2y;
      r2y = r1y + d;
    }
    return { x1: r1x, y1: r1y, x2: r2x, y2: r2y }; // return the corrected rect.
  }

  /**
   * Crops the image and saves it in jpeg format
   * @param {Konva.Rect} r - Ref of the cropping rectangle
   */
  function setCrop(r) {
    let jpeg = new Konva.Image({
      image: img,
      x: sX,
      y: sY
    });
    jpeg.cropX(r.x());
    jpeg.cropY(r.y());
    jpeg.cropWidth(r.width() * scale);
    jpeg.cropHeight(r.height() * scale);
    jpeg.width(r.width());
    jpeg.height(r.height());
    const url = jpeg.toDataURL({ mimeType: "image/jpeg", quality: "1.0" });
    const a = document.createElement("a");
    a.href = url;
    a.download = "cropped.jpg";
    a.click();
  }

  // Foreground rect to capture events
  const r1 = useRef();

  // Cropping rect
  const r2 = useRef();

  const image = useRef();
  const grp = useRef();

  return (
    <div className="container">
      <Stage width={sW} height={sH}>
        <Layer>
          {!loading && (
            <Image
              ref={image}
              listening={false}
              {...{
                image: img,
                x: sX,
                y: sY
              }}
            />
          )}

          <Rect
            ref={r1}
            {...{
              x: 0,
              y: 0,
              width: sW,
              height: sH,
              fill: "white",
              opacity: 0
            }}
            onMouseDown={function (e) {
              setmode("drawing");
              startDrag({ x: e.evt.layerX, y: e.evt.layerY });
              image.current.opacity(0.2);
            }}
            onMouseMove={function (e) {
              if (mode === "drawing") {
                updateDrag({ x: e.evt.layerX, y: e.evt.layerY });
              }
            }}
            onMouseUp={function (e) {
              setmode("");
              r2.current.visible(false);
              setCrop(r2.current);
              image.current.opacity(1);
              grp.current.removeChildren(i);
            }}
          />
          <Group listening={false} ref={grp}></Group>

          <Rect
            ref={r2}
            listening={false}
            {...{
              x: 0,
              y: 0,
              width: 0,
              height: 0,
              stroke: "white",
              dash: [5, 10]
            }}
          />
        </Layer>
      </Stage>
    </div>
  );
}

render(<Cropper />, document.getElementById("root"));

感谢@Vanquiished Wombat提供的所有宝贵意见。这是对他的回答的改编here

上述代码的演示

于 2020-11-21T14:04:59.290 回答