이미지&비디오 여러개 업로드 - 미리보기/시간제한/삭제Frontend/UI&UX2022. 7. 5. 21:04
Table of Contents
What to do?
- 이미지와 비디오를 여러개 업로드하고 미리보기를 할 수 있게 하자.
- 동영상이 여러개 올라올 수 있으므로 autoplay는 false로 해둠! 한번에 여러개 재생되면 어지러워..
<input type="file" multiple={true} accept="video/*, image/*"/>
- 만약 비디오의 길이가 16초보다 길면 해당 비디오는 alert를 띄우고 업로드가 안되게하자.
- 각 이미지와 비디오를 hover하면 우측 상단에 삭제 버튼이 보이게 하고 클릭하면 삭제하는 기능도 추가하자
- css로 해결할 부분!
- 미리보기 URL을 createObjectURL로 만들기 때문에 사용하지 않을 때나 컴포넌트가 언마운트될 때는 적절히 revokeObjectURL로 미리보기 URL을 폐기하자
소스코드
App.js
import Uploader from "./components/Uploader";
const App = () => {
return (
<div className="App">
<Uploader/>
</div>
);
}
export default App;
components/Uploader.js
- video의 길이를 재기 위해 미리보기 URL로 element를 생성하는 것과 시간을 재는 부분이 비동기적으로 수행되어서 state를 업데이트하는 함수보다 늦게 실행되기 때문에 await와 Promise로 적절히 동기적으로 처리되게 만들었다.
- video 엘리먼트의 속성에 controls=true와 autoplay=false 속성을 줘서 재생 버튼이 보이게하고 자동 재생은 막아두었다.
- createObjectURL로 미리보기 URL로 만들기 때문에 적절히 revokeObjectURL로 사용하지 않는 미리보기 URL을 폐기하는 것이 무조건 좋음!
import {useEffect, useState} from 'react';
import "./uploader.scss";
import {Button} from "@mui/material";
import HighlightOffIcon from '@mui/icons-material/HighlightOff';
const Uploader = () => {
/*
fileList는 아래와 같은 object의 array로 구성
{
fileObject: files[i],
preview_URL: preview_URL,
type: fileType,
}
*/
const [fileList, setFileList] = useState([]);
let inputRef;
const saveImage = async (e) => {
e.preventDefault();
// state update전 임시로 사용할 array
const tmpFileList = [];
const files = e.target.files;
if (files) {
for (let i = 0; i < files.length; i++) {
const preview_URL = URL.createObjectURL(files[i]);
const fileType = files[i].type.split("/")[0];
if (fileType === "video") {
/*
video일 때 createElement 후 video의 시간을 재는 과정이 비동기라서 state update보다 나중에 실행되어
정상적으로 state에 반영되지 않기 때문에 promise와 await로 잠시 코드를 멈춰서 비동기를 해결하게 한다.
*/
await new Promise((resolve) => {
const videoElement = document.createElement("video");
videoElement.src = preview_URL;
const timer = setInterval(() => {
if (videoElement.readyState === 4) {
if (videoElement.duration > 16) {
alert("동영상의 길이가 16초보다 길면 안됩니다");
// src에 넣지 않을 것이므로 미리보기 URL 제거
URL.revokeObjectURL(preview_URL);
} else {
fileList.push({
fileObject: files[i],
preview_URL: preview_URL,
type: fileType,
});
}
clearInterval(timer);
// Promise resolve
resolve("good");
}
}, 500);
});
} else {
fileList.push({
fileObject: files[i],
preview_URL: preview_URL,
type: fileType,
});
}
}
}
// 마지막에 state update
setFileList([...tmpFileList, ...fileList]);
};
// index에 해당하는 state 삭제
const deleteImage = (index) => {
const tmpFileList = [...fileList];
tmpFileList.splice(index, 1);
setFileList(tmpFileList);
}
useEffect(()=>{
// 컴포넌트가 언마운트되면 revokeObjectURL로 미리보기 URL 모두 삭제
return () => {
fileList?.forEach((item)=>{
URL.revokeObjectURL(item.preview_URL);
})
}
}, [])
console.log(fileList);
return (
<div className="uploader-wrapper">
<input
type="file" multiple={true} accept="video/*, image/*"
onChange={saveImage}
// 클릭할 때 마다 file input의 value를 초기화 하지 않으면 버그가 발생할 수 있다
// 사진 등록을 두개 띄우고 첫번째에 사진을 올리고 지우고 두번째에 같은 사진을 올리면 그 값이 남아있음!
onClick={(e) => e.target.value = null}
ref={refParam => inputRef = refParam}
style={{display: "none"}}
/>
<div className="file-container">
{fileList?.map((item, index) => (
<div className="file-wrapper">
{item.type === "image" ? (
<img src={item.preview_URL}/>
) : (
<video src={item.preview_URL} autoPlay={false} controls={true}/>
)}
<div className="delete-button" onClick={()=>{deleteImage(index)}}>
<HighlightOffIcon fontSize="large" color="error"/>
</div>
</div>
))}
</div>
<div className="upload-button">
<Button variant="contained" onClick={() => inputRef.click()}>
Upload
</Button>
</div>
</div>
);
}
export default Uploader;
components/Uploader.scss
- 이미지를 hover할 때 삭제 버튼을 클릭할 수 있고 보일수 있게 적절히 opacity와 z-index 값을 넣어주자
.uploader-wrapper{
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.file-container{
width: 500px;
height: 250px;
padding: 16px;
margin: 32px 0;
border: 1px solid slateblue;
border-radius: 15px;
display: flex;
align-items: center;
// 넘치면 스크롤
overflow: auto;
.file-wrapper{
position: relative;
// 사진이 줄어드는 것 방지
flex-shrink: 0;
width: 200px;
height: 200px;
margin: 0 10px;
// hover할 때 delete-button이 보이고 사용 클릭 가능한 상태로 변경
&:hover{
.delete-button{
opacity: 1;
z-index: 1;
}
}
img, video{
width: 100%;
height: 100%;
object-fit: cover;
}
.delete-button{
// 부모 우측 상단에 고정하기 위해 absolute사용
position: absolute;
top: 0;
right: 0;
cursor: pointer;
// 맨 처음엔 보이지도 않고 클릭할 수도 없게 만듬
opacity: 0;
z-index: 0;
}
}
}
}
'Frontend > UI&UX' 카테고리의 다른 글
carousel 만들기 (0) | 2022.07.20 |
---|---|
snap-scroll & scroll된 동영상 재생 (youtube shorts) (0) | 2022.07.07 |
비디오&이미지 업로더 - 미리보기/시간 제한 (0) | 2022.07.01 |
react-dnd → Drag & Drop (1) | 2022.06.30 |
tailwind-css (0) | 2022.06.11 |
@덕구공 :: Duck9s'
주니어 개발자에욤
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!