浅析富文本编辑器框架Slate.js

本文不是关于Slate.js使用入门的文章,如果还不了解该框架,建议先阅读下官方的文档:Slate官网文档

关于Slate的一些特性
  • 不同于其他编辑器类的库,Slate并不提供譬如粗体、斜体、字体色等开箱即用的功能
  • Slate只是提供了一套自己定义的核心数据模型,以此一些操作数据和选区相关的API
  • 视图层的渲染和行为完全由开发者基于React定制

从顶层设计上看,Slate的架构是典型的MVC模型,由自身定义数据模型(Model),暴露操作数据的方法(Controller),然后交由用户使用该数据在React中做渲染(View)

虽然在实现简单的编辑器应用时这种方式显得有些繁冗,但在遇到需要对业务做较定制化的功能,如内嵌复杂表单、流程图等时,就能展现出极大的灵活性。而这类需求在使用其他编辑器的库时,经常是不可行的或者成本很高(往往要在源码层面进行改造)

Slate的数据模型

Slate.js数据模型的设计非常的“类DOM”,对于拥有Web基础的开发者降低了心智负担。下面从节点(Node)和选区(Selection)的设计上说明。

  1. type Node = Editor | Element | Text
  2. interface Editor {
  3. children: Node[]
  4. }
  5. interface Element {
  6. children: Node[]
  7. [key: string]: unknown
  8. }
  9. interface Text {
  10. text: string,
  11. [key: string]: unknown
  12. }

Node作为最抽象的节点类型,包括以下三种类型:

  • Editor 编辑器的根节点类型

  • Element 具有children属性,可以作为其他Node的父节点;由传入的renderElement函数做自定义渲染

  • Text 包含文本信息;由renderLeaf函数做自定义渲染;在添加mark时,将文本打散成不同的Leaf(这个行为是由Slate执行的,下面的例子会讲)

除了接口中定义的属性,也可以在节点中添加任意业务相关的属性值(如下面的例子)。

一个基础的使用示例如下:

  1. const RichText = (props: any) => {
  2. // 创建Editor
  3. const editor = createEditor()
  4. // 初始值
  5. const [value, setValue] = useState([{
  6. type: 'paragraph',
  7. children: [{ text: "" }]
  8. }])
  9. // 自定义Element渲染
  10. const renderElement = (props) => {
  11. const { attributes, children, element } = props
  12. switch (element.type) { // 根据Element中的type属性判断节点类型
  13. case "heading-one":
  14. return <h1 {...attributes}>{children}</h1>;
  15. case "heading-two":
  16. return <h2 {...attributes}>{children}</h2>;
  17. case "paragraph":
  18. return <p {...attributes}>{children}</p>;
  19. }
  20. }
  21. // 自定义Leaf渲染
  22. const renderLeaf = (props) => {
  23. const { attributes, children, leaf } = props
  24. // 根据Text中的自定义属性判断样式类型
  25. if (leaf['background-color']) {
  26. children = <span style={{ backgroundColor: leaf['background-color']}}>
  27. {children}
  28. </span>
  29. }
  30. if (leaf['font-color']) {
  31. children = <span style={{ color: leaf['font-color']}}>
  32. {children}
  33. </span>
  34. }
  35. return <span {...attributes}>{children}</span>;
  36. }
  37. return <Slate editor={editor} value={value} onChange={(value) => setValue(value)}>
  38. <Editable
  39. renderElement={renderElement}
  40. renderLeaf={renderLeaf}/>
  41. </Slate>
  42. }

假如我们在该编辑器中输入了两行文字,并选取一段文本添加颜色样式:

则上图的文本内容所对应的数据结构:

  1. [
  2. {
  3. type: "paragraph",
  4. children: [
  5. {
  6. text: "著名武术泰斗马保国。"
  7. }
  8. ]
  9. },
  10. {
  11. type: "paragraph",
  12. children: [
  13. {
  14. text: "著名"
  15. },
  16. {
  17. text: "篮球运动员",
  18. 'font-size': "rgba(139, 87, 42, 1)"
  19. },
  20. {
  21. text: "蔡徐坤"
  22. }
  23. ]
  24. }
  25. ]

该数组中最顶层的对象映射了Element元素(由renderElement渲染的),在其中的type字段中设为paragraph标志为默认的块级元素(当然也可以设为其他任何值,Slate.js并不关心type字段的含义)。下一层的叶子节点对象,包含了text字段,表示文本内容;也可能含有自定义的一些marks,例如上面的'font-size',用来在renderLeaf中依据marks来实现自定义的样式渲染。

接下来再选取一段文字赋予一个背景色:

添加背景色的选区是在上面带有font-size的Text节点中的,因此会被打散成三个Text,变为如下形式:

  1. [
  2. {
  3. type: "paragraph",
  4. children: [
  5. {
  6. text: "著名武术泰斗马保国。"
  7. }
  8. ]
  9. },
  10. {
  11. type: "paragraph",
  12. children: [
  13. {
  14. text: "著名"
  15. },
  16. {
  17. text: "篮球",
  18. 'font-size': "rgba(139, 87, 42, 1)"
  19. },
  20. {
  21. text: "运动",
  22. 'font-size': "rgba(139, 87, 42, 1)",
  23. 'background-color': "rgba(80, 227, 194, 1)"
  24. },
  25. {
  26. text: "员",
  27. 'font-size': "rgba(139, 87, 42, 1)"
  28. },
  29. {
  30. text: "蔡徐坤"
  31. }
  32. ]
  33. }
  34. ]
光标和选区

光标的定位由一个 Pathoffset 确定,Path 代表节点在文档中的位置,offset 则代表在节点中的偏移量:

  1. declare type Path = number[]
  2. interface Point {
  3. path: Path,
  4. offset: number
  5. }

Paht 是一个number类型数组,包含的数值代表着从文档数据模型的根部到光标所在Text节点的路径offset 表示光标在Text节点上的偏移量

如图中框中的节点所对应的Path就是[1, 0]

选取的接口定义 Range 与原生selection的属性非常相似:

  1. interface Range {
  2. anchor: Point,
  3. focus: Point
  4. }

锚点anchor代表选区的起始点,焦点focus代表选区的结束点;两者都为上述的Point类型。

插件机制

Slate提供了插件的机制允许我们覆盖编辑器原有的行为。除了直接使用slate-react和slate-history这些官方的插件,也可以自定义插件来对Slate编辑器进行拓展,而且实现方式非常简易:提供一个函数,该函数接收一个编辑器的实例editor对象,在其中重写实例对象上的方法,并返回editor实例。

下面是个例子,加入在实现业务时有这么一个场景,需要在文本域中插入一些自定义的控件如按钮、下拉框等,并且都不可被编辑;而默认情况下在文本域中所有的dom元素都是contenteditable=true的状态,是能够被光标聚焦和编辑的。为了改变这种行为,可以自行实现一个插件:

  1. import { createEditor } from "slate"
  2. import { withReact } from "slate-react"
  3. import { withHistory } from "slate-history"
  4. const myCustomeEditor = (editor) => {
  5. const { isVoid } = editor // editor原有的isVoid方法, 用以判断节点是否可编辑
  6. editor.isVoid = (element) => { // 根据自定义的type字段将元素置为 不可编辑的
  7. return element.type === 'custome-element' ? true : isVoid(element)
  8. }
  9. return editor
  10. }
  11. // 创建了一个带有三个插件组合的Slate编辑器
  12. const eidotr = myCustomeEditor(withHistory(withReact(createEditor())))

浅析富文本编辑器框架Slate.js的更多相关文章

  1. 放弃WebView,使用Crosswalk做富文本编辑器

    版权声明: 欢迎转载,但请保留文章原始出处 作者:GavinCT 出处:http://www.cnblogs.com/ct2011/p/4100132.html 为什么放弃WebView Androi ...

  2. 【重点突破】—— React实现富文本编辑器

    前言:富文本编辑器Rich Text Editor, 简称 RTE, 是一种可内嵌于浏览器,所见即所得的文本编辑器.   一.安装插件 react-draft-wysiwyg: 文本编辑器插件 dra ...

  3. 富文本编辑器kindeditor的使用

    第一步:导入前端js文件 <!-- 富文本编辑器 --> <link rel="stylesheet" href="../plugins/kindedi ...

  4. js ui框架 My97日期控件 富文本编辑器

    My97日期控件 http://www.my97.net/dp/index.asp 富文本编辑器 http://www.kindsoft.net/demo.php 百度的magic也不错 http:/ ...

  5. 百度富文本编辑器ueditor在jsp中的使用(ssm框架中的应用)

    折腾了一下午终于把百度富文本编辑器ueditor搞定了!   项目地址:https://github.com/724888/lightnote_new     首先我参考了一个ueditor的demo ...

  6. JS编写自己的富文本编辑器

    富文本编辑器,网上有很多功能齐全种类丰富的如百度的Ueditor,简单适用型的如WangEditor等等.在经过一番挑选后,我发现都不适用现在的项目,然后决定自己造轮子玩玩.富文本编辑器中主要涉及到J ...

  7. 富文本编辑器上传图片需要配置js,后台代码

    富文本编辑器上传图片需要配置js,后台代码

  8. 一款纯HTML+CSS+JS富文本编辑器-handyeditor

    官网:http://he.catfish-cms.com/ 修改版本(修改一些BUG和图片上传服务器 点击下载: handyeditor富文本编辑器.zip): 图片上传接口上传类型: Content ...

  9. Vue.js中使用wangEditor富文本编辑器

    1.前端代码 前端HTML <script src="https://cdn.bootcss.com/wangEditor/10.0.13/wangEditor.js"> ...

随机推荐

  1. Rust 多态

    Rust 多态 分发 多态的上下文中的方法解析过程被称为分发,调用该方法称为分发化,在支持多态的主流语言中,分发可以通过以下任意一种方式进行. 静态分发 当在编译期决定要调用的方法时,它被称为静态分发 ...

  2. Ansible_编写循环和条件任务

    一.利用循环迭代任务 1️⃣:Ansible支持使用loop关键字对一组项目迭代任务,可以配置循环以利用列表中的各个项目.列表中各个文件的内容.生成的数字序列或更为复杂的结构来重复任务 1.简单循环 ...

  3. Centos双网卡配置默认路由

    Centos6.5 双网卡,我们只需要一个默认路由,如果两个都有或都没有会有一系列的问题 [root@centos]# vi /etc/sysconfig/network修改以下内容NETWORKIN ...

  4. 有关RootViewController设置的问题和Unbalanced calls to begin/end appearance transitions for <CYLTabbarController>

    问题 今天做项目时遇到了一个问题,我想做一个登陆页面,在用户输入了登录名和密码后跳转到app主界面,最开始用的是在方法中新建一个appdelegate对象,再将其中的window属性设置Tabbar为 ...

  5. 西门子 S7300 以太网模块连接组态王方法

    北京华科远创科技有限研发的远创智控ETH-YC模块,以太网通讯模块型号有MPI-ETH-YC01和PPI-ETH-YC01,适用于西门子S7-200/S7-300/S7-400.SMART S7-20 ...

  6. TPS和响应时间之间是什么关系

    在这个图中,定义了三条曲线.三个区域.两个点以及三个状态描述. 三条曲线:吞吐量的曲线(紫色).使用率 / 用户数曲线(绿色).响应时间曲线(深蓝色).三个区域:轻负载区(Light Load).重负 ...

  7. docker容器与镜像的区别

    今天抛开原理,抛开底层.通俗的讲解docker中容器与镜像的区别. 对于初学者来说,刚刚接触docker会有点迷,特别是镜像与容器.其实我们可以理解镜像与容器为一对多的关系. 下图错误的示范,为什么是 ...

  8. 腾讯 angel 3.0:高效处理模型

    腾讯 angel 3.0:高效处理模型 紧跟华为宣布新的 AI 框架开源的消息,腾讯又带来了全新的全栈机器学习平台 angel3.0.新版本功能特性覆盖了机器学习的各个阶段,包括:特征工程.模型训练. ...

  9. TensorFlow实现多层感知机MINIST分类

    TensorFlow实现多层感知机MINIST分类 TensorFlow 支持自动求导,可以使用 TensorFlow 优化器来计算和使用梯度.使用梯度自动更新用变量定义的张量.本文将使用 Tenso ...

  10. NVIDIA FFmpeg 转码技术分析

    NVIDIA FFmpeg 转码技术分析 所有从 Kepler 一代开始的 NVIDIA GPUs 都支持完全加速的硬件视频编码,而从费米一代开始的所有 GPUs 都支持完全加速的硬件视频解码.截至 ...