You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React-Quilljs图片缩放与对齐失效:提交API后样式丢失

问题解决:React-QuillJS图片缩放样式丢失

问题根源

你遇到的问题是因为Quill默认的image blot不会保存图片的width/height等行内样式,当通过dangerouslyPasteHTML重新加载HTML内容时,Quill会将图片解析为默认的image embed,导致之前调整的样式丢失。此外blotFormatter调整的样式直接作用在DOM元素上,但默认情况下Quill不会将这些样式同步到可保存的内容结构中,或保存后重新解析时无法识别。

解决方案

1. 自定义支持样式的Image Blot

创建自定义Image Blot,让它能够存储并渲染图片的width、height样式:

import Quill from 'quill';

const Image = Quill.import('formats/image');

class CustomImage extends Image {
  static blotName = 'image';
  static tagName = 'IMG';

  static create(value: any) {
    const node = super.create(value) as HTMLImageElement;
    if (value.src) node.src = value.src;
    // 应用保存的样式
    if (value.width) node.style.width = value.width;
    if (value.height) node.style.height = value.height;
    return node;
  }

  static value(node: HTMLImageElement) {
    return {
      src: node.src,
      width: node.style.width,
      height: node.style.height,
    };
  }
}

// 注册自定义Blot
Quill.register(CustomImage, true);

2. 调整编辑器初始化逻辑

修改组件代码,确保自定义Blot提前注册,同时优化内容保存与加载逻辑:

import { useEffect } from 'react';
import { useQuill } from 'react-quilljs';
import BlotFormatter from 'quill-blot-formatter';
import Quill from 'quill';

// 提前注册自定义Image Blot和blotFormatter模块
const Image = Quill.import('formats/image');

class CustomImage extends Image {
  static blotName = 'image';
  static tagName = 'IMG';

  static create(value: any) {
    const node = super.create(value) as HTMLImageElement;
    if (value.src) node.src = value.src;
    if (value.width) node.style.width = value.width;
    if (value.height) node.style.height = value.height;
    return node;
  }

  static value(node: HTMLImageElement) {
    return {
      src: node.src,
      width: node.style.width,
      height: node.style.height,
    };
  }
}

Quill.register(CustomImage, true);
Quill.register('modules/blotFormatter', BlotFormatter);

export default function TextEditor({
  value,
  onValueText,
}) {
  const { quill, quillRef } = useQuill({
    modules: { 
      blotFormatter: {},
      toolbar: [['image']] // 显式配置toolbar确保图片按钮可用
    },
  });

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

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

      apiClient
        .uploadImage({ formData })
        .then((res: any) => {
          const range = quill?.getSelection();
          if (range && quill) {
            // 插入带初始样式的图片
            quill.insertEmbed(
              range.index,
              'image',
              {
                src: `${process.env.NEXT_PUBLIC_CLOUD_FRONT_URL!}/${res.data}`,
                width: '100%' // 可选默认样式
              }
            );
          }
        })
        .catch((err) => {
          console.log(err);
        });
    };
  };

  useEffect(() => {
    if (quill) {
      // 根据传入value的类型选择加载方式
      if (typeof value === 'string') {
        quill.clipboard.dangerouslyPasteHTML(value || '');
      } else {
        quill.setContents(value);
      }
      
      quill.getModule('toolbar').addHandler('image', selectLocalImage);
      
      quill.on('text-change', () => {
        // 优先保存Delta格式(更可靠),若必须保存HTML也会包含样式
        // onValueText(quill.getContents());
        onValueText(quill.root.innerHTML);
      });
    }
  }, [quill, value]);

  return (
    <div>
      <div ref={quillRef} />
    </div>
  );
}

3. 关键注意事项

  • 保存格式推荐:优先使用Quill的Delta格式(quill.getContents())保存内容,Delta能完整保留所有格式信息;若后端必须接收HTML,自定义Blot会确保样式写入DOM的style属性,重新加载时可被正确解析。
  • 模块注册时机:确保自定义Blot和blotFormatter在Quill初始化前完成注册,避免模块未生效。
  • 样式同步:blotFormatter调整图片样式后,自定义Blot的value方法会自动将样式同步到可保存的内容结构中。

内容的提问来源于stack exchange,提问作者Sadaf Zillani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 01:47:03