我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Rust+React创建富文本编辑器

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Rust+React创建富文本编辑器

简介

在Fiberplane,我们最近遇到了一个有趣的挑战:我们正在使用的富文本编辑器库已经过时了。我们曾经使用Slate.js——一个很好的编辑器——但是当我们为协作编辑实现我们自己的富文本基元时,我们发现我们自己的基元和Slate的数据模型之间的脱节是一个阻碍因素。所以我们开始思考——如果我们建立自己的富文本编辑器(RTE, Rich Text Editor)会怎样?

从一个非常高层次的角度来看,一个富文本编辑器是由两个部分组成的。

  • 一个数据模型和对其进行操作的核心逻辑。
  • 一个渲染上述数据模型的状态并处理用户互动的视图。

我们在视图中使用了Slate,但结果是它也拉入了自己的数据模型。如果我们可以直接在React中实现视图,我们可以大大简化我们的堆栈,并完全控制它的每个方面。缺点是什么?RTEs因为需要支持复杂的用户交互而臭名昭著,而现在我们需要自己处理每一个交互。

在这篇文章中,我们将讨论我们所面临的挑战以及我们如何解决这些问题。

数据模型

我们的产品是一个协作式的笔记本编辑器。笔记本是一个基于块的编辑器,由不同类型的单元组成,从文本单元到图片和图表。因此,我们确定了一个数据模型,它既有利于我们的协作功能,也有利于为我们在单元格内使用的任何富文本字段提供动力的RTE。在这篇文章中,我们将重点讨论TextCell

struct TextCell {
    pub id: String,
    pub content: String,
    pub formatting: Option<Formatting>,
}

这里的content只是纯文本内容,而formatting是将纯文本变成富文本的东西。"多汁"的部分都在格式化类型里面。

type Formatting = Vec<AnnotationWithOffset>;
​
struct AnnotationWithOffset {
    annotation: Annotation,
    offset: u32,
}
​
enum Annotation {
    StartBold,
    EndBold,
    StartItalics,
    EndItalics,
    StartLink { url: String },
    EndLink,
    
}

正如你所看到的,这只不过是一个注释列表,它定义了要应用的格式化类型和它开始的偏移量。我们有意不选择类似于HTML的树状结构,因为格式化范围可以重叠,这将导致复杂的树状操作。此外,每个注释只有一个偏移量的简单性使我们很容易实现我们用于协作的操作转换(OT)算法。

核心逻辑

随着数据模型的出现,也带来了与之互动的代码。当你在一个单元格中打字时,我们在哪里插入新打的字符?这如何影响content和相关的formatting?如果你在一个选择上切换格式,应该发生什么?如果你将一个单元格从中间分割开来,又该怎么办?所有这些以及更多都在Rust的核心逻辑中实现。

你要知道,无论如何我们都需要这些逻辑,因为我们的OT算法也需要它。但现在我们也能用同样的原语来驱动我们的编辑器。

为了使这个逻辑易于测试,它被实现为纯函数,我们在TypeScript的Redux reducer中调用。我们创建了fp-bindgen来生成Rust代码和调用它的TypeScript代码之间的绑定关系。

为了适应RTE(当我们还在使用Slate时还不需要),我们不得不自己引入一段逻辑,就是光标管理。例如,当用户按下左方向键时,我们分派一个MoveCursor动作,其有效载荷如下。

struct MoveCursorPayload {
    pub delta: i32,
    pub extend_selection: bool,
    pub unit: CursorUnit,
}

delta指定光标是向前还是向后移动,通过指定一个1-1的值。extend_selection属性是在用户按住Shift键时使用的,用来扩展当前的选择,或者在还没有选择的情况下创建一个。这个unit决定了我们是按Unicode字母群(用户通常称之为 "字符")还是按单词移动光标,用于用户按住Ctrl/键时。然后,我们的Rust还原器会处理这些动作,并处理所有的边缘情况,包括确保光标不会出现在@的中间。

视图

在我们RTE的大部分开发过程中,我们的编辑器甚至不是一个编辑器。至少从浏览器的角度来看不是。这是因为浏览器通常只识别两种类型的编辑器:纯文本编辑器,如<input><textarea>元素,以及使用一种叫做contenteditable的属性创建的自由格式编辑器。我们的编辑器两者都不是。

我们在最终版本中仍然使用contenteditable属性,因为我们很快会讨论一些实际的影响,但我们有意识地决定尽可能少地依赖它。这对我们最初构建RTE的方式产生了深远的影响,你将在本节中看到。

如果我们最初的版本根本没有使用contenteditable,那么我们怎么能够创建一个富文本编辑器?从用户的角度来看,RTE只不过是一个看起来像文本字段的东西,有一个光标,允许他们输入任何他们喜欢的内容。

所以我们创建了一个普通的React组件,并根据单元格的contentformatting生成了富文本内容,然后使用React.createElement()插入实际的元素,这些元素只是一个应用了样式的<span>元素的平面列表(偶尔会有<a>元素洒在链接上)。然后,我们添加了必要的事件处理程序来捕捉用户的互动,这又将再次调用数据模型上的适当逻辑。

那么用户的光标呢?只是另一个我们自己插入的小React组件。我们会在useLayoutEffect()钩子中测量它需要的位置,然后根据这个来定位它。

所以......很简单,很容易,对吗?好吧,我们现在需要处理的大量的交互使这成为一个重大的挑战。例如,让我们再看一下光标导航。上一节中的例子显示了如何向左和向右移动光标。但是如果用户按了向下的箭头,他们的光标最终会在哪两个字符之间呢?这不是一个简单的问题,因为保持光标的垂直位置需要测量上面那一行的字符的位置。但你如何定义什么是 "上面那一行"?无论是content还是formatting都不包含这些信息。然后记住我们还必须支持选择。还有鼠标互动...

这当然会让人感到不知所措,在开发过程中,可能很难保持对哪些工作和哪些不工作的概述。而这正是我们觉得最初没有contenteditable的工作很好的原因。我们自己做所有的事情,使我们非常清楚自己的位置。任何不工作的交互都是我们仍然需要实现的。没有什么会意外地工作,因为浏览器为我们解决了这个问题--浏览器在这里处于次要地位。

当然,对于最终的版本,很难绕过使用contenteditable。这是因为如果没有它,浏览器扩展将无法识别你的编辑器。而移动浏览器甚至会顽固地拒绝调出屏幕键盘......

手动差异化

所以我们确实需要contenteditable,但是还有一个问题。React不支持对已启用contenteditable的元素的内容进行修补。这是有原因的:contenteditable基本上是告诉浏览器去玩吧。这就像一个没有规则的操场。

React并不喜欢这样。它依靠虚拟DOM来决定它需要如何更新实际的DOM,但当浏览器可以在它不知情的情况下把地毯从它下面拉出来并更新实际的DOM时,这种方法就陷入了困境。这也是我们一开始就避免的原因。为了在更新我们的数据模型时能够保留用户的意图(OT算法的一个重要方面),最好是了解导致任何变化的互动。但是,如果你试图理解浏览器对DOM在内容可编辑元素中的变化,你最多只能是猜测。

所以我们借鉴了React的玩法,实现我们自己的差异算法。但我们不是针对虚拟DOM进行差分,而是在useLayoutEffect()钩子函数中针对真实DOM进行差分和修补。这相对简单,因为我们的用例非常专业,而且它还有一个好处,如果真实DOM中发生任何意外(可能是由于浏览器扩展),我们的算法将简单地将视图恢复到我们基于数据模型的预期。

杂项

上述所有内容可能会让你对编辑器的工作原理有一个较高的认识,但魔鬼是在细节中的。下面是我们需要解决的一些小问题。

  • 支持Unicode。每个人都喜欢的标准,但在工作中却很麻烦。幸运的是,Rust有优秀的unicode_segmentation板块,对我们帮助很大。这帮助我们解决了一些问题,比如按字进行光标导航,以及确保光标能正确地跳过字母群。
  • 光标定位是很棘手的,但我们发现最好的方法是使用浏览器的Selection对象,并通过这种方式设置一个(透明的)本地光标。然后我们使用getBoundingClientRect()来测量浏览器渲染光标的位置,然后我们就可以在那里定位我们自己的光标。
  • 组合事件被浏览器用来组成带有重音的字符和处理拼音等输入。不要忘记处理这些。

总结

创建你自己的富文本编辑器是一项艰巨的任务,但只要有正确的架构和良好的规划,它肯定是可以做到的。如果你发现自己处于必须选择或开发一个富文本编辑器的位置,我们希望你能发现这篇文章的有用信息。

注:特别感谢技术指导dazhao(赵达)对本文翻译的审阅指正。

作者:Arend van Beelen

原文链接:Creating a Rich Text Editor using Rust and React

以上就是Rust+React创建富文本编辑器的详细内容,更多关于Rust React富文本编辑器的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Rust+React创建富文本编辑器

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

怎么使用Rust+React创建富文本编辑器

这篇文章主要介绍“怎么使用Rust+React创建富文本编辑器”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么使用Rust+React创建富文本编辑器”文章能帮助大家解决问题。数据模型我们的产品是
2023-07-02

前端常用的富文本编辑器

富文本编辑器,Multi-function Text Editor, 简称 MTE, 是一种可内嵌于浏览器,所见即所得(WYSIWYG)的文本编辑器。下面就来分享 10 款前端常用的富文本编辑器插件!

Vue怎么整合富文本编辑器TinyMce

本篇内容主要讲解“Vue怎么整合富文本编辑器TinyMce”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue怎么整合富文本编辑器TinyMce”吧!选择一个合适的富文本编辑器对于一个前端项目至
2023-07-04

Vue 轻量级富文本编辑器-Vue-Quill-Editor

本篇给大家介绍一款Vue 轻量级富文本编辑器-Vue-Quill-Editor,希望能够对你有所帮助。

推荐十款常用的富文本编辑器

Slate.js 是受 Draft.js 启发的富文本编辑器。它是一个可深度定制的富编辑器框架,专用于 React。与 Draft.js 类似,它具有良好的 API、强大的插件基础设施以及与 React 的深度连接。

Yjs + Quill:快速实现支持协同编辑的富文本编辑器

Y.js 是一个支持 协同编辑的开源库。只要我们将自己的数据转换为 Y.js 提供的 Y.Array、Y.Map 类型,Y.js 就会自动帮我们做数据的一致性处理和同步。
Yjs协同编辑2024-11-30

jquery怎么获取富文本编辑器的内容

今天小编给大家分享一下jquery怎么获取富文本编辑器的内容的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。具体步骤如下:1.
2023-06-05

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录