如何从头手写一个富文本编辑器(解析slate源码,连载)
2021/12/28 22:37:10
本文主要是介绍如何从头手写一个富文本编辑器(解析slate源码,连载),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
背景
最近文档很火,老板也要。我也很感兴趣,于是入坑学习实践了一番。一眨眼就是一年过去了,项目初见成效,但是发现困难和挑战也越来越棘手。于是深入研究改编了一下源码,为后面重写源码做准备。
我们的项目的成果截图,镇宅一下:
文章末尾有demo源码,欢迎评论交流。
数据结构
既然是学习slate源码也就不想创新一个数据结构了,沿着前人的路先走一下吧。考虑到后续的大文档需要视窗加载,我认为一个JSON搞定文档过于粗糙了,后续可能会改造成多个数组组成一个文档。
第一天,最简单的demo
首先,写一个最简单p标签,又叫我们可以怎样从浏览器手中接管用户文本输入。
[{type:'p',children:[{text:'大橘'}]}]
效果如下
如果是想要一行两个大橘,我需要的结构是这样:
[{type:'p',children:[{text:'大橘大橘'}]}]
此时需要一个操作insertText:
export function insertText(root, text, path) { // 获取指定path的element var node = getNodeByPath(root, path); if (text) { node.text = node.text + text; } } function getNodeByPath(root, path) { // return root[0].children[0] var node = root; console.log(window.root === root) for (var i = 0; i < path.length; i++) { const p = path[i] node = node[p] || node.children[p]; } return node; } const root = [{ type: 'p', children: [{ text: '大橘' }] }] insertText(root, '大橘', [0]) console.log(JSON.stringify(root)) //[{"type":"p","children":[{"text":"大橘大橘"}]}]
好了一个编辑器最简单的逻辑ok了。
视图展示
这里我选择了react
创建项目
(1)npm install -g create-react-app (2)create-react-app day001 (3)cd day001 (4)npm start
在App.js中写如下代码
import './App.css'; import {useEffect} from 'react' import {getString, insertText} from './insertText' window.root =[{ type: 'p', children: [{ text: '' }] }] function App() { // const [root, setRoot] = useState(initRoot) useEffect(() => { const input = document.getElementById('editor'); input.addEventListener('beforeinput', updateValue); function updateValue(e) { e.preventDefault() e.stopPropagation() insertText(window.root, e.data, [0,0]) console.log(e.data, window.root) input.textContent = getString(window.root) } }, []) return ( <div className="App"> 这是一个demo编辑器 <div id='editor' contentEditable onInput={(e)=>{ e.preventDefault() e.stopPropagation() console.log(e) return }}> </div> </div> ); } export default App;
效果图:
第二天,掌控浏览器中光标
小标题又可以叫做在接管输入文字以后,我们可以怎样在富文本光标位置输入文本?
在第一天,我们已经实现了,监听用户输入,并选择性的输入内容。虽然它使用的原理很有价值,但是这个编辑器有点low,不管用户在编辑器哪里输入,内容都只能在文本末尾追加。作为一个富文本编辑器这是不可饶恕的。
那么现在,我们来完善这个问题。
首先,我们知道如何获取光标的位置,以及对应文本的位置。这里我们会用到window.getSelection() api来获取光标所在的dom,以及光标在dom中文本的位置。
insertText代码修改如下
export function insertText(root, text, path) { const domSelection = window.getSelection() console.log('domSelection', domSelection, domSelection.isCollapsed, domSelection.anchorNode, domSelection.anchorOffset, JSON.stringify(domSelection)) // 获取指定path的element var node = getNodeByPath(root, path); if (domSelection.isCollapsed) { if (text) { const before = node.text.slice(0, domSelection.anchorOffset) const after = node.text.slice(domSelection.anchorOffset) node.text = before + text + after } } else { // TODO 如果光标选中一个范围 } // console.log(root[0].children[0] === node, root[0].children[0], node) }
我们实现了在光标位置插入文本,但是光标在输入后位置不对了,我们接下来要改变光标。
简单介绍一下setBaseAndExtent
方法
// dom 是指要选中的dom节点,offset是指dom节点里面文字的位置 window.getSelection().setBaseAndExtent( dom, offset, dom2, offset2)
重新写一下我们的APP.js文件,主要修改了两个useEffect
方法,以及把文本渲染交给state来改变。
import './App.css'; import { useState, useEffect } from 'react' import { getString, insertText } from './insertText' window.root = [{ type: 'p', children: [{ text: '' }] }] function App() { // 记录我们输入的内容 const [txt, setTxt] = useState('') // 光标的offset const [txtOffset, setTxtOffset] = useState(0) // 负责注册监听beforeinput事件,并阻止默认事件。在监听中修改window.root,并在里面更新txt和txtO,最后清除光标,防止txt更新导致光标闪动。 useEffect(() => { const input = document.getElementById('editor'); input.addEventListener('beforeinput', updateValue); function updateValue(e) { e.preventDefault() e.stopPropagation() insertText(window.root, e.data, [0, 0]) // console.log(e.data, window.root) const getText = getString(window.root) const { anchorOffset } = window.getSelection() setTxtOffset(anchorOffset + e.data.length) setTxt(getText) window.getSelection().removeAllRanges() } return () => { input.removeEventListener('beforeinput', updateValue); } }, []) // 监听txtOffset,并且用setBaseAndExtent更新光标位置,使用setTimeout是因为要在页面渲染后,再改变光标位置 useEffect(() => { const { anchorNode } = window.getSelection() setTimeout(() => { if (!anchorNode) { return } let dom = anchorNode if (dom.childNodes && dom.childNodes[0]) { dom = dom.childNodes[0] } window.getSelection().setBaseAndExtent( dom, txtOffset, dom, txtOffset) }) }, [txtOffset]) return ( <div className="App"> 这是一个demo编辑器 <div id='editor' contentEditable onInput={(e) => { e.preventDefault() e.stopPropagation() console.log(e) return }}> {txt} </div> </div> ); } export default App;
此时,我们的编辑已经可以正常输入英文和数字。但是中文的问题如何解决呢?
后续更新~
源码:https://github.com/PangYiMing/study-slate
这篇关于如何从头手写一个富文本编辑器(解析slate源码,连载)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-06小米11i印度快充版ROM合集:极致体验,超越期待
- 2024-10-06【ROM下载】小米11i 5G 印度版系统, 疾速跃迁,定义新速度
- 2024-10-06【ROM下载】小米 11 青春活力版,青春无极限,活力全开
- 2024-10-05小米13T Pro系统合集:性能与摄影的极致融合,值得你升级的系统ROM
- 2024-10-01基于Python+Vue开发的医院门诊预约挂号系统
- 2024-10-01基于Python+Vue开发的旅游景区管理系统
- 2024-10-01RestfulAPI入门指南:打造简单易懂的API接口
- 2024-10-01初学者指南:了解和使用Server Action
- 2024-10-01Server Component入门指南:搭建与配置详解
- 2024-10-01React 中使用 useRequest 实现数据请求