반응형
이번 글을 통해 배워갈 내용
- 리액트 이미지 업로드
- 리액트 이미지 필터
- 리액트 이미지 출력
일단 리액트 프로젝트를 만든 다음
npx create-react-app my-app
cd my-app
npm start
기본으로 생성되는
App.js, App.css
파일만 수정하였습니다
state 들과 canvas ref들입니다
const [rawImages, setRawImages] = useState([])
const [rawPreviews, setRawPreviews] = useState([])
const [filteredPreviews, setFilteredPreviews] = useState([])
const canvasRef = useRef(null)
전체 렌더는 아래와 같습니다
return (
<div className="App">
<FileInputView />
<RawImagesView />
<FilteredImagesView />
<MyCanvas />
</div>
);
파일을 업로드합니다
const FileInputView = ()=>{
return <input type="file" onChange={handleFileInput} accept=".jpg, .jpeg, .png"></input>
}
업로드한 파일을 핸들링합니다
const file = e.target.files[0]
if(!isValidFile(file)){
alert("is not valid file")
return
}
addRawImages(file)
addRawPreviews(file)
}
const addRawImages = (file) => {
setRawImages([...rawImages, file])
}
const addRawPreviews = (file) => {
setRawPreviews([...rawPreviews, URL.createObjectURL(file)])
}
const isValidFile = (file)=>{
// todo file validation
return true
}
(누수방지) 메모리 관리를 위해서 useEffect도 추가합니다
useEffect(() => {
// free memory when ever this component is unmounted
if(rawPreviews != null){
return () => rawPreviews.forEach(rp=>URL.revokeObjectURL(rp))
}
}, [rawPreviews])
자 이제 이미지가 추가되면 rawImage들이 보입니다
const RawImagesView = ()=>{
return <div className='imageCon'>
{ rawPreviews.map((item, idx) => <div key={"raw"+idx}><img src={item} alt="not valid" onClick={(e)=>createFilteredImage(e)}/></div>)}
</div>
}
생성된 rawImage를 클릭하면 필터 이미지가 생성됩니다
const createFilteredImage = async (e) =>{
const image = e.target
const ctx = canvasRef.current.getContext('2d')
// filter
ctx.width = image.width;
ctx.height = image.height;
ctx.filter = 'grayscale(1)';
ctx.drawImage(image, 0, 0, image.width, image.height);
// save
const dataURL = canvasRef.current.toDataURL();
setFilteredPreviews([...filteredPreviews, dataURL])
}
필터 이미지를 보여줍니다
const FilteredImagesView = ()=>{
return <div className='imageCon'>
{ filteredPreviews.map((item, idx) => <div key={"ftr"+idx}><img src={item} alt="not valid" /></div>)}
</div>
}
전체 코드는 아래와 같습니다
(시간 관계상 리닝은 하지 않았습니다)
import './App.css';
import { useState,useEffect,useRef } from 'react';
function App() {
const [rawImages, setRawImages] = useState([])
const [rawPreviews, setRawPreviews] = useState([])
const [filteredPreviews, setFilteredPreviews] = useState([])
const canvasRef = useRef(null)
const handleFileInput = (e) => {
const file = e.target.files[0]
if(!isValidFile(file)){
alert("is not valid file")
return
}
addRawImages(file)
addRawPreviews(file)
}
const addRawImages = (file) => {
setRawImages([...rawImages, file])
}
const addRawPreviews = (file) => {
setRawPreviews([...rawPreviews, URL.createObjectURL(file)])
}
const isValidFile = (file)=>{
// todo file validation
return true
}
useEffect(() => {
// free memory when ever this component is unmounted
if(rawPreviews != null){
return () => rawPreviews.forEach(rp=>URL.revokeObjectURL(rp))
}
}, [rawPreviews])
const createFilteredImage = async (e, imagePath) =>{
const image = e.target
const ctx = canvasRef.current.getContext('2d')
// ctx.drawImage(image, 0, 0, image.width * .6, image.height * .6);
// ctx.drawImage(imagePath, 0,0, )
// filter
ctx.width = image.width;
ctx.height = image.height;
ctx.filter = 'grayscale(1)';
ctx.drawImage(image, 0, 0, image.width, image.height);
// save
const dataURL = canvasRef.current.toDataURL();
setFilteredPreviews([...filteredPreviews, dataURL])
}
const FileInputView = ()=>{
return <input type="file" onChange={handleFileInput} accept=".jpg, .jpeg, .png"></input>
}
const RawImagesView = ()=>{
return <div className='imageCon'>
{ rawPreviews.map((item, idx) => <div key={"raw"+idx}><img src={item} alt="not valid" onClick={(e,item)=>createFilteredImage(e,item)}/></div>)}
</div>
}
const FilteredImagesView = ()=>{
return <div className='imageCon'>
{ filteredPreviews.map((item, idx) => <div key={"ftr"+idx}><img src={item} alt="not valid" /></div>)}
</div>
}
const MyCanvas = ()=>{
return <canvas ref={canvasRef} style={{display:'none'}}></canvas>
}
return (
<div className="App">
<FileInputView />
<RawImagesView />
<FilteredImagesView />
<MyCanvas />
</div>
);
}
export default App;
// https://img.ly/blog/how-to-manipulate-an-image-with-jimp-in-react/
// https://stackoverflow.com/questions/66254514/filter-an-image-on-upload-in-react
// https://www.npmjs.com/package/jimp
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/filter
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL
// https://stackoverflow.com/questions/6850276/how-to-convert-dataurl-to-file-object-in-javascript
추가한 CSS
.imageCon{
margin-top: 200px;
width: 100%;
display: grid;
height: auto;
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
추후에는 JIMP 라이브러리를 사용해서 간편하게 작업해보겠습니다
참조 및 인용
https://codemasterkimc.tistory.com/50
반응형
'Javascript > React' 카테고리의 다른 글
React에 Redux Toolkit 사용해보기 Typescript Vite (0) | 2023.10.05 |
---|---|
React에 카카오 지도 넣는 방법 (초간단) (Client Side Render) (0) | 2023.06.18 |
TypeScript와 함께 useReducer를 사용하는 방법 (0) | 2022.07.16 |
리액트에서 여러 체크박스들을 하나의 컴포넌트로 재활용 해보기 ts (0) | 2022.06.22 |
김씨가 리액트에서 함수 활용하는 비법 한가지 (0) | 2022.05.31 |