Quill React 에디터 사용해보기 (이미지 업로드 및 사이즈 조절)

2022. 3. 22. 10:47React

728x90

 

Quill Editor란? 

공식문서에 따르면 Quill Editor는 모던 웹을 위한 오픈소스 WYSIWYG 에디터라고 소개하고 있다.

WYSIWYG는 What You See Is What You Get의 약자로, 편집 과정에서의 화면 포맷이 최종 완성본이랑 거의 똑같이 나오게 된다는 의미로 쓰이고 있다. 우리가 흔히 알고 있는 네이버, 다음 블로그, 미디엄, 등 여러 플랫폼에서 사용자가 직접 쓰는 콘텐츠에는 WYSIWYG 에디터가 사용되고 있다. 이미 모든게 갖춰진 에디터를 사용할 수 있으면 좋겠지만 모든 에디터가 오픈소스는 아니기에 새로운 에디터를 개발할게 아니라면 기존의 오픈소스 에디터에서 필요한 기능들을 추가해서 사용해야 한다. 그중에서 필자가 추천하는 에디터는 지금부터 사용해볼 Quill Editor이다.

 


Quill 사용해보기

 

 

 

 

설치

일단 npm 또는 yarn으로 quill을 추가해주자. quill은 현재 해당 포스트 작성일 기준으로 1.3.7버전까지 release 됐지만 현재 2.0 버전을 베타 테스트 중이라 조만간 새로운 버전이 출시될 것 같다. 기회가 된다면 추후에 2.0 버전이 나왔을 때 migration 하는 방법도 같이 살펴보도록 하자. 

 

해당 포스트에서는 react를 사용할것이기 때문에 그냥 quill이 아닌 react-quill을 추가해줬다.

 

npm install react-quill // npm install quill

 

사용방법

이제 ReactQuill 컴포넌트에 사용할 툴바 옵션을 따로 지정해주던지 기본값을 사용하면 된다.

필자의 경우 툴바에 수학수식 삽입 기능 같은 건 따로 필요 없었기에 아래와 같이 딱 필요한 기능들만 추가해서 전달해줬다

 

주요 기능들만 남겨두고 다 제외

 

// 사용하고 싶은 옵션, 나열 되었으면 하는 순서대로 나열
const toolbarOptions = [
  ["link", "image", "video"],
  [{ header: [1, 2, 3, false] }],
  ["bold", "italic", "underline", "strike"],
  ["blockquote"],
  [{ list: "ordered" }, { list: "bullet" }],
  [{ color: [] }, { background: [] }],
  [{ align: [] }],
]; 


// 옵션에 상응하는 포맷, 추가해주지 않으면 text editor에 적용된 스타일을 볼수 없음
export const formats = [
  "header",
  "font",
  "size",
  "bold",
  "italic",
  "underline",
  "strike",
  "align",
  "blockquote",
  "list",
  "bullet",
  "indent",
  "background",
  "color",
  "link",
  "image",
  "video",
  "width",
];

const modules = {
  toolbar: {
    container: toolbarOptions,
  },
};

 

이제 해당 옵션들을 각각 modules랑 formats prop으로 넘겨주면 된다.

 

import React from "react";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";

const Editor = ({ placeholder, value, ...rest }) => {
  return (
  // 테마 (bubble, snow, custom) https://quilljs.com/docs/themes/
    <ReactQuill
      {...rest}
      placeholder={placeholder}
      value={value || ""}
      theme="snow" 
      modules={modules}
      formats={formats}
    ></ReactQuill>
  );
};

 

커스텀 이미지 핸들러

이 후 기본적인 기능들은 다 사용할 수는 있었지만 필자의 경우 이미지 업로드 부분은 손을 좀 봐야 했다. 일단 기존 이미지 핸들러 로직에서 이미지가 base64 형식으로 이미지 태그에 들어갔기 때문에 너무 길어졌고 그 형식 그대로 데이터를 저장하는 건 적합하지 않았다.  그래서 이미지를 올릴 때마다 서버에 저장되고 반환된 url을 이미지 태그에 삽입해주는 로직이 필요했다 (필자는 파이어 스토리지를 사용했다).

 

해당 로직을 실행하기 위해선 아래와 같이 기존의 이미지 핸들러를 덮어 씌우는 작업이 필요했다.

 

const Editor = ({ placeholder, value, ...rest }) => {
  // quill 에디터 컴포넌트 ref
  const quillRef = useRef(null);

// modules를 통해 핸들러를 추가해주는 방법과 toolbar를 선택해서 핸들러를 추가해주는 방법이 있다
// const modules = {
//   toolbar: {
//     container: toolbarOptions,
//     handlers: {
//       image: handleImage
//     }
//   },
// };

  useEffect(() => {
    const handleImage = () => {
    // 이미지 핸들 로직
    }
    
    if (quillRef.current) {
      const { getEditor } = quillRef.current;
      const toolbar = quillRef.current.getEditor().getModule("toolbar");
      toolbar.addHandler("image", handleImage);
    }
  }, []);

  return (
    <ReactQuill
      {...rest}
      ref={quillRef}
      value={value || ""}
      theme="snow"
      modules={{
        ...modules,
        imageResize: {
          parchment: Quill.import("parchment"),
          modules: ["Resize", "DisplaySize", "Toolbar"],
        },
      }}
      formats={formats}
      placeholder={placeholder}
      preserveWhitespace
    ></ReactQuill>
  );
};

export default Editor;

 

이제 툴바에서 이미지 아이콘을 클릭했을때 실행될 handleImage 핸들러 함수를 살펴보자. 여기서 나오는 getSelection, insertEmbed, deleteText 등 모든 메서드는 공식문서 api에 자세히 설명되어있지만 이해하기 쉽도록

주석 처리를 해두었으니 참고하길 바란다.

 

 const handleImage = () => {
        const input = document.createElement("input");
        input.setAttribute("type", "file");
        input.setAttribute("accept", "image/*");
        input.click();
        input.onchange = async () => {
          const file = input.files[0];

          // 현재 커서 위치 저장
          const range = getEditor().getSelection(true);

          // 서버에 올려질때까지 표시할 로딩 placeholder 삽입
          getEditor().insertEmbed(range.index, "image", `/images/loading.gif`);

          try {
            // 필자는 파이어 스토어에 저장하기 때문에 이런식으로 유틸함수를 따로 만들어줬다 
            // 이런식으로 서버에 업로드 한뒤 이미지 태그에 삽입할 url을 반환받도록 구현하면 된다 
            const filePath = `contents/temp/${Date.now()}`;
            const url = await uploadImage(file, filePath); 
            
            // 정상적으로 업로드 됐다면 로딩 placeholder 삭제
            getEditor().deleteText(range.index, 1);
            // 받아온 url을 이미지 태그에 삽입
            getEditor().insertEmbed(range.index, "image", url);
            
            // 사용자 편의를 위해 커서 이미지 오른쪽으로 이동
            getEditor().setSelection(range.index + 1);
          } catch (e) {
            getEditor().deleteText(range.index, 1);
          }
        };
      };

 

이런 식으로 이미지 핸들러 함수를 작성해주면 새로운 이미지를 올릴 때마다 해당 이미지가 서버에 저장되고 훨씬 보기 좋고 관리하기 좋은 url이 이미지 태그에 삽입된다.

 

이미지 조절 모듈 사용하기

한가지 아쉬운 점은 기본적으로 이미지 사이즈 조절이 불가능하다는 점이다. 이 또한 커스텀 리사이징 로직을 추가해도 되지만 에디터를 만드는데 많은 시간을 투자할 수 없다면 이미 만들어진 모듈을 사용하는 방법도 가능하다.

 

 

quill-image-resize-module-react

A module for Quill rich text editor to allow images to be resized (with react fixes).. Latest version: 3.0.0, last published: 4 years ago. Start using quill-image-resize-module-react in your project by running `npm i quill-image-resize-module-react`. There

www.npmjs.com

 

 리액트용이 아닌 quill-image-resize-module 따로 웹팩을 만져줘야하기 때문에 필자는 별도의 세팅이 필요 없는 quill-image-resize-module-react를 사용해줬다.

 

npm i quill-image-resize-module-react

 

이제 모듈을 register 해준뒤 modules에 imageResize를 추가하고 설정값을 전달해주자

 

import ReactQuill, { Quill } from "react-quill";
import ImageResize from "quill-image-resize-module-react";

Quill.register("modules/imageResize", ImageResize);

const modules = {
  toolbar: {
    container: toolbarOptions,
  },
  imageResize: {
    // https://www.npmjs.com/package/quill-image-resize-module-react 참고
    parchment: Quill.import("parchment"),
    modules: ["Resize", "DisplaySize", "Toolbar"],
  },
};

 

이제 기본적인 기능을 가진 에디터가 구축이 되었고 아래와 같이 이미지 업로드 및 조절이 가능할 것이다

 

 

 

한 가지 주의할 점은 이미지 조절은 이미지의 용량을 작게 만드는 작업이 아닌 이미지 태그의 width, height를 조절시키는 것이기 때문에 만약에 따로 compression작업이 필요한 것이라면 별도의 모듈을 설치해서 사용해야 한다.

 

마무리

 

다른 오픈소스 에디터를 많이 사용해보진 않았지만, 예전에 사용해보았던 ckeditor보다 기능은 현저히 적은 대신 가볍고 모든 기능이 무료로 제공되어서 소규모 프로젝트에 적용하기엔 더 괜찮아 보였다. 또 ckeditor에서 자주 경험했던 한글 깨짐 이슈도 전혀 없었다 (좀 오래전에 썼던 거라 아직도 ckeditor에 한글 깨짐 이슈가 있는지는 모르겠다).