본문 바로가기
개발/React

리액트 - 이미지 캡처해서 웹에 붙여넣기 (Capture and Paste Image)

by 피로물든딸기 2024. 1. 15.
반응형

리액트 전체 링크

 

아래 결과는 링크에서 확인할 수 있다.

 

이미지를 캡처하여 웹 프론트엔드에 붙여넣기를 해보자.

 

아래와 같이 div 태그로 이미지를 붙여넣을 영역을 만든다.

onPasteCtrl + V (붙여넣기) 를 할 경우 동작하는 이벤트다.

    <div
      onPaste={handleImagePaste}
      style={{ border: "1px solid #ddd", padding: "20px", textAlign: "center" }}
    >
      <h1>이미지 붙여넣기</h1>
      {image && <img src={image} />}
    </div>

 

onPasteuseState와 함께 아래와 같이 동작한다.

클립 보드에 존재하는 데이터 중 "image"를 찾는다. (originalEvent는 옛 브라우저에서 사용된다.)

그리고 해당 이미지를 Blob 객체로 만들어서 FileReaderLoad한다.

  const [image, setImage] = useState(null);

  const handleImagePaste = (event) => {
    const items = (event.clipboardData || event.originalEvent.clipboardData).items;
    
    for (let i = 0; i < items.length; i++) {
      if (items[i].type.indexOf("image") !== -1) {
        const blob = items[i].getAsFile();
        const reader = new FileReader();

        reader.onload = () => {
          setImage(reader.result);
        };

        reader.readAsDataURL(blob);
        break;
      }
    }
  };

 

실제로 Blob 객체로 이미지를 로드하면 아래와 같이 확인을 할 수 있다.

 

전체 코드는 다음과 같다.

import React, { useState } from "react";

const Capture = () => {
  const [image, setImage] = useState(null);

  const handleImagePaste = (event) => {
    const items = (event.clipboardData || event.originalEvent.clipboardData).items;
    
    for (let i = 0; i < items.length; i++) {
      if (items[i].type.indexOf("image") !== -1) {
        const blob = items[i].getAsFile();
        const reader = new FileReader();

        reader.onload = () => {
          setImage(reader.result);
        };

        reader.readAsDataURL(blob);
        break;
      }
    }
  };

  return (
    <div
      onPaste={handleImagePaste}
      style={{ border: "1px solid #ddd", padding: "20px", textAlign: "center" }}
    >
      <h1>이미지 붙여넣기</h1>
      {image && <img src={image} />}
    </div>
  );
};

export default Capture;

선택한 영역에 붙여넣기

 

이제 조금 응용해서 선택된 영역에 이미지를 붙여넣자.

 

4개의 영역을 grid로 구분을 하여, 선택한 영역을 useState로 관리한다.

그리고 선택한 영역과 일치하는 곳은 임시로 배경색을 변경하도록 하자.

  const [quadrant, setQuadrant] = useState(null);

  ...

  const handleAreaClick = (newQuadrant) => {
    setQuadrant(newQuadrant);
  };

  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "1fr 1fr", // Col 1 : 1 분할
        gridTemplateRows: "1fr 1fr", // Row 1 : 1 분할
        height: "500px",
      }}
    >
      {[0, 1, 2, 3].map((area) => (
        <div
          key={area}
          style={{
            border: "1px solid #ddd",
            padding: "20px",
            textAlign: "center",
            backgroundColor: quadrant === area ? "#e0e0e0" : "transparent",
            cursor: "pointer",
          }}
          onClick={() => handleAreaClick(area)}
          //onPaste={...}
        >
          {images[area] && <img src={images[area]} />}
        </div>
      ))}
    </div>
  );

 

위의 코드를 실행하면 아래와 같이 선택한 영역의 배경색이 변경된다.

 

handleImagePaste에 영역이 없는 경우 return 처리하고, 해당 영역에 image를 복사하였다.

  const handleImagePaste = (event) => {
    if (quadrant === null) {
      return;
    }

    const items = (event.clipboardData || event.originalEvent.clipboardData).items;

    for (let i = 0; i < items.length; i++) {
      if (items[i].type.indexOf("image") !== -1) {
        const blob = items[i].getAsFile();
        const reader = new FileReader();

        reader.onload = () => {
          setImages((prevImages) => {
            const newImages = [...prevImages];
            newImages[quadrant] = reader.result;
            return newImages;
          });
          setQuadrant(null);
        };

        reader.readAsDataURL(blob);
        break;
      }
    }
  };

 

전체 코드는 다음과 같다.

import React, { useState } from "react";

const Capture = () => {
  const [quadrant, setQuadrant] = useState(null);
  const [images, setImages] = useState(Array(4).fill(null));

  const handleImagePaste = (event) => {
    if (quadrant === null) {
      return;
    }

    const items = (event.clipboardData || event.originalEvent.clipboardData).items;

    for (let i = 0; i < items.length; i++) {
      if (items[i].type.indexOf("image") !== -1) {
        const blob = items[i].getAsFile();
        const reader = new FileReader();

        reader.onload = () => {
          setImages((prevImages) => {
            const newImages = [...prevImages];
            newImages[quadrant] = reader.result;
            return newImages;
          });
          setQuadrant(null);
        };

        reader.readAsDataURL(blob);
        break;
      }
    }
  };

  const handleAreaClick = (newQuadrant) => {
    setQuadrant(newQuadrant);
  };

  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "1fr 1fr", // Col 1 : 1 분할
        gridTemplateRows: "1fr 1fr", // Row 1 : 1 분할
        height: "500px",
      }}
    >
      {[0, 1, 2, 3].map((area) => (
        <div
          key={area}
          style={{
            border: "1px solid #ddd",
            padding: "20px",
            textAlign: "center",
            backgroundColor: quadrant === area ? "#e0e0e0" : "transparent",
            cursor: "pointer",
          }}
          onClick={() => handleAreaClick(area)}
          onPaste={handleImagePaste}
        >
          {images[area] && <img src={images[area]} />}
        </div>
      ))}
    </div>
  );
};

export default Capture;

navigator로 구현하기

 

위의 코드를 navigator로 구현하면 아래와 같다.

import React, { useState } from "react";

const Capture = () => {
  const [quadrant, setQuadrant] = useState(null);
  const [images, setImages] = useState(Array(4).fill(null));

  const handleImagePaste = async () => {
    if (quadrant === null) {
      return;
    }

    try {
      const clipboardItems = await navigator.clipboard.read();

      for (const item of clipboardItems) {
        if (
          item.types.includes("image/png") ||
          item.types.includes("image/jpeg")
        ) {
          const blob = await item.getType("image/png" || "image/jpeg");
          const reader = new FileReader();

          reader.onload = () => {
            setImages((prevImages) => {
              const newImages = [...prevImages];
              newImages[quadrant] = reader.result;
              return newImages;
            });
            setQuadrant(null);
          };

          reader.readAsDataURL(blob);
          break;
        }
      }
    } catch (error) {
      console.error("Error reading clipboard:", error);
    }
  };

  const handleAreaClick = (newQuadrant) => {
    setQuadrant(newQuadrant);
  };

  return (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "1fr 1fr", // Col 1 : 1 분할
        gridTemplateRows: "1fr 1fr", // Row 1 : 1 분할
        height: "500px",
      }}
    >
      {[0, 1, 2, 3].map((area) => (
        <div
          key={area}
          style={{
            border: "1px solid #ddd",
            padding: "20px",
            textAlign: "center",
            backgroundColor: quadrant === area ? "#e0e0e0" : "transparent",
            cursor: "pointer",
          }}
          onClick={() => handleAreaClick(area)}
          onPaste={handleImagePaste}
        >
          {images[area] && <img src={images[area]} />}
        </div>
      ))}
    </div>
  );
};

export default Capture;

 

navigator는 최신 브라우저에서만 지원되고 비동기 처리를 해야하지만,

navigator.clipboard에서 다양한 데이터 타입을 다룰 수 있다.

반응형

댓글