execCommand实现富文本编辑器

前言

近期研究了富文本编辑器相关内容,在网上找了很多相关的资料进行学习。发现富文本编辑器相关的资料比较零碎,无法形成系统性知识,这里通过系统性的专题记录下了解到的关于富文本相关知识。

富文本编辑器发展历程

开源的富文本编辑器技术通常长期的发展,可以分为三个阶段:

  1. L0

编辑器技术的起始阶段,依赖于浏览器原生编辑能力。通过浏览器execCommand方法,实现对编辑内容的加粗、斜体、回车...。execCommand是浏览器的原生api,允许通过命令操纵可编辑区域(contenteditable)的元素。还不了解execCommand的同学可以去MND document.execCommand 看看。

  1. L1

基于浏览器contentditable富文本的编辑能力,自定义实现编辑区域操作命令。在浏览器基础能力和排版能力上,满足大部分编辑器自定义需求。L1能力的编辑器代表如下:

  • Quill.js 基于浏览器 contenteditable 编辑能力实现的富文本编辑器,石墨文档就是基于Quill实现的
  • ProseMirror也是依赖于浏览器contenteditable能力,引入了Schema、插件系统、Virtual DOM等等的富文本编辑器,国外的产品Confluence 就是基于ProseMirror,由此可见ProseMirror的稳定性和扩展性强大。近期研究富文本编辑器也是主要看ProseMirror相关内容,后面其他文章会基于ProseMirror原理来分享编辑器相关知识。
  • Draft.js,Facebook团队推出的把富文本编辑器和React结合的富文本作品。在编辑器中引入了状态管理、Immutable等等React的概念。
  • Slate.js相对于其他编辑器框架,最晚推出。站在巨人的肩膀上,借鉴吸收了ProseMirror、Quill、Draft的优点。Slate.js只提供了编辑器的基础能力,定制化极强。很多富文本编辑器团队都会给予Slate做二次开发,定制自己团队的编辑器。钉钉文档就是基于Slate.js开发的。
  1. L2

不依赖浏览器的编辑能力,自己实现选区、光标、排版。目前只有Google Doc实现了L2能力的富文本编辑器,开发难度以及成本很大。

基于execCommand实现简单的L0富文本编辑器

通过执行document.execCommand,操纵可编辑器区域内容的元素。 document.execCommand还支持很多文本编辑能力,感兴趣的同学可以自己查阅文档进行添加。 代码如下:

js 复制代码
// Editor.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }
    #menu-bar {
      margin: 100px 60px 10px;
      display: flex;
    }
    .btn {
      padding: 4px;
      margin-right: 8px;
      border-radius: 2px;
      border: 1px solid #ccc;
      background: #82c8a0;
      color: #fff;
    }
    #editor {
      height: 600px;
      padding: 10px 4px;
      margin: 0px 60px;
      outline: none;
      border: 1px solid #ccc;
    }
  </style>
</head>
<body>
  <div id="menu-bar">
    <button id="bold" class="btn">加粗</button>
    <button id="italic" class="btn">斜体</button>
    <button id="underline" class="btn">下划线</button>
    <button id="justifyCenter" class="btn">居中</button>
    <button id="justifyLeft" class="btn">左对齐</button>
    <button id="justifyRight" class="btn">右对齐</button>
    <button id="insertOrderedList" class="btn">有序列表</button>
    <button id="insertUnorderedList" class="btn">无序列表</button>
    <button id="h1" class="btn">H1</button>
    <button id="h2" class="btn">H2</button>
    <button id="" class="btn">...</button>
  </div>
  <div id="editor" contenteditable="true"></div>
</body>
<script src="./editor.js"></script>
</html>
js 复制代码
// Editor.js
function bold() {
  //加粗
  return document.execCommand('bold', false, null);
}

function italic() {
  // 斜体
  return document.execCommand('italic', false, null);
}

function underline() {
  // 下划线
  return document.execCommand('underline', false, null);
}

function justifyCenter() {
  // 居中
  return document.execCommand('justifyCenter', false, null);
}

function justifyLeft() {
  // 左对齐
  return document.execCommand('justifyLeft', false, null);
}

function justifyRight() {
  // 右对齐
  return document.execCommand('justifyRight', false, null);
}

function h1() {
  // 1级标题
  return document.execCommand('fontSize', false, 7);
}

function h2() {
  // 2级标题
  return document.execCommand('fontSize', false, 6);
}

const menuButtons = {
  'bold': bold,
  'italic': italic,
  'underline': underline,
  'justifyCenter': justifyCenter,
  'justifyLeft': justifyLeft,
  'justifyRight': justifyRight,
  'h1': h1,
  'h2': h2
  //...
};

function setupEditor() {
  const menuBars = document.getElementById('menu-bar').children;
  for(let bar of menuBars) {
     const id = bar.getAttribute('id');
     bar.addEventListener('click', menuButtons[id]);
  }
}

setupEditor();

L0编辑器痛点

上面通过浏览器contentEditable + document.execCommand实现了一个简单的富文本编辑器,这种编辑器存在很多坑。不用浏览器厂商对contentEditable实现方案不同,导致contentEditable中生成的Dom结构不可控。 例如通过Enter回车创建一个新的文本行,预期是换行。但是在不同浏览器中新的一行html标签各不相同。Chrome/Safari以及Firefox60+中是div标签,IE中是p标签。还有很多这种类似问题,这里就不展开说明啦。

相关推荐
子兮曰5 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰5 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万5 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝5 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋5 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁5 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95275 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大5 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师5 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学5 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端