이미지를 첨부하기

  • 홈페이지를 만들다 보면 이미지 첨부를 하는 경우가 있다.
  • 이럴때 단순 input type=file로 넣는것도 있지만 복사 붙여넣기, 드래그드랍으로 넣는 방식을 소개한다.
  • react를 기준으로 소개 합니다.
  • 전체 적인 구조는 이렇습니다.
    • 입력
      • 입력을 받는 element
        • 드래그 드랍 이벤트 적용, past이벤트 적용
      • 입력을 받는 input태그
    • 출력
      • 미리보기가 되는 img태그
    • 전송
      • form형식으로 전송
      • imgSrc를 보내는 방법을 소개한다.

첨부 방법들

  • 일반적인 첨부, 붙여넣어서 첨부, 이미지를 드래그 해서 첨부
  • 3가지 첨부방법을 소개한다.

일반 첨부

  • input type=file를 이용한 일반적인 첨부방법

//---------------javascript 영역

const [imgsrc, setImgsrc] = useState("" as any); //첨부로 받은 이미지src를 저장하는 useState

//파일 첨부시 작동 함수
const readImage = (e: any) => {
  const file = e.target.files[0]; //파일을 한개 가져온다.
  const reader = new FileReader();
  reader.readAsDataURL(file);//읽어온 파일을 filereader로 받는다.

  reader.onloadend = () => {
    //reader.result에 첨부로 넣은 이미지의 src를 얻을 수 있다.
    setImgsrc(reader.result);
  };
};


//---------------html영역
<input
  id={id} /*label을 위한 id지정*/
  type="file" //타입은 file로
  accept="image/*" //이미지 형식만 받는다.
  className="hidden" //tailwinds로 display:hidden이라는 뜻
  multiple={false} //여러 파일을 허용하는지 여부
  onChange={readImage} //파일이 입력시 작동하는 함수
/>

/*위의 input과 연결된다. */
//이부분을 클릭시 파일 첨부가 열린다, 이쁘게 꾸미자.
<label htmlFor={id}>
  첨부하기.
</label>

/*이미지를 확인하고 싶을때*/
<img
  className="max-h-full"
  src={
    imgsrc //만약 위에서 파일이 첨부되면 imgSrc에 값이 들어가고 확인이 가능해진다.
      ? imgsrc
      : `https://dummyimage.com/300x150/ffffff/000000.png&text=PRIVIEW` //imgSrc가 없을때 기본이미지
  }
  alt="미리보기"
/>

복사 붙여 넣기

  • 붙여넣기 이벤트를 적용한다.
//---------------javascript 영역
const [imgsrc, setImgsrc] = useState("" as any); //첨부로 받은 이미지src를 저장하는 useState

//복붙 이미지 첨부 이벤트
const pastImage = (e: any) => {
  var item = e.clipboardData.items[0];
  if (item.type.indexOf("image") === 0) {//붙여넣는게 image일때만 작동
    var blob = item.getAsFile();
    const reader = new FileReader();
    reader.readAsDataURL(blob);
    reader.onload = function () {
      setImgsrc(reader.result);
    };
  }
};

//---------------html영역
<div onPaste={pastImage}>
  {/*붙여 넣는 영역*/}
  {/*img태그 내부에 넣어서 붙여넣고 미리보는 형태로 만들 수 있다.*/}
  <img
    src={
      imgsrc
        ? imgsrc
        : `https://dummyimage.com/300x150/ffffff/000000.png&text=PRIVIEW`
    }
    alt="미리보기"
  />
</div>

드래그, 드랍 넣기

  • 드래그이벤트들을 적용한다.
//---------------javascript 영역
const [imgsrc, setImgsrc] = useState("" as any); //첨부로 받은 이미지src를 저장하는 useState

//https://velog.io/@yiyb0603/React%EC%97%90%EC%84%9C-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%ED%95%98%EA%B8%B0
//드래그 이벤트
//이미지를 드래그해서 입력
const dragEnter = (e: any) => {
  //아래와 같은 막는것을 넣어줘야 drop이벤트를 제대로 잡을 수 있다.
  e.stopPropagation(); //중요
  e.preventDefault(); //중요
};
const dragLeave = (e: any) => {
  e.stopPropagation();
  e.preventDefault();
};
const dragOver = (e: any) => {
  e.stopPropagation();
  e.preventDefault();
};
const drop = (e: any) => {
  e.preventDefault();
  let file = e.dataTransfer && e.dataTransfer.files[0];
  if (!file) {
    //드래그한것이 없을때 or 파일이 아니면
    return;
  }
  if (file.type.indexOf("image") === 0) {
    //드랍한것이 image일때만 작동
    const reader = new FileReader();
    reader.onload = function () {
      setImgsrc(reader.result);
    };

    reader.readAsDataURL(file);
  }
};

//---------------html영역

<div
  onDragEnter={dragEnter} //드래그해서 들어올때
  onDragLeave={dragLeave} //드래그한것이 나갈때
  onDragOver={dragOver} //드래그하고 태그위에 있을때
  onDrop={drop} //드랍했을때
>
  {/*드래그 드랍 영역*/}
  {/*미리보는 img태그를 통해 드랍하고 나서 미리보는 형태가 가능하다.*/}
  <img
    className="max-h-full"
    src={imgsrc ? imgsrc : `https://dummyimage.com/300x150/ffffff/000000.png&text=PRIVIEW`}
    alt="미리보기"
  />
</div>;

첨부후 전송하기

  • 전송하는 axios의 설정
let fileApi = axios.create({
  headers: {
    "Content-Type": "multipart/form-data", //multipart/form-data로 전송하자!
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Headers": "*",
    "Access-Control-Allow-Methods": "POST, GET, PUT, DELETE",
  },
});
  • 전송을 위한 값 넣기
//https://stackoverflow.com/questions/18867697/how-to-upload-canvas-image-through-formdata-multipart-with-jquery-ajax
//일반 imgSrc(dataURI)는 서버에서 받지 못한다. 이를 blob으로 변환해줘야 한다.
export function dataURItoBlob(dataURI: any) {
  // convert base64/URLEncoded data component to raw binary data held in a string
  var byteString;
  if (dataURI.split(",")[0].indexOf("base64") >= 0) byteString = atob(dataURI.split(",")[1]);
  else byteString = unescape(dataURI.split(",")[1]);

  // separate out the mime component
  var mimeString = dataURI.split(",")[0].split(":")[1].split(";")[0];

  // write the bytes of the string to a typed array
  var ia = new Uint8Array(byteString.length);
  for (var i = 0; i < byteString.length; i++) {
    ia[i] = byteString.charCodeAt(i);
  }
  return new Blob([ia], { type: mimeString });
}

const submit = () => {
  let formData = new FormData(); // formData 객체를 생성한다.
  formData.append("dto", `{ "badgeType": "${id}" }`); //필요하면 추가 파라메터도 같이 전송도 가능하다
  formData.append("file", dataURItoBlob(imgsrc)); //이미지 소스를 전송

  /*이 formData를 전송한다.*/
  formData //axios는 multipart/form-data로 전송
};

//api전송
fileApi
  .post(url, formData)
  .then(({ data }) => data);

전체 코드 보여줘요!

  • 한번 전체 코드를 확인합시다.
    • 중간 태그가 이상한 부분은 커스텀 태그의 흔적
    • 알아서 button태그에 맞게 넣거나, div로 대체해서 구현하기.
    • tailwinds로 클래스명이 다 깁니다.

export default function ImageInput({ id }: Props) {
  const [imgsrc, setImgsrc] = useState("" as any);
  const [drag, setDrag] = useState(false); //드래그 여부
  const [readyImg, setReadyImg] = useState(false); //이미지 준비 여부

  //query - api를 호출하는 부분임 , react-query가 아니라 그냥 axios를 사용해도 무관
  const submitMutate = useUserBadgeSubmit(id, setImgsrc);

  const submit = () => {
    let formData = new FormData(); // formData 객체를 생성한다.
    formData.append("dto", `{ "badgeType": "${id}" }`);
    formData.append("file", dataURItoBlob(imgsrc)); //이미지 소스
    //dataURItoBlob은 별도의 util파일에서 import해서 사용하자.

    /*이부분은 수정 필요*/
    const payload: BadgeSubmitType = {
      formdata: formData,
    };
    //여기서 파일을 전송하는 api를 넣자!
    submitMutate.mutate(payload);
    /*이부분은 수정 필요*/

    setReadyImg(false);
  };
  //이미지 첨부 이벤트
  const readImage = (e: any) => {
    const file = e.target.files[0];
    const reader = new FileReader();
    reader.readAsDataURL(file);
    reader.onloadend = () => {
      setImgsrc(reader.result);
      setReadyImg(true);
    };
  };

  //복붙 이미지 첨부 이벤트
  const pastImage = (e: any) => {
    var item = e.clipboardData.items[0];
    if (item.type.indexOf("image") === 0) {
      var blob = item.getAsFile();
      const reader = new FileReader();
      reader.onload = function () {
        setImgsrc(reader.result);
        setReadyImg(true);
      };

      reader.readAsDataURL(blob);
    }
  };

  //https://velog.io/@yiyb0603/React%EC%97%90%EC%84%9C-%EB%93%9C%EB%9E%98%EA%B7%B8-%EC%95%A4-%EB%93%9C%EB%A1%AD%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C-%ED%95%98%EA%B8%B0
  //드래그 이벤트
  //이미지를 드래그해서 입력
  const dragEnter = (e: any) => {
    //아래와 같은 막는것을 넣어줘야 drop이벤트를 잡을 수 있다.
    setDrag(true);
    e.stopPropagation();
    e.preventDefault();
  };
  const dragLeave = (e: any) => {
    setDrag(false);
    e.stopPropagation();
    e.preventDefault();
  };
  const dragOver = (e: any) => {
    setDrag(true);
    e.stopPropagation();
    e.preventDefault();
  };
  const drop = (e: any) => {
    setDrag(false);
    e.preventDefault(); //중요
    let file = e.dataTransfer && e.dataTransfer.files[0];
    if (!file) {
      return;
    }
    if (file.type.indexOf("image") === 0) {
      const reader = new FileReader();
      reader.onload = function () {
        setImgsrc(reader.result);
        setReadyImg(true);
      };

      reader.readAsDataURL(file);
    }
  };

  //NO_REQUEST,IN_PROGRESS, FINISH
  return (
    <>
      <div className="flex flex-col justify-center">
        <input
          id={id}
          type="file"
          accept="image/*"
          className="hidden"
          multiple={false}
          onChange={readImage}
        />
        {/* 미리보기 이미지  */}
        <div>
          <div className="flex justify-between">
            <label htmlFor={id}>
              <button value="첨부하기" />
            </label>
          </div>
          <div
            className="mb-16 mt-16 flex h-350 w-400 items-center justify-center rounded-8 bg-gray-200"
            onPaste={pastImage}
            onDragEnter={dragEnter}
            onDragLeave={dragLeave}
            onDragOver={dragOver}
            onDrop={drop}
          >
            {drag ? (
              <div className="flex h-full w-full items-center justify-center rounded-8 bg-blue-200">
                <div className="flex h-5/6  w-5/6 items-center justify-center rounded-8 border-2 border-dashed border-black bg-red-300">
                  Drop해주세요
                </div>
              </div>
            ) : (
              <img
                className="max-h-full"
                src={
                  imgsrc
                    ? imgsrc
                    : `https://dummyimage.com/300x150/ffffff/000000.png&text=PRIVIEW`
                }
                alt="미리보기"
              />
            )}
          </div>
        </div>
        {/* 이미지 전송 */}
        <div className="flex justify-end">
          <button
            value="제출하기"
            onClick={submit}
            disable={readyImg} //이미지가 준비되어야 버튼활성화
          />
        </div>
      </div>
    </>
  );
}