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

如何在Next.js的ReactQuill中用内联样式替代Quill类

让react-quill-new生成内联样式适配HTML邮件

在Next.js项目中使用react-quill-new编辑器时,需要生成内联样式(如style="text-align: center;")而非Quill默认的类样式(如ql-align-center),因为HTML邮件客户端无法识别外部类样式。此前尝试设置clipboard.matchVisual: false和指定formats属性均未生效,生成的HTML仍包含类样式。

当前Editor组件代码

'use client'

import dynamic from "next/dynamic";
import "./EditorStyle/bubble.css";
import Quill from "quill";

const ReactQuill = dynamic(() => import("react-quill-new"), { ssr: false });

export const Editor = ({ content, onChange, className }: { content: any, className: string, onChange: (value: string) => void }) => {

    const module = {
        toolbar: {
            container: [
                [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
                ["bold", "italic", "underline"],
                ['link'],
                [{ 'list': 'ordered' }, { 'list': 'bullet' }],
                [{ 'color': [] }, { 'background': [] }],
                ['clean'],
                [{ 'font': [] }],
                [{ 'align': [] }],
            ],
            handlers: {
                link: function(value: string) {
                    const that: any = this;
                    const tooltip = that.quill.theme.tooltip;
                    const input = tooltip.root.querySelector("input[data-link]");
                    input.dataset.link = "google.com";

                    if (value) {
                        const range = that.quill.getSelection();
                        if (range == null || range.length === 0) {
                            return;
                        }
                        let preview = '';
                        if (/^\S+@\S+\.\S+$/.test(preview) && preview.indexOf("mailto:") !== 0) {
                            preview = `mailto:${preview}`;
                        }
                        const { tooltip } = that.quill.theme;
                        tooltip.edit("link", preview);
                    } else {
                        that.quill.format("link", false);
                    }
                }
            }
        },
        clipboard: {
            matchVisual: false
        }
    };

    return (
        <div>
            <div>
                <ReactQuill
                    theme="bubble"
                    value={content}
                    modules={module}
                    placeholder="Write something amazing..."
                    onChange={onChange}
                    className={`${className}`}
                />
            </div>
        </div>
    );
};

需求

  • 对齐样式(居中、左/右对齐)使用内联style属性,而非ql-align-*类
  • 列表、字体、颜色等所有样式均采用内联样式呈现

解决方案

通过自定义Quill格式扩展,替换默认的类样式逻辑为内联样式设置。在组件初始化时注册这些自定义格式,覆盖Quill的默认行为:

1. 注册自定义格式

在组件内部添加格式注册代码(确保在ReactQuill渲染前执行):

// 注册自定义格式,覆盖Quill默认行为
(() => {
  // 对齐格式:替换类为text-align内联样式
  Quill.register({
    'formats/align': class Align extends Quill.import('formats/align') {
      static formats(node: HTMLElement) {
        const textAlign = node.style.textAlign;
        return textAlign ? textAlign : undefined;
      }
      format(name: string, value: string) {
        if (value) {
          this.domNode.style.textAlign = value;
        } else {
          delete this.domNode.style.textAlign;
        }
        // 移除Quill默认添加的对齐类
        this.domNode.classList.remove(...Array.from(this.domNode.classList).filter(cls => cls.startsWith('ql-align-')));
      }
    }
  }, true);

  // 字体格式:替换类为font-family内联样式
  Quill.register({
    'formats/font': class Font extends Quill.import('formats/font') {
      format(name: string, value: string) {
        if (value) {
          this.domNode.style.fontFamily = value;
        } else {
          delete this.domNode.style.fontFamily;
        }
        // 移除Quill默认添加的字体类
        this.domNode.classList.remove(...Array.from(this.domNode.classList).filter(cls => cls.startsWith('ql-font-')));
      }
    }
  }, true);

  // 列表格式:移除默认类并添加内联缩进样式
  Quill.register({
    'formats/list': class List extends Quill.import('formats/list') {
      static formats(node: HTMLElement) {
        return node.tagName === 'OL' ? 'ordered' : node.tagName === 'UL' ? 'bullet' : undefined;
      }
      format(name: string, value: string) {
        super.format(name, value);
        // 移除Quill默认添加的列表类
        this.domNode.classList.remove('ql-list', `ql-${value}`);
        // 添加邮件友好的内联缩进
        if (value === 'ordered' || value === 'bullet') {
          this.domNode.style.marginLeft = '2em';
        } else {
          delete this.domNode.style.marginLeft;
        }
      }
    }
  }, true);
})();

2. 整合后的完整组件

将上述注册代码插入原组件中,最终代码如下:

'use client'

import dynamic from "next/dynamic";
import "./EditorStyle/bubble.css";
import Quill from "quill";

const ReactQuill = dynamic(() => import("react-quill-new"), { ssr: false });

// 注册自定义格式,覆盖Quill默认行为
(() => {
  // 对齐格式
  Quill.register({
    'formats/align': class Align extends Quill.import('formats/align') {
      static formats(node: HTMLElement) {
        const textAlign = node.style.textAlign;
        return textAlign ? textAlign : undefined;
      }
      format(name: string, value: string) {
        if (value) {
          this.domNode.style.textAlign = value;
        } else {
          delete this.domNode.style.textAlign;
        }
        this.domNode.classList.remove(...Array.from(this.domNode.classList).filter(cls => cls.startsWith('ql-align-')));
      }
    }
  }, true);

  // 字体格式
  Quill.register({
    'formats/font': class Font extends Quill.import('formats/font') {
      format(name: string, value: string) {
        if (value) {
          this.domNode.style.fontFamily = value;
        } else {
          delete this.domNode.style.fontFamily;
        }
        this.domNode.classList.remove(...Array.from(this.domNode.classList).filter(cls => cls.startsWith('ql-font-')));
      }
    }
  }, true);

  // 列表格式
  Quill.register({
    'formats/list': class List extends Quill.import('formats/list') {
      static formats(node: HTMLElement) {
        return node.tagName === 'OL' ? 'ordered' : node.tagName === 'UL' ? 'bullet' : undefined;
      }
      format(name: string, value: string) {
        super.format(name, value);
        this.domNode.classList.remove('ql-list', `ql-${value}`);
        if (value === 'ordered' || value === 'bullet') {
          this.domNode.style.marginLeft = '2em';
        } else {
          delete this.domNode.style.marginLeft;
        }
      }
    }
  }, true);
})();

export const Editor = ({ content, onChange, className }: { content: any, className: string, onChange: (value: string) => void }) => {

    const module = {
        toolbar: {
            container: [
                [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
                ["bold", "italic", "underline"],
                ['link'],
                [{ 'list': 'ordered' }, { 'list': 'bullet' }],
                [{ 'color': [] }, { 'background': [] }],
                ['clean'],
                [{ 'font': [] }],
                [{ 'align': [] }],
            ],
            handlers: {
                link: function(value: string) {
                    const that: any = this;
                    const tooltip = that.quill.theme.tooltip;
                    const input = tooltip.root.querySelector("input[data-link]");
                    input.dataset.link = "google.com";

                    if (value) {
                        const range = that.quill.getSelection();
                        if (range == null || range.length === 0) {
                            return;
                        }
                        let preview = '';
                        if (/^\S+@\S+\.\S+$/.test(preview) && preview.indexOf("mailto:") !== 0) {
                            preview = `mailto:${preview}`;
                        }
                        const { tooltip } = that.quill.theme;
                        tooltip.edit("link", preview);
                    } else {
                        that.quill.format("link", false);
                    }
                }
            }
        },
        clipboard: {
            matchVisual: false
        }
    };

    return (
        <div>
            <div>
                <ReactQuill
                    theme="bubble"
                    value={content}
                    modules={module}
                    placeholder="Write something amazing..."
                    onChange={onChange}
                    className={`${className}`}
                />
            </div>
        </div>
    );
};

补充说明

  • 颜色、背景色格式Quill默认已使用内联样式,无需额外修改
  • 标题(header)格式默认使用<h1>-<h6>标签,邮件客户端原生支持
  • 加粗、斜体、下划线默认使用<b>/<strong>、<i>/<em>、<u>标签,符合邮件规范

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 15:15:55