一步一步解析wangEditor4源码

1,808 阅读5分钟

此文章将带领你怎么去学习阅读一个优秀的开源项目wangEditor

wangEditor版本4

Typescript 开发的 Web 富文本编辑器, 轻量、简洁、易用、开源免费github传送门

1. 找到入口文件

src/wangEditor.ts

解析:可以看出来入口文件很精简,可以明确的了解到wangEidtor所需要的一些文件

  • 1.样式文件less
  • 2.wangeditor的类文件import Editor from './editor/index'
  • 3.polyfill一些功能兼容的文件import './utils/polyfill'
  • 4.初始化的一些可以扩展及配置的菜单基础类export * from './menus/menu-constructors/index'

注意:不解析less,less用法可以去参考less官网文档

2. 打开

src/utils/polyfill

解析:就针对一下两个功能点做了兼容,注释也写的很清楚:

    1. 针对matches方法的兼容实现
    1. 针对IE11的Promise兼容

3. 重头戏

src/editor/index.ts

这个文件才是编辑器的核心入口

首先是构造函数

这个构造函数有两个参数

  • toolbarSelector -- 必须 -- 编辑器工具栏的选择器
  • textSelector -- 可选 -- 编辑器内容的选择器

解析:此函数针对编辑器需要的功能及属性配置进行初始化,功能见图

其他的都是暴露给用户使用的api
API备注
create()创建编辑器实例
beforeDestroy()销毁前钩子
destroy()销毁编辑器
fullScreen()进入全屏
unFullScreen()关闭全屏
scrollToHead()跳到指定锚点
registerMenu()注册扩展菜单

4. 选区api封装

src/editor/selection.ts

API备注
getRange()获取当前 range
saveRange()保存选区范围
saveRange()折叠选区范围
getSelectionText()获取选区范围内的文字
getSelectionContainerElem()获取选区范围的 DOM 元素
getSelectionStartElem()选区范围开始的 DOM 元素
getSelectionEndElem()选区范围结束的 DOM 元素
isSelectionEmpty()选区是否为空(没有选择文字)
restoreSelection()恢复选区范围
createEmptyRange()创建一个空白(即 &#8203 字符)选区
createRangeByElems()重新设置选区
createRangeByElem()根据 DOM 元素设置选区
getSelectionRangeTopNodes()获取 当前 选取范围的 顶级(段落) 元素
moveCursor()移动光标位置,默认情况下在尾部
getCursorPos()获取光标在当前选区的位置
clearWindowSelectionRange()清除当前选区的Range,notice:不影响已保存的Range
recordSelectionNodes()记录节点 - 从选区开始节点开始 一直到匹配到选区结束节点为止

总结: 这个文件主要封装了一些selection的选区操作和光标操作

5. execCommand命令封装

src/editor/command.ts

API备注
do()执行富文本操作的命令
insertHTML()插入 html
insertElem()插入 DOM 元素

总结: 封装了以上三个,还有几个只是原生加了一层封装,就不列不出来了, 就是针对execCommand命令的一些封装

6. 初始化编辑区域

src/text/index.ts

API备注
togglePlaceholder()切换placeholder
clear()清空内容
html()获取内容/设置内容
text()获取文本/设置文本
setJson()设置json
getJson()获取json
append()追加 html 内容

另外还有些api是私有的,就不介绍了, 有兴趣可以自行查阅其功能

总结:暴露了一些编辑区域操作的api给用户使用

还有这里有个功能值得一提,就是事件委托的全局管理,拿了一部分代码:

// 键盘 down 时的 hooks
$textElem.on('keydown', (e: KeyboardEvent) => {
    const keydownEvents = eventHooks.keydownEvents
    keydownEvents.forEach(fn => fn(e))
})

这里将编辑区域的keydown事件都放在了keydownEvents的数组里,然后循环执行,已到达全局管理,非常的优雅。

7. 菜单初始化

src/menus/index.ts

API备注
extend自定义添加菜单
init初始化菜单
menuFind获取指定菜单对象
changeActive修改菜单激活状态

这就是个菜单管理及初始化的类文件

8. 全局zIndex管理

src/editor/z-index/index.ts

这个文件就是针对编辑器所有用到的样式zIndex的统一管理

9. 监听编辑器内容改变的实现

src/editor/change/index.ts

它继承了Mutation, 我们跟随着打开封装Mutation的文件src/utils/observer/mutation.ts

soga, 原来是用了原生的MutationObserver API

MutationObserver API 创建并返回一个新的 MutationObserver 它会在指定的DOM发生变化时被调用

Web API官方使用的example

// 选择需要观察变动的节点
const targetNode = document.getElementById('some-id');

// 观察器的配置(需要观察什么变动)
const config = { attributes: true, childList: true, subtree: true };

// 当观察到变动时执行的回调函数
const callback = function(mutationsList, observer) {
    // Use traditional 'for loops' for IE 11
    for(let mutation of mutationsList) {
        if (mutation.type === 'childList') {
            console.log('A child node has been added or removed.');
        }
        else if (mutation.type === 'attributes') {
            console.log('The ' + mutation.attributeName + ' attribute was modified.');
        }
    }
};

// 创建一个观察器实例并传入回调函数
const observer = new MutationObserver(callback);

// 以上述配置开始观察目标节点
observer.observe(targetNode, config);

// 之后,可停止观察
observer.disconnect();

总体来说就是一个监听dom树结构发生变化的事件API,看到这里就豁然开朗了,用法就按照例子来,不介绍了。

10. 撤销和恢复功能

src/editor/history/index.ts

看看具体实现,构造代码:

一上来就是三个缓存

    1. 内容缓存
    1. 滚动scrollTop缓存
    1. 选区缓存

看到最后发现它引用了Cache基础类,我们来打开它:src/utils/data-structure/cache.ts

设置了几个protected的属性

    1. data: 正常操作(用户输入、js代码修改内容、恢复操作)产生的缓存
    1. revokeData: 撤销操作产生的缓存(恢复操作时需要这些数据)
    1. isRe: 上一步操作是否为 撤销/恢复

构造函数传入了一个最大尺寸来限制缓存最大存储

 constructor(protected maxSize: number) {
        this.data = new CeilStack(maxSize)
        this.revokeData = new CeilStack(maxSize)
 }

这里面的CeilStack是一个存储缓存的数据结构, 以数组来实现堆栈来缓存,到达后进先出的效果

未完待续