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

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

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

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

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

Slate的数据模型

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

type Node = Editor | Element | Text

interface Editor {
children: Node[]
} interface Element {
children: Node[]
[key: string]: unknown
} interface Text {
text: string,
[key: string]: unknown
}

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

  • Editor 编辑器的根节点类型

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

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

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

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

const RichText = (props: any) => {
// 创建Editor
const editor = createEditor()
// 初始值
const [value, setValue] = useState([{
type: 'paragraph',
children: [{ text: "" }]
}]) // 自定义Element渲染
const renderElement = (props) => {
const { attributes, children, element } = props switch (element.type) { // 根据Element中的type属性判断节点类型
case "heading-one":
return <h1 {...attributes}>{children}</h1>;
case "heading-two":
return <h2 {...attributes}>{children}</h2>;
case "paragraph":
return <p {...attributes}>{children}</p>;
}
}
// 自定义Leaf渲染
const renderLeaf = (props) => {
const { attributes, children, leaf } = props // 根据Text中的自定义属性判断样式类型
if (leaf['background-color']) {
children = <span style={{ backgroundColor: leaf['background-color']}}>
{children}
</span>
} if (leaf['font-color']) {
children = <span style={{ color: leaf['font-color']}}>
{children}
</span>
} return <span {...attributes}>{children}</span>;
} return <Slate editor={editor} value={value} onChange={(value) => setValue(value)}>
<Editable
renderElement={renderElement}
renderLeaf={renderLeaf}/>
</Slate>
}

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

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

[
{
type: "paragraph",
children: [
{
text: "著名武术泰斗马保国。"
}
]
},
{
type: "paragraph",
children: [
{
text: "著名"
},
{
text: "篮球运动员",
'font-size': "rgba(139, 87, 42, 1)"
},
{
text: "蔡徐坤"
}
]
}
]

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

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

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

[
{
type: "paragraph",
children: [
{
text: "著名武术泰斗马保国。"
}
]
},
{
type: "paragraph",
children: [
{
text: "著名"
},
{
text: "篮球",
'font-size': "rgba(139, 87, 42, 1)"
},
{
text: "运动",
'font-size': "rgba(139, 87, 42, 1)",
'background-color': "rgba(80, 227, 194, 1)"
},
{
text: "员",
'font-size': "rgba(139, 87, 42, 1)"
},
{
text: "蔡徐坤"
}
]
}
]
光标和选区

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

declare type Path = number[]

interface Point {
path: Path,
offset: number
}

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

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

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

interface Range {
anchor: Point,
focus: Point
}

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

插件机制

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

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

import { createEditor } from "slate"
import { withReact } from "slate-react"
import { withHistory } from "slate-history" const myCustomeEditor = (editor) => {
const { isVoid } = editor // editor原有的isVoid方法, 用以判断节点是否可编辑 editor.isVoid = (element) => { // 根据自定义的type字段将元素置为 不可编辑的
return element.type === 'custome-element' ? true : isVoid(element)
} return editor
} // 创建了一个带有三个插件组合的Slate编辑器
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. 强哥MySQL学习笔记

    数据库服务器:1.数据库2.数据表 数据表:1.表结构(字段)2.表数据(记录)3.表索引(加快检索) 表引擎:1.myisam2.innodb 查看表字段desc table;删除数据库:drop ...

  2. IDEA 打包和导入 Jar 包

    Jar 包介绍 Jar 包 ( Java Archive,Java 归档文件) 是与平台无关的压缩文件格式,它允许将多个 Java 源文件编译生成的 class 文件(即字节码文件)打包成一个压缩文件 ...

  3. Mac 使用 Parallels Desktop 虚拟机安装 win10 教程

    Parallels Desktop 介绍 Parallels Desktop 是一款运行在 Mac 电脑上的极为优秀的虚拟机软件,用户可以在 Mac OS X下非常方便运行 Windows.Linux ...

  4. -bash: $'\201ccd': δ 的错误是linux编码问题(Centos7)

    如果目录是中文目录,你的编码为: [root@dbbd-api01 ~]# cat /etc/locale.conf LANG=zh_CN.GB18030 [root@dbbd-api01 ~]# 那 ...

  5. Servlet中的过滤器和监听器

    1.什么是过滤器? Servlet规范中定义的一种特殊的组件,用来拦截容器的调用过程.容器收到请求之后,首先调用过滤器,然后再调用Servlet 2.生命周期: 1.servlet:servlet的生 ...

  6. kylin的rowkey优化之调整rowkey顺序

    在以hbase为存储的cuboid中,会有很多计算好的数据行,这每个行的key都是由维度值按顺序生成的rowkey 而这个顺序,在我们做cube设计的时候是可以调整的. 具体调整路径是:cube de ...

  7. kotlin知识点

    主构造函数里的参数,如果不声明为var或者val,则这个参数一般是用来初始化父类.它不算是这个类的字段,它的作用域只在主构造函数当中. val 的对象不仅数据不能变, 引用也不能变. //自定义的类似 ...

  8. NBU Rman异机恢复Oracle

    前段时间一个亿级分区表,被分割成历史表和业务表,历史表中保留15天以外的数据,每天都会从业务表中的15天外的数据copy到历史表,并删除业务表15天外的数据,逻辑也很简单,但插入历史表的where 条 ...

  9. Go基础结构与类型02---使用iota定义常量组

    package main import "fmt" /*const ( USA = 0 China = 1 Russia = 2 Britain = 3 France = 4 )* ...

  10. ALD和CVD晶体管薄膜技术

    ALD和CVD晶体管薄膜技术 现代微处理器内的晶体管非常微小,晶体管中的一些关键薄膜层甚至只有几个原子的厚度,光是英文句点的大小就够容纳一百万个晶体管还绰绰有余.ALD 是使这些极细微结构越来越普遍的 ...