Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

냥냠

[React] react-quil 이미지 s3로 처리하기 (2) 본문

React

[React] react-quil 이미지 s3로 처리하기 (2)

sueeee-e 2024. 11. 8. 15:56

1 ) react-quill을 이용해서 텍스트 에디터 넣기 (+리사이징까지)

1. 라이브러리 설치 

 

2. 필요한 모듈, 포맷, 테마 추가

* 모듈은 useMemo로 감싸줘야 한다. 

 

3. <ReactQuill />

 

여기까지 하면 기본 에디터가 완성이 되고 나는 이미지 크기를 조절해주고 싶어서 라이브러리를 찾아봤다. 

여러가지를 사용해본 결과 

https://www.npmjs.com/package/quill-image-resize

-> https://velog.io/@jsy7517/React-Quill%EC%97%90%EC%84%9C-Image-Resize-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0

이거나

https://www.npmjs.com/package/@xeger/quill-image-actions

https://www.npmjs.com/package/@xeger/quill-image-formats

이 두 개를 같이 쓰는 것이 잘 되었다. 

 

📍resize 주의할 점 : format에 width, height 를 꼭 포함시켜줘야 한다. 

📍actions, formats 는 modules에 toobar 위에 imageActions:{}; imageFormats: {}; 추가

📍그 외는 toolbar 레벨에 ImageResize : { parchment : Quill.import('parchment') } 해주기

📍당연하지만 확인해볼 땐 데스크탑 모드에서 해야 된다 .. ㅜ 나는 모바일을 키워서 확인해보다가 안됐음 ; 

 

리사이징까지 된 최종 코드 

import { Button } from '@mui/material';
import {React, useState, useMemo, useRef} from 'react'
import ReactQuill, { Quill } from 'react-quill';
import 'react-quill/dist/quill.snow.css';
import { ImageActions } from '@xeger/quill-image-actions';
import { ImageFormats }from '@xeger/quill-image-formats';

Quill.register('modules/imageActions', ImageActions);
Quill.register('modules/imageFormats', ImageFormats);

const EditorBox = () => {
    const [value, setValue] = useState('');
    const quillRef = useRef();
    const handleSubmit = () => {
        console.log('상세설명 저장')
    }
    const formats = [
        "header", "bold","italic","underline","strike","blockquote","list","bullet", 
        "indent","link","image","video",'font','float','align','height', 'width'
      ];
    const modules = useMemo(()=> {
        return {
            imageActions: {},
            imageFormats: {},
            toolbar: {
                container: [
                    [{ 'font': [] }],
                    [{ header: [1, 2, false] }],
                    ['bold', 'italic', 'underline', 'strike', 'blockquote'],
                    [{ list: 'ordered' }, { list: 'bullet' }, { indent: '-1' }, { indent: '+1' }],
                    ['link', 'image', 'video'],
                    [{ align: [] }, { color: [] }, { background: [] }], 
                    ['clean'],
                ],
            },
        }
    }, []);

    return(
        <div>
            <ReactQuill 
            value={value} 
            theme="snow"
            ref={quillRef}
            onChange={setValue}
            formats={formats}
            modules={modules}
            style={{height:400 , overflowY:'auto'}} />
            <Button onClick={handleSubmit}>저장</Button>
        </div>
    )
}
export default EditorBox


 

2 )  이미지 처리하기 (S3)

react-quill을 사용해서 에디터를 만들고 난 뒤에 

이미지를 처리하는 방법에 대해 생각해봤다. 

 

react (프론트) -> S3에 바로 업로드  -> URL  반환->  백엔드가 처리

 

** 프론트에서 s3로 바로 업로드하면 액세스 키나 id가 front에 작성이 되기 때문에 보안상 위험하다. 

현재 파일업로드를 통해서 바로 s3에 업로드하는 것까지 구현은 했기 때문에 나중에 서버에서 presigned url을 받아와서 

안전하게 이미지 처리를 할 예정이다. 

 

 quill에 작성한 내용 중에서 이미지는 url로 바꿔서 html 형식으로 서버에 최종적으로 보내줄건데

그 과정에서

1. image를 올릴 때마다 서버에 요청해서 url로 바꾸어 주는 imagehandler를 만들건지 (이 방법을 많이 사용하던데)

=> 나중에 삭제한 이미지들도 서버에 저장되어 서버가 무거워질 수 있다. 

 

2. 에디터에 모든 내용을 작성하고 '저장'을 눌렀을 때 img 의 src 내용을 추출해서 이 부분만 서버에 요청해서 url로 바꾸어 준 뒤 서버에 html 형태로 보낼 건지 (그 전까진 img는 base64 형태)

=> 이렇게 하면 파일이 너무 많으면 느려질 수 있는데 그래서 서버에 보내줄 땐 base64를 blob 형태로 바꿔서 보내줘야 한다.

 

고민하다가 2번 방법으로 해보기로 했다. 아무래도,, s3에 많이 업로드하면 부담될 거 같기 때문

 

✨그럼 정리해보자면✨

 

  1. 저장 버튼 클릭 → handleSubmit() → 에디터에서 이미지 추출.
  2. Base64 이미지를 base64ToBlob() 함수로 Blob 객체로 변환.
  3. 변환된 Blob을 S3에 업로드하고, 업로드된 이미지 URL을 반환.
  4. 반환된 URL로 HTML 콘텐츠에서 기존 Base64 src를 URL로 교체.
  5. 수정된 HTML을 서버로 전송하거나 원하는 방식으로 처리.

이렇게 해서 업로드했다가 삭제한 사진은 S3에 저장이 안되도록 해주겠다. 

 

S3와 연결하는 방법은 이전 게시글을 보면 된다. 

2024.11.05 - [React] - [React] S3으로 이미지 처리하기(1)

 

주요 함수 :

 

  • handleSubmit(): 에디터의 내용을 가져와서 모든 이미지를 찾고, handleImageUpload를 호출하여 base64 형식의 이미지 URL을 S3에 업로드된 URL로 교체한다. 마지막으로 업데이트된 HTML 콘텐츠를 로그로 출력해서 확인
  • handleImageUpload(images, editorContent): 에디터에서 가져온 콘텐츠에서 base64 형식의 이미지 URL을 찾아 uploadImage 함수를 통해 S3에 업로드하고, 업로드된 이미지의 URL로 콘텐츠의 이미지 URL을 대체한다.
  • base64ToBlob(base64): base64 형식의 이미지 데이터를 Blob 형식으로 변환하여 이미지 업로드에 적합한 형태로 만든다.
  • uploadImage(blob): Blob 데이터를 S3 버킷에 업로드하여 URL을 생성 후 성공적으로 업로드되면 이미지의 URL을 반환하여 에디터 콘텐츠에서 이미지를 대체하는 데 사용한다.
 const handleSubmit = async () => {
        const editorContent = quillRef.current.getEditor().root.innerHTML;
        const images = editorContent.match(/(<img[^>]*src\s*=\s*[\"']?([^>\"']+)[\"']?[^>]*>)/g) || [];
        const updatedContent = await handleImageUpload(images, editorContent);
        console.log("서버로 보낼 HTML:", updatedContent); //나중에 여기다가 서버로 보내기, 함수는 따로 작성
    };

    const handleImageUpload = async (images, editorContent) => {
        for (let image of images) {
            const base64Src = image.match(/src="([^"]+)"/)[1]; 
            const blob = base64ToBlob(base64Src);
            const imageUrl = await uploadImage(blob);
            editorContent = editorContent.replace(base64Src, imageUrl);
        }
        return editorContent;
    };
    const base64ToBlob = (base64) => {
        const byteCharacters = atob(base64.split(',')[1]);
        const byteArrays = [];
        for (let offset = 0; offset < byteCharacters.length; offset += 512) {
            const slice = byteCharacters.slice(offset, offset + 512);
            const byteNumbers = new Array(slice.length);
            for (let i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
            }
            byteArrays.push(new Uint8Array(byteNumbers));
        }
        return new Blob(byteArrays, { type: 'image/jpeg' }); 
    };

    const uploadImage = async (blob) => {
        try {
            const params = {
                Bucket: process.env.REACT_APP_bucketName,
                Key: `uploads/${Date.now()}`,  
                Body: blob,                   
                ContentType: blob.type       
            };
            const { Location } = await s3.upload(params).promise();
            return Location; 
        } catch (err) {
            console.error('S3 Upload Error:', err);
            throw new Error('이미지 업로드 중 오류가 발생했습니다.');
        }
    };

 

 

이렇게 하면 파일을 업로드했을 때 이미지 src에 url 형태로 바뀐 HTML 이 출력되면 성공이다.