通过Webpack+TS实现一个简易的富文本编辑器

1,390 阅读4分钟

概述

这篇文章主要是来记录一下通过Webpack+TS实现的一个富文本编辑器。对源码感兴趣可以到github仓库看看

主要功能如下:

  1. 实现一个编辑区域
  2. 实现"加粗", "设置标题", "设置颜色"三个简易功能

Webpack配置

既然决定要使用TS,那么就可以通过webpack搭配ts-loader或者awesome-typescript-loader来对ts语言进行编译。相应的webpack配置文件也非常好写,这里就简单贴张截图。

Tip

  • 除此之外记录一个使用Webpack-dev-server遇到的小困难 这是因为在新版本中,要使用webpack serve xxx来代替webpack xxx

开发流程

接下来,我将自上而下来描述我是如何将这个富文本编辑器实现出来的。

设计思路

我认为首先应该从使用的角度入手,也就是说假设所有的功能都以及实现好了,通过这种假象的方式来决定我们的整个项目结构。 我这里的话是参考了wangeditor的使用方式:

const Editor = window.Editor;
const editor = new E('#container');
editor.create()

于是呢,我便依样画葫芦,我的简易编辑器使用方式如下:

const editorDom = document.getElementById("root");
const editor = new EasyEditor(editorDom);
editor.create();
window.editor = editor;

与wangeditor不同点就在于传入构造函数的参数不同,我要求传入的是一个dom节点,而wangeditor要求传入的是一个类似于jquery获取元素的参数值。

实现富文本编辑器关键问题

  1. 实现一个编辑区域

这个非常简单,只要留意过H5新Api的小伙伴应该都会注意到contenteditable属性. 只要将该属性设置为true。那么该DOM元素的区域就是一个可编辑区域。

  1. 实现"加粗", "设置标题", "设置颜色"三个简易功能

可以说,这三个功能其实都是对某部分字体加上样式,通过一番资料查阅,我发现浏览器提供给我们一个api: document.execCommand, 该api允许我们运行命令来操纵可编辑内容区域的元素。

下面做一个小演示:

首先我们在编辑区域选中一段字体:

然后我们在控制台执行document.execCommand("bold", false, null), 将选中部分字体加粗

函数执行返回true, 即浏览器支持该操作; 若返回false, 那就是不支持该操作。

除了加粗外,还有其它很多命令,如设置font-size,设置color等。在MDN上都有说明。

至此,我们逐个击破了两大主要问题,接下来就是通过代码来进行实现啦。

代码实现

虽然功能简单, 但代码还是不少的, 因此下面只贴出重要的代码。

初始化菜单栏以及编辑区

菜单项栏是通过一个menuItems数组来动态渲染的,可以由用户提供,若用户没有提供,则使用系统自带的。

[
        {
            text: "H",
            children: [
                { text: '一级标题', command: 'fontSize-6' },
                { text: '二级标题', command: 'fontSize-5' },
                { text: '三级标题', command: 'fontSize-4' },
                { text: '四级标题', command: 'fontSize-3' },
                { text: '五级标题', command: 'fontSize-2' },
                { text: '六级标题', command: 'fontSize-1' }
            ]
        },
        {
            text: "B",
            command: "bold"
        },
        {
            text: "C",
            children: [
                {text: "red", command: "foreColor-red"},
                {text: "blue", command: "foreColor-blue"},
                {text: "green", command: "foreColor-green"},
            ]
        }
]

文本编辑区中,默认换行使用的是div,参照wangeditor的,使用document.execCommand("defaultParagraphSeparator", false, "p")可以将默认换行标签改成p标签

在渲染菜单项的时候,是允许有二级菜单的。同时,command会通过setAttribute绑定到生成的dom上。以便后续对编辑区域进行操作。

最后还有一个问题,那就是当选中了编辑区中部分内容后,点击菜单栏按钮会失去选中部分。这样document.execCommand就失效了。

我的解决办法是: 每次鼠标抬起时都将当前选中的部分缓存起来,以便后续虽然失去选中部分,但仍然可以找回选中部分。经过一番资料查阅,我找到了window.getSelection这个函数可以返回用户选择文本的一些信息,以下是MDN官方文档提供的示例:

function foo() {
    let selection = window.getSelection();
    let cacheRange = selObj.getRangeAt(0);
    // 其他代码
}

拿到cacheRange那这就好办了,这要把它存起来,等到点击菜单栏的时候,再把它放回selection中,然后使用document.execCommand执行命令即可。

function initEvent(dom: HTMLElement,editor: EasyEditor) {
	// 绑定鼠标抬起缓存range事件
    dom.addEventListener("mouseup", function(e) {
        editor.cacheRange = window.getSelection().getRangeAt(0);
    })
}
// 通过事件委托, 把每个菜单项的点击事件委托到菜单上
menuDom.addEventListener('click', function(e) {
    const targetDom = e.target as HTMLElement;
    const command = targetDom.getAttribute('command');
    if(!command) return ;
    if(editor.cacheRange) {
        const selection = window.getSelection();
        selection.removeAllRanges();
        selection.addRange(editor.cacheRange);
        handleCommand(command); // 解析并执行绑定在dom上的命令
    }
})

效果截图

总结

整个实现的过程为两天,有一天半的时间花在了调研查资料,工程规划上。写代码时间占比还是比较少的。

从0到1实现了一个富文本编辑器,可以说是很少有这种经历,以往都是在算法上掰扯,精心实现某一个模块,对写工程的经验比较少,还是得多多练习。