0

0

Quill.js 富文本编辑器:通过自定义模块实现页面内目录导航 (TOC)

碧海醫心

碧海醫心

发布时间:2025-10-14 12:07:01

|

828人浏览过

|

来源于php中文网

原创

quill.js 富文本编辑器:通过自定义模块实现页面内目录导航 (toc)

本文详细介绍了如何在 Quill.js 富文本编辑器中,通过自定义其链接和标题模块,以实现自动生成页面内目录 (TOC) 的基础能力。核心在于修改链接默认行为以支持页面内锚点跳转,并为标题标签自动生成唯一 ID,从而为后续的目录生成奠定基础。

引言:Quill.js 与目录生成的需求

Quill.js 是一款功能强大的富文本编辑器,提供了丰富的编辑功能。然而,在某些场景下,例如长篇文章或技术文档,我们常常需要一个页面内目录 (Table of Contents, TOC) 来帮助用户快速导航。Quill.js 默认情况下并不直接支持自动生成 TOC,主要存在两个挑战:

  1. 链接默认行为:Quill.js 的默认链接模块通常会将所有链接设置为 target="_blank",这意味着点击链接会在新标签页中打开。这对于外部链接是理想的,但对于页面内的锚点链接,我们期望它们能在当前页面内平滑跳转。
  2. 标题标签缺乏唯一 ID:为了创建锚点链接,文档中的标题(如 <h1>, <h2> 等)需要拥有唯一的 id 属性。Quill.js 默认生成的标题标签不包含这些 id 属性。

为了克服这些限制,我们需要对 Quill.js 的核心模块进行定制。

定制 Quill.js 链接模块

为了让页面内锚点链接能够在当前页面跳转,我们需要修改 Quill.js 链接模块的 target 属性行为。具体来说,当链接值以 # 开头时(表示一个页面内锚点),我们应该移除 target 属性或将其设置为 _self;对于其他外部链接,则保留 target="_blank"。

以下是定制链接模块的代码示例:

var Link = Quill.import('formats/link');

class MyLink extends Link {
    /**
     * 创建链接节点时的处理逻辑。
     * @param {string} value - 链接的值 (href)。
     * @returns {HTMLElement} - 创建的链接 DOM 节点。
     */
    static create(value) {
        let node = Link.create(value);
        value = Link.sanitize(value); // 清理链接值
        node.setAttribute('href', value);

        // 如果链接以 '#' 开头,则移除 target 属性,实现页面内跳转
        if (value.startsWith("#")) {
            node.removeAttribute('target');
        } else {
            // 否则,保持 target="_blank"
            node.setAttribute("target", "_blank");
        }
        return node;
    }

    /**
     * 格式化链接时的处理逻辑。
     * @param {string} name - 格式名称。
     * @param {string} value - 格式值。
     */
    format(name, value) {
        super.format(name, value);

        if (name !== this.statics.blotName || !value) {
            return;
        }

        // 再次检查并设置 target 属性,确保格式化后的行为正确
        if (value.startsWith("#")) {
            this.domNode.removeAttribute("target");
        } else {
            this.domNode.setAttribute("target", "_blank");
        }
    }
}

// 注册自定义的链接模块
Quill.register(MyLink, true); // 第二个参数为 true 表示覆盖默认模块

代码解析:

Cliclic AI
Cliclic AI

Cliclic商品背景图编辑器是一款功能强大的AI工具,帮助用户快速生成具有吸引力的商品图背景。

下载
  • 我们通过 Quill.import('formats/link') 获取 Quill 默认的链接模块。
  • MyLink 类继承自 Link。
  • static create(value) 方法在创建新的链接 DOM 节点时被调用。我们在此处判断 value 是否以 # 开头来决定 target 属性。
  • format(name, value) 方法在链接被格式化(例如,用户编辑链接文本或 URL)时被调用。同样,我们在此处确保 target 属性的正确性。
  • Quill.register(MyLink, true) 将我们自定义的 MyLink 注册为 Quill 的链接模块,并覆盖了默认的实现。

定制 Quill.js 标题模块

为了让标题能够被锚点链接引用,每个标题标签都需要一个唯一的 id 属性。Quill.js 默认的标题模块并不会自动添加 id。我们需要扩展标题模块,在标题创建时为其分配一个唯一的 ID。

以下是定制标题模块的代码示例:

var Header = Quill.import('formats/header');
var ids = []; // 用于存储已生成的ID,确保唯一性

/**
 * 生成一个简单的随机ID。
 * 实际应用中可能需要更健壮的ID生成策略。
 * @returns {string} - 唯一的ID字符串。
 */
function getRandomId() {
    let _id = Math.random().toString(16).slice(2, 9);
    // 简单检查冲突,实际应用中可能需要更复杂的循环或UUID库
    while(ids.includes(_id)) {
        _id = Math.random().toString(16).slice(2, 9);
    }
    ids.push(_id);
    return _id;
}

class MyHeader extends Header {
    /**
     * 构造函数在标题 DOM 节点被创建并附加到文档时调用。
     * @param {HTMLElement} domNode - 标题的 DOM 节点。
     */
    constructor(domNode) {
        super(domNode);
        // 为标题节点设置唯一的ID
        domNode.setAttribute('id', getRandomId());
        this.cache = {}; // Quill 内部使用,保持一致
    }

    /**
     * 静态方法 create() 用于创建新的 Blot 实例。
     * 这里我们保持与父类一致,因为ID的设置主要在 constructor 中处理。
     * @returns {HTMLElement} - 创建的标题 DOM 节点。
     */
    static create() {
        const node = super.create();
        return node;
    }

    /**
     * 静态方法 formats() 用于获取 Blot 的格式属性。
     * 我们添加 id 属性以便于后续获取。
     * @param {HTMLElement} domNode - 标题的 DOM 节点。
     * @returns {object} - 包含 id 属性的对象。
     */
    static formats(domNode) {
        let formats = super.formats(domNode);
        formats.id = domNode.getAttribute("id");
        return formats;
    }
}

// 注册自定义的标题模块
Quill.register("formats/header", MyHeader, true);
// 确保 blotName 正确,Quill 内部使用
MyHeader.blotName = "header";

代码解析:

  • getRandomId() 函数用于生成一个随机字符串作为 ID。在实际生产环境中,建议使用更健壮的 UUID 或基于内容的哈希值来生成 ID,以避免潜在的冲突和提高可读性。
  • MyHeader 类继承自 Header。
  • constructor(domNode) 是关键。当 Quill 创建一个标题 DOM 节点时,会调用此构造函数。我们在这里调用 domNode.setAttribute('id', getRandomId()) 来为该标题节点设置一个唯一的 ID。
  • static formats(domNode) 方法被重写,以便在获取标题的格式信息时,也能包含其 id 属性。这在后续遍历 Quill 内容时获取标题 ID 会很有用。
  • Quill.register("formats/header", MyHeader, true) 将我们自定义的 MyHeader 注册为 Quill 的标题模块,并覆盖了默认实现。

实现目录生成逻辑

通过上述定制,我们的 Quill.js 编辑器现在具备了以下能力:

  1. 内部链接支持:当用户在编辑器中创建以 # 开头的链接时,它们将作为页面内锚点链接,并在当前页面跳转。
  2. 标题唯一 ID:所有通过 Quill.js 创建的标题(h1 到 h6)都将自动拥有一个唯一的 id 属性。

有了这些基础,接下来就是编写实际的 JavaScript 代码来生成目录。这通常涉及以下步骤:

  1. 获取编辑器内容:可以通过 quill.getContents() 获取 Quill 内部的 Delta 格式内容,或者直接访问编辑器 DOM (quill.root)。

  2. 遍历并提取标题

    • 如果使用 Delta 格式,需要遍历 Delta 操作,识别出 header 格式的文本,并从对应的 DOM 节点中提取其 id 和文本内容。
    • 如果直接访问 DOM,可以使用 document.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]') 来获取所有带有 id 属性的标题元素。
  3. 构建目录结构:根据提取到的标题文本和 ID,动态生成一个 HTML 列表(通常是 <ul> 和 <li> 嵌套 <a> 标签)作为目录。例如:

    <nav class="toc">
        <ul>
            <li><a href="#header-id-1">一级标题内容</a></li>
            <li>
                <ul>
                    <li><a href="#header-id-2">二级标题内容</a></li>
                </ul>
            </li>
        </ul>
    </nav>
  4. 插入目录:将生成的目录 HTML 结构插入到页面的指定位置,例如文章内容的顶部。

示例(概念性代码,不直接包含在 Quill 定制中):

// 假设 quill 实例已经初始化
function generateTableOfContents(quill) {
    const editorContent = quill.root; // 获取编辑器内容的 DOM 元素
    const headers = editorContent.querySelectorAll('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]');

    if (headers.length === 0) {
        return ''; // 没有标题,不生成目录
    }

    let tocHtml = '<ul>';
    let currentLevel = 0; // 跟踪当前标题级别

    headers.forEach(header => {
        const level = parseInt(header.tagName.substring(1)); // 获取标题级别 (h1 -> 1, h2 -> 2)
        const id = header.getAttribute('id');
        const text = header.textContent;

        if (level > currentLevel) {
            // 新的子级别,开始新的无序列表
            for (let i = 0; i < (level - currentLevel); i++) {
                tocHtml += '<ul>';
            }
        } else if (level < currentLevel) {
            // 返回上级,关闭多余的无序列表
            for (let i = 0; i < (currentLevel - level); i++) {
                tocHtml += '</ul>';
            }
        }

        tocHtml += `<li><a href="#${id}">${text}</a></li>`;
        currentLevel = level;
    });

    // 关闭所有未闭合的无序列表
    for (let i = 0; i < currentLevel; i++) {
        tocHtml += '</ul>';
    }

    return tocHtml;
}

// 在页面加载或编辑器内容更新后调用
// const tocContainer = document.getElementById('toc-container');
// if (tocContainer) {
//     tocContainer.innerHTML = generateTableOfContents(quill);
// }

注意事项与总结

  • ID 生成策略:示例中使用了 Math.random() 生成 ID,这在小型应用中可能够用,但在大型或高并发环境中,可能需要更鲁棒的 UUID (Universally Unique Identifier) 库来保证 ID 的全球唯一性。
  • 性能优化:如果文章内容非常庞大,频繁地遍历 DOM 或 Delta 内容来生成 TOC 可能会影响性能。可以考虑在内容更新时进行防抖 (debounce) 或节流 (throttle) 处理,或者仅在需要时(例如用户点击“显示目录”按钮)才生成。
  • 样式与交互:生成的目录通常需要 CSS 样式来美化,并且可以添加 JavaScript 交互,例如点击目录项时平滑滚动到对应位置,或高亮当前视口中的标题。
  • Quill 版本兼容性:Quill.js 的 API 在不同版本之间可能存在细微差异。请确保你的代码与所使用的 Quill.js 版本兼容。
  • 更复杂的目录结构:上述示例生成的是一个简单的嵌套列表。如果需要更复杂的目录结构(例如,在目录中显示标题编号),则需要在 generateTableOfContents 函数中加入额外的逻辑。

通过对 Quill.js 的链接和标题模块进行定制,我们成功解决了在编辑器中实现页面内目录导航的关键障碍。这展示了 Quill.js 强大的可扩展性,允许开发者根据特定需求深度定制其行为。在此基础上,结合额外的 JavaScript 逻辑来解析编辑器内容并动态渲染目录,即可实现一个完整的 Quill.js 自动目录生成功能。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

887

2023.07.31

python中的format是什么意思
python中的format是什么意思

python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

463

2024.06.27

js 字符串转数组
js 字符串转数组

js字符串转数组的方法:1、使用“split()”方法;2、使用“Array.from()”方法;3、使用for循环遍历;4、使用“Array.split()”方法。本专题为大家提供js字符串转数组的相关的文章、下载、课程内容,供大家免费下载体验。

760

2023.08.03

js截取字符串的方法
js截取字符串的方法

js截取字符串的方法有substring()方法、substr()方法、slice()方法、split()方法和slice()方法。本专题为大家提供字符串相关的文章、下载、课程内容,供大家免费下载体验。

221

2023.09.04

java基础知识汇总
java基础知识汇总

java基础知识有Java的历史和特点、Java的开发环境、Java的基本数据类型、变量和常量、运算符和表达式、控制语句、数组和字符串等等知识点。想要知道更多关于java基础知识的朋友,请阅读本专题下面的的有关文章,欢迎大家来php中文网学习。

1567

2023.10.24

字符串介绍
字符串介绍

字符串是一种数据类型,它可以是任何文本,包括字母、数字、符号等。字符串可以由不同的字符组成,例如空格、标点符号、数字等。在编程中,字符串通常用引号括起来,如单引号、双引号或反引号。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

651

2023.11.24

java读取文件转成字符串的方法
java读取文件转成字符串的方法

Java8引入了新的文件I/O API,使用java.nio.file.Files类读取文件内容更加方便。对于较旧版本的Java,可以使用java.io.FileReader和java.io.BufferedReader来读取文件。在这些方法中,你需要将文件路径替换为你的实际文件路径,并且可能需要处理可能的IOException异常。想了解更多java的相关内容,可以阅读本专题下面的文章。

1228

2024.03.22

php中定义字符串的方式
php中定义字符串的方式

php中定义字符串的方式:单引号;双引号;heredoc语法等等。想了解更多字符串的相关内容,可以阅读本专题下面的文章。

1204

2024.04.29

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Sass 教程
Sass 教程

共14课时 | 0.9万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 3.6万人学习

CSS教程
CSS教程

共754课时 | 42.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号