이미지를 첨부하기
- 홈페이지를 만들다 보면 이미지 첨부를 하는 경우가 있다.
- 이럴때 단순 input type=file로 넣는것도 있지만 복사 붙여넣기, 드래그드랍으로 넣는 방식을 소개한다.
- react를 기준으로 소개 합니다.
- 전체 적인 구조는 이렇습니다.
- 입력
- 입력을 받는 element
- 드래그 드랍 이벤트 적용, past이벤트 적용
- 입력을 받는 input태그
- 입력을 받는 element
- 출력
- 미리보기가 되는 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>
</>
);
}