如何从头手写一个富文本编辑器(解析slate源码,连载)

2023-10-27

背景

最近文档很火,老板也要。我也很感兴趣,于是入坑学习实践了一番。一眨眼就是一年过去了,项目初见成效,但是发现困难和挑战也越来越棘手。于是深入研究改编了一下源码,为后面重写源码做准备。

我们的项目的成果截图,镇宅一下:

image.png

文章末尾有demo源码,欢迎评论交流。

数据结构

既然是学习slate源码也就不想创新一个数据结构了,沿着前人的路先走一下吧。考虑到后续的大文档需要视窗加载,我认为一个JSON搞定文档过于粗糙了,后续可能会改造成多个数组组成一个文档。

第一天,最简单的demo

首先,写一个最简单p标签,又叫我们可以怎样从浏览器手中接管用户文本输入。

[{type:'p',children:[{text:'大橘'}]}]

效果如下

slate-p标签大橘效果图

如果是想要一行两个大橘,我需要的结构是这样:

[{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;


效果图:

image.png

第二天,掌控浏览器中光标

小标题又可以叫做在接管输入文字以后,我们可以怎样在富文本光标位置输入文本?

在第一天,我们已经实现了,监听用户输入,并选择性的输入内容。虽然它使用的原理很有价值,但是这个编辑器有点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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何从头手写一个富文本编辑器(解析slate源码,连载) 的相关文章

随机推荐

  • docker: Error response from daemon: Ports are not available: listen tcp 0.0.0.0:9090: bind: An attem

    Windows10下使用docker部署minio时报错 命令 docker run p 9090 9000 name minio v mydata minio data data v mydata minio config root mi
  • 基于大数据框架的协同过滤算法餐饮推荐系统【Update2023-6-25】

    开始之前 设计思路 首先这位作者的推荐系统给了我很大的构思启发 Github地址 https github com share23 Food Recommender 他的系统采用实时大数据技术组件 具体有Spark Streaming HD
  • lvm 扩容和缩减

    lvm 扩容和缩减 1 LVM简介 LVM是逻辑卷管理 Logical Volume Manager 的简称 它是Linux环境下对磁盘分区进行管理的一种机制 LVM是建立在硬盘和分区之上的逻辑层 来提高磁盘分区管理的灵活性 LVM的工作原
  • 脚本关

    第9题 提交验证码后发现 所以用burpsuite改脚本 把电话号码改成他所要求的在扔回去 就能出现key
  • 基于springboot+vue+fastdfs的文件简易管理系统

    文件系统的实现描述 技术栈运用 后端技术栈 springboot mybatis 数据库 mysql 5 7 前端 vue cli3 0 axios element ui 分布式文件存储系统 FastDFS 功能实现 前台 前台展示用户上传
  • 微信小程序滑动穿透解决方案

    微信小程序滑动穿透解决方案 微信小程序弹窗滑动会穿透 导致底层也会跟着滑动 在网上找了好多方法都没有解决滑动穿透问题 后来在开发中做滑动的时候用到了scroll view 后改用这个标签尝试了一下 果然解决了滑动穿透的问题 出现滑动穿透的原
  • 常见的Java框架有哪些?

    Java语言仍然是当下 程序猿 们最爱使用的热门编程语言之一 想要进入这个行业 Java可以为你引路 很多初学的人可能没有系统的了解过Java框架都有哪些 今天为大家整理一下常见的Java框架都有什么 Java框架 1 Spring框架 S
  • matlab 多输入多输出神经网络

    构建训练样本集 inputn input train outputn output train 构建BP神经网络 net newff inputn outputn 21 21 tansig tansig trainbr 网络参数 net t
  • STM32 之十 供电系统及内部参照电压(VREFINT)使用及改善ADC参考电压,内部参照电压的具体方法,只有在STM32F0x芯片的参考手册中才能找到,其他MCU的参考手册都是很简单的说明

    STM32 之十 供电系统及内部参照电压 VREFINT 使用及改善ADC参考电压 ZCShouEXP 2018 12 21 10 50 33 16404 收藏 32 展开 问题 今天在使用 STM32F407 的 ADC 时遇到一个问题
  • char *,char **,char a[ ],char *a[]

    1 字符数组 C语言中规定数组代表数组所在内存位置的首地址 也是 str 0 的地址 即str str 0 而printf s str 为什么用首地址就可以输出字符串 因为还有一个关键 在C语言中字符串常量的本质表示其实是一个地址 这是许多
  • 2022年年终总结,不忘初心,砥砺前行

    从2019年开始第一次在csdn写个人年终总结 不知不觉已经坚持了三年 今年是第四次写年终总结 其实在前几个周自己写过一个年终总结 不过那是每年公司年要求每个人都要写的一个工作述职 来总结过去一年自己给公司所做的贡献 以及存在的问题 还有未
  • MySQL优化篇:执行计划explain中key_len计算方式

    概述 key len表示索引使用的字节数 根据这个值可以判断索引的使用情况 特别是在使用联合索引的时候 判断该索引有多少部分被使用到非常重要 key len的长度计算公式很重要 key len越小 说明索引效果越好 准备结构和数据 在MyS
  • IDEA 2016免费下载(附安装教程)

    下载地址 软件名称 IntelliJ IDEA 2016 软件大小 790MB 安装环境 Windows 下载链接 https pan baidu com s 1Hy0bVzh9uemWMnhRgx8HkA 提 取 码 geek 建议复制粘
  • QT之Layout类

    这个类是用来布局的 它有各种各样既定风格的盒子 往这个盒子里添加控件 这些控件就会按照这个盒子的风格来找到自己的位置 举个例子 一个水平盒子往里面添加控件 是按照从左往右的顺序依次添加 QHBoxLayout layout 首先创建一个水平
  • IDEA中编译及运行ssm(非maven)项目

    一直用springboot框架 所有回顾下ssm项目环境配置及启动 1 导入项目 2 配置项目环境 2 1然后添加项目自带的jar包 2 2添加 tomcat server服务器 要不运行时代码会报错 缺少依赖 选择自己电脑上的tomcat
  • 写一篇关于chatGPT的心得体会

    这次使用ChatGPT训练的大型语言模型 让我真正感受到了自然语言处理的强大能力 ChatGPT可以根据用户输入的文本 快速生成准确 流畅的回复 拥有丰富的语义表达能力 可以识别各种语句的结构和意义 快速建立起人机之间的交互 它不仅可以帮助
  • MobaXterm插件连接Linux虚拟机

    一 前言 在VirtualBox里面打开的虚拟机系统界面是非常小的 而且看不到鼠标的光标显示 无法去随意点击和进行文件的手动操作 所以老师这里有一个可以连接虚拟机的插件 MobaXterm插件 这个就相当于是手机的投屏器 可以放大系统界面
  • 微信小程序开发架构——JavaScript的基本概述 和 JavaScript在 Nodejs、小程序中、浏览器中的使用方法

    轻量 是指在入门JavaScript语言时候觉得JavaScript 没有其它语言学习起来那么重 解释性 是指所编写的JavaScript语言它在运行时 机器会把JavaScript语言翻译成机器语言 JavaScript语法接近于Java
  • 数字图像字符识别——数字识别

    本文简单介绍图片字符识别的原理 主要识别图片中的数字 其他字符识别原理类似 大家应该知道 对于人类来说 可以很容易理解一张图片所表达的信息 这是人类视觉系统数万年演变进化的结果 但对于计算机这个诞生进化不到百年的 新星 要让它理解一张图像上
  • 如何从头手写一个富文本编辑器(解析slate源码,连载)

    背景 最近文档很火 老板也要 我也很感兴趣 于是入坑学习实践了一番 一眨眼就是一年过去了 项目初见成效 但是发现困难和挑战也越来越棘手 于是深入研究改编了一下源码 为后面重写源码做准备 我们的项目的成果截图 镇宅一下 文章末尾有demo源码