본문 바로가기

JavaScript/React

[React] 웹 에디터 React-Quill 사용하기 (2)

728x90
반응형
관련 포스팅

 

2024.06.16 - [React] - [React] 웹 에디터 React-Quill 사용하기 (1)

 

[React] 웹 에디터 React-Quill 사용하기 (1)

게시글 작성 기능을 구현할 때 그냥 글 작성 기능만 만들면 무언가 심심해 보인다. 폰트 색 변경, 크기 조정, 이미지 추가 등의 기능을 사용할 수 있는 웹 에디터 기능이 있다면 글 작성 페이지의

rlawo32.tistory.com

 

 

이전 글에서 React-Quill에 대한 전반적인 사용법을 알아보고 구현해 보았다. 


이번엔 이미지 적용에 대해 상세하게 구현해보고자 한다.

글을 작성할 때 이미지를 함께 사용하면 <img> html 태그로 생성이 되고 저장이 될 것이다. 그러나 이렇게 작성한 글을 DB에 저장하고 불러오면 이미지는 보이지 않을 것이다. DB에 들어간 이미지 경로는 존재하지 않기 때문이다.

그래서 이미지를 사용한 글 작성을 할 땐 이미지를 이미지 서버에 저장을 하고 경로를 설정해 주어야 나중에 불러올 때 정상적으로 나타날 것이다. 

 

여기에 필요한 Handler와 기능들을 구현해보겠다.

 

 

1. 이미지 Handler 구현하기

우선 React-Quill에 있는 이미지 추가 버튼을 눌렀을 때 동작할 Handler를 생성해 주고 지정해 주자

 

아래와 같이 Handler를 만들어주고 module에서 해당 Handler를 추가하여 지정해 주면 된다.

그러면 해당 Handler가 동작되어 Quill에 추가한 이미지의 정보를 얻어낼 수 있다.

// reactEditor.tsx

...

const changeImageHandler = ():void => {
      const input:HTMLInputElement = document.createElement("input");
      input.setAttribute("type", "file");
      input.setAttribute("accept", "image/jpg,image/png,image/jpeg");
      input.setAttribute("multiple", "multiple");
      input.click();
}

...

const modules:{} = useMemo(() => ({
        toolbar: {
            container: "#toolBar",
            handlers: {
                image: changeImageHandler
            }
        },
}), []);

...

 

 

이 상태로 이미지를 추가하면 이미지를 선택하는 화면은 동작하지만 Quill에는 추가되지 않을 것이다. 

그렇기에 input.onChange를 통해 추가한 이미지를 이미지 서버에 올리고 업로드된 이미지 주소를 가져와 출력해 주면 된다. 우선 이미지 서버를 이용하기 전 FileReader를 사용해서 미리 보기를 구현해 보겠다.

// reactEditor.tsx

...

const changeImageHandler = ():void => {
      const input:HTMLInputElement = document.createElement("input");
      input.setAttribute("type", "file");
      input.setAttribute("accept", "image/jpg,image/png,image/jpeg");
      input.setAttribute("multiple", "multiple");
      input.click();
      
      input.onchange = async ():Promise<void> => { // onChange 추가
        let file:FileList|null = input.files;
        const editor:any = quillRef.current.getEditor();
        const range:any = editor.getSelection();

        if(file !== null) {
          const reader = new FileReader();
          for(let i:number=0; i<file.length; i++) {
            reader.readAsDataURL(file[0]);

            reader.onloadend = () => {
              editor.insertEmbed(range.index, 'image', reader.result);
              editor.setSelection(range.index + 1);
            };
          }
        }
      }
}

...

const modules:{} = useMemo(() => ({
        toolbar: {
            container: "#toolBar",
            handlers: {
                image: changeImageHandler
            }
        },
}), []);

...

 

우선 useRef를 사용하여 Quill의 정보를 가져와서 이곳에 input 사용하여 추가한 이미지를 넣어준다.

 

 

아래 사진처럼 잘 동작하는 걸 확인할 수 있다.

FileReader를 통해 미리 보기 형태로 출력이 되기에 html 태그가 적용되기 전 내용을 확인을 해보면 src에 base64 형태로 입력이 된다.

 

 

아직 확인은 안 해봤지만 base64 형태로 DB에 넣고 사용하면 이미지 출력은 아마 될 것이다.

하지만 어떤 이미지인지 특정할 수도 없고 무엇보다 base64 String 값이 너무 길기에 저장하고 사용하기엔 별로다.

그러니 따로 이미지 서버를 이용해서 구현을 하는 게 나중에 사용하기에도 구별하기에도 좀 더 좋을 것이다.

 

728x90
반응형

 

 

 

 

 

 

 

 

2. 서버로 이미지 전달

onChange에서 추가한 FileList을 사용해서 formData에 file을 넣어 서버로 전달을 해보겠다.

// reactEditor.tsx

...

const changeImageHandler = ():void => {
      const input:HTMLInputElement = document.createElement("input");
      input.setAttribute("type", "file");
      input.setAttribute("accept", "image/jpg,image/png,image/jpeg");
      input.setAttribute("multiple", "multiple");
      input.click();
      
      input.onchange = async ():Promise<void> => { // onChange 추가
        let file:FileList|null = input.files;
        const editor:any = quillRef.current.getEditor();
        const range:any = editor.getSelection();

        if(file !== null) {
          const reader = new FileReader();
          for(let i:number=0; i<file.length; i++) {
            reader.readAsDataURL(file[0]);

            reader.onloadend = () => {
              editor.insertEmbed(range.index, 'image', reader.result);
              editor.setSelection(range.index + 1);
            };

	    // formData 추가
            const formData:FormData = new FormData();
            formData.append('files', file[i]);

            await axios({
                method: "POST",
                url: "/board/boardUploadImage",
                data: formData,
                headers: { 'Content-Type': 'multipart/form-data' }
            }).then((res):void => {

            }).catch((err):void => {
                console.log(err.message);
            })
          }
        }
      }
}

...

const modules:{} = useMemo(() => ({
        toolbar: {
            container: "#toolBar",
            handlers: {
                image: changeImageHandler
            }
        },
}), []);

...

 

 

위와 같이 append를 통해 formData에 file을 추가하고 axios를 작성해 준다.

그리고 컨트롤러는 아래와 같이 작성해 주고 사용해 주면 된다.

@RequiredArgsConstructor
@RestController
@RequestMapping("/board")
public class BoardController {

    @PostMapping("/boardUploadImage")
    public String boardUploadImage(@RequestPart("files") MultipartFile multipartFile) {
        return "Upload Success";
    }
}

 

 

추후엔 S3를 사용해서 이미지 서버를 구현하고 적용하여 이미지를 사용해 보겠다.

 

 

 

728x90
반응형