Sitemap

Quill Editor Image Align Error Fix

2 min readJun 9, 2025

--

2025.06.09

import QuillEditorWrapper from '@/components/parts/QuillEditorWrapper';
import { getToken } from '@/utils/auth';
import { API_BASE_URL } from '@/utils/config';
import { message } from 'antd';
import axios from 'axios';
import Quill from 'quill';
import ImageResize from 'quill-image-resize-module-react';
import { memo, useCallback, useRef } from 'react';
import ReactQuill from 'react-quill';
import 'react-quill/dist/quill.snow.css';

// 이미지 리사이즈 모듈 등록
Quill.register('modules/imageResize', ImageResize);

// 기본 이미지 포맷 가져오기
const BaseImageFormat = Quill.import('formats/image');

// 이미지 포맷에서 사용할 속성 목록
const ImageFormatAttributesList = ['alt', 'height', 'width', 'style'];

// 이미지 포맷 확장
class ImageFormat extends BaseImageFormat {
static formats(domNode: HTMLElement) {
return ImageFormatAttributesList.reduce(function (formats: Record<string, string>, attribute: string) {
if (domNode.hasAttribute(attribute)) {
formats[attribute] = domNode.getAttribute(attribute) || '';
}
return formats;
}, {});
}

format(name: string, value: string | null) {
if (ImageFormatAttributesList.indexOf(name) > -1) {
if (value) {
this.domNode.setAttribute(name, value);
} else {
this.domNode.removeAttribute(name);
}
} else {
super.format(name, value);
}
}
}

// 확장된 이미지 포맷 등록
Quill.register(ImageFormat, true);

interface QuillEditorComponentProps {
value?: string;
onChange: (text: string) => void;
}

const QuillEditorComponent = ({ value, onChange }: QuillEditorComponentProps) => {
const quillInstance = useRef<ReactQuill>(null);

const handleChange = (text: string) => {
onChange(text);
};

const imageHandler = useCallback(() => {
const input = document.createElement('input');
input.setAttribute('type', 'file');
input.setAttribute('accept', 'image/*');
input.click();

input.onchange = async () => {
if (input.files) {
const file = input.files[0];
try {
const formData = new FormData();
formData.append('image', file);

const response = await axios.post(`${API_BASE_URL}/api/editor/upload/image`, formData, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
const { url } = response.data;

const editor = quillInstance.current?.getEditor();
const range = editor?.getSelection();
if (editor && range) {
editor.insertEmbed(range.index, 'image', `${API_BASE_URL}${url}`);
// 이미지 삽입 후 커서를 다음 위치로 이동
editor.setSelection(range.index + 1, 0);
}
} catch (error) {
console.error('Image upload failed:', error);
message.error('이미지 업로드 실패');
}
}
};
}, []);

return (
<QuillEditorWrapper
forwardedRef={quillInstance}
value={value}
onChange={handleChange}
theme="snow"
placeholder="내용을 입력해주세요."
formats={[
'header',
'bold',
'italic',
'underline',
'strike',
'link',
'image',
'align',
'color',
'background',
'list',
'bullet',
'check',
'width',
'height',
'style',
'alt',
]}
modules={{
toolbar: {
container: [
{ header: [1, 2, 3, 4, 5, 6, false] },
'bold',
'italic',
'underline',
'strike',
'link',
'image',
{ align: ['', 'center', 'right', 'justify'] },
{ color: [] },
{ background: [] },
{ list: 'ordered' },
{ list: 'bullet' },
{ list: 'check' },
'clean',
],
handlers: {
image: imageHandler,
},
},
imageResize: {
parchment: Quill.import('parchment'),
modules: ['Resize', 'DisplaySize', 'Toolbar'],
},
}}
/>
);
};

export default memo(QuillEditorComponent);

--

--