from: https://io-meter.com/2014/09/01/contenteditable-and-selection/

最近研究了一下在浏览器中实现的 WYSIWYG 文本编辑器的原理, 在了解基本原理并浏览了 zenpen 这个相对简单的在线编辑器的源码后, 在这方面有种豁然开朗的感觉。

说来让人惊讶,最初在浏览器中使之变为可能的浏览器是 IE5。在那个时代, IE 的确也算是非常先进的浏览器了,现在广为使用的 AJAX 技术,不也是 IE5 最早提供的么? 不过这里就不再讨论当初 IE 那套陈旧的 API 了,而主要来讨论 HTML5 之后被各个浏览器广泛支持的一些技术方法。

进行 WYSIWYG 的文本编辑,需要的几个基础是

  1. 使得某一部分 DOM 可以被编辑
  2. 可以获取和操作用户选中的区域
  3. 可以在编辑的同时对所编辑的部分 DOM 进行修改,实现如添加样式等功能

这些功能都非常方便实现。

使 DOM 可编辑

使一部分 HTML 的 DOM 作为容器进入编辑状态,只需要为这个 DOM 添加一个 contentEditable 属性。 一般来讲,是使用div或者article元素作为这样的容器。

被添加 contentEditable 属性的容器元素的子元素都就可以由用户修改了, 如果想在这个容器下面嵌套一个不可修改的子元素,需要显式地在这个子元素中添加 contentEditable='false'这样的声明。

获取和操作用户选择

操作和获取用户选择是一个非常有用的功能,它不但可以用来实现这里提出的编辑器的功能, 还可以用来实现在光标位置显示提示选单等多种功能,在后面对所编辑部分进行样式修改的时候也常用到。

想要获取一个 Selection 对象非常简单:

var selection = window.getSelection();

Selection 对象有anchorNodefocusNode两个属性,可以用来获得选中部分的开始和结束元素, 不过实用不多(一般实用 Range 代替)。此外还有一个isCollapsed 属性值得注意,当其为true时,代表选择区域开始和结束在相同的点,也就是没有选中内容时光标闪烁的模式。

selection 对象还有诸多方法可以用来修改选择范围,主要就是对 Range 对象的编辑。

首先可以通过

var range = selection.getRangeAt(0);

来获取被选中的第一个区块,以此类推还可以获得第二个、第三个。简单起见我们只讨论选中一个区块的情况。 获得了 Range 对象,我们就可以方便地进行获得选中区域内容了。 Range 对象有一对属性分别用来获得选择区域的开始和结束点。

range.startContainer // 开始点所在的容器(元素)
range.endOffset // 开始点在其容器内的偏移
range.endContainer // 结束点所在的容器(元素)
range.endOffset // 结束点在其容器内的偏移

除了这两对属性,还有一个非常有用的属性,那就是

range.commonAncestorContainer // 选择范围的共同父元素

上面这个属性常用于检测选择范围的类型/样式,比如检测到选中范围的公共父元素是一个 h1 元素, 那么可以在工具栏中将代表 h1 元素的按钮设为激活状态。

需要注意的是,返回的 Range 对象是一类可变对象。简单来说,如果用户的选区改变了, 那么 Range 对象的内容也会改变。因此要记录某一时刻的 Range ,就要记录上面提到了的两对属性。 此外,Range 对象的属性都是只读的,需要通过对应的函数方法来修改。

Range 还定义了各种获取内容和修改内容的函数,详细参数和方法可以参见文档, 这里对几个常见的 Use Case 说明一下。

获得选取的坐标范围

我们可以获得光标在网页上的精确位置,对于选区还能得到其矩形边框的几何表示, 这为我们显示提示菜单提供了方便。获得光标的位置或者选区的矩形边框可以使用

var rect = range.getBoundingClientRect();

rect对象包括的属性包括矩形的topleftrightbottom坐标。 如果选取是collapsed的话,这四个属性就可以用来计算光标的位置了。

保存选区并恢复

如果对选择区域内的元素进行了修改,比如添加新元素、改变元素类型等等, 那么原来的选区会失效。因此一个比较有用的技巧就是在修改元素之前,先保存选区 Range, 待修改完成后再恢复。

一个完整的例子如下:

var selection = window.getSelection();
var range = selection.getRangeAt(0);// 保存所有 Range 的属性
var startContainer = range.startContainer;
var startOffset = range.startOffset;
var endContainer = range.endContainer;
var endOffset = range.endOffset;// 进行元素修改操作
// ......// 构造新的 Range
var newRange = document.createRange(); // 注意,此处必须创建一个新的选区,在原来的 range 上修改无效
newRange.setStart(startContainer, startOffset);
newRange.setEnd(endContainer, endOffset);// 恢复选区
selection.removeAllRanges();
selection.addRange(newRange);

需要注意的是,有些操作可能会自动修改选区,那么使用上面方法就不能达到恢复选区的目的了。 一个常用的技巧是为恢复选区添加一个延迟,也就是在上面将addRange调用放入setTimeout当中。

setTimeout(function(){selection.addRange(newRange);
}, 50);

编辑 DOM,修改样式

文本编辑器的一个很重要的功能就是修改内容的样式,比如将文字加粗、倾斜、加下划线等。 还包括将段落修改为标题、块引用等。一个比较直观的方法是按照上述介绍的保存和恢复选区的方法, 按照需求修改元素添加样式即可。但是这种方法其实细想起来比较复杂,尤其是段落中, 存在混杂多种样式,以至于存在样式可以嵌套的情况(比如一段即是加粗又是倾斜的文字), 维护节点关系和清理空白节点会很繁琐。

好在浏览器为我们提供了一个方便的接口来实现这样的功能。那就是document.execCommand, 这个接口将各种操作抽象成命令的形式。下面展示了实现一些基本功能的方法。

document.execCommand('bold'); // 加粗所选
document.execCommand('italic'); // 倾斜所选
document.execCommand('underline'); // 下划线所选document.execCommand('createLink', true, 'http://io-meter.com'); // 为所选文字加链接
document.execCommand('unlink'); // 取消链接document.execCommand('formatBlock', true, 'h1'); // 将光标所在段落修改为一级标题
document.execCommand('formatBlock', true, 'p'); // 将光标所在块修改为段落

除此之外,浏览器还提供了一些编辑命令,如copycutpaste等。 完整的命令列表可以参见这个文档

需要注意的是,各种浏览器对这些命令的支持也有些不同,因此需要格外注意。 了解了这些命令,就具备实现编辑器中修改样式等功能的基本知识了。

侦听修改

在此还需要说一点题外话,在以往要侦听用户对文本的修改,一般是绑定keydown事件, 此外考虑到用户还可能选取并拖拽来改变内容,可能还要添加mouseup事件, 这种方法是低效且繁琐的,还对元素样式的修改无能为力。

还好,现代浏览器提供了一种新的机制用来检测内容的修改,那就是 Mutation Observer 机制。关于这方面,有一篇很好的文章值得阅读: Detect, Undo And Redo DOM Changes With Mutation Observers。

总结

OK,了解了这些知识,实现一个简单的 Web 文本编辑器是不是也显得不那么难了呢? 虽然在这些 API 上不同浏览器还有些差异,但是已经被广泛应用在实现在线文本编辑功能了。

其实如果只专注于现代浏览器,那么实现如同 Medium 那样漂亮又实用的编辑和写作工具也不是非常困难的事!

在线文本编辑器实现原理相关推荐

  1. HTML在线文本编辑器实现原理

    相信很多人都使用过多种富文本编辑器,富文本编辑器常用于编辑博客.用户交互,富文本编辑器分为两种:所见即所得和非所见即所得 两种富文本编辑器的实现原理是不相同的. 1. 非所见即所得编辑器 这种编辑器的 ...

  2. FCKeditor在线文本编辑器初级应用

    2019独角兽企业重金招聘Python工程师标准>>> 首先从FCKeditor的官网( http://www.fckeditor.net/)下载该编辑器,我下载的版本是FCKedi ...

  3. 这个在线文本编辑器的源代码

    第一次用CSND的Blog的时候就被它的在线编辑器所吸引了.一直想自己写一个这样优秀的在线编辑器,但是一直没有时间. 但是,今天上网的时候却看见了这个在线文本编辑器的源代码 .很是激动,以后写的网站程 ...

  4. 在线文本编辑器(一)—FreeTextBox

    FreeTextBox是最常见的在线文本编辑器了,有诸多好处. 从官方网站http://freetextbox.com/上下载最新的版本,目前来讲最新的版本是4.0 Beta1,是Beta版,我下载了 ...

  5. 怎样选择在线文本编辑器

    在线文本编辑器就是我们平时在网上写博客,写文章时候用到的,比如这篇文章就是用在线文本编辑器写出来的.下面就讨论下几种常用的在线文本编辑器,以及他们的优点. FCKeditor FCKeditor是一款 ...

  6. kindeditor在方法中动态创建在线文本编辑器

    官方编辑器初始化代码: KindEditor.ready(function(K) { editor = K.create('textarea[name="content"]', { ...

  7. java html文本编辑器,基于Java WebHTML在线文本编辑器解决方案.doc

    基于Java WebHTML在线文本编辑器解决方案 基于Java WebHTML在线文本编辑器解决方案 摘要:FckEditor作为众多优秀HTML在线文本编辑器之一,以其支持多语言的优势而受到众多国 ...

  8. 基于jquery的bootstrap在线文本编辑器插件Summernote

    Summernote是一个基于jquery的bootstrap超级简单WYSIWYG在线编辑器.Summernote非常的轻量级,大小只有30KB,支持Safari,Chrome,Firefox.Op ...

  9. 在线文本编辑器-ueditor实践

    2019独角兽企业重金招聘Python工程师标准>>> 1.在线文本编辑器的作用? UEditor是由百度web前端研发部开发所见即所得富文本web编辑器,具有轻量,可定制,注重用户 ...

最新文章

  1. h5新增浏览器本地缓存localStorage
  2. 树莓派3开wifi热点
  3. 正则表达式笔试题php,2017年初级PHP程序员笔试题
  4. 【老孙随笔】项目经理要如何看待技术?
  5. Java实现获取HDFS子目录数量_Java实现读取HDFS目录
  6. PHP实现文章的删除,php如何实现删除文章
  7. php变量前下滑_PHP变量
  8. python回归分析实验_python线性回归实验
  9. python异常捕获_python 捕获异常
  10. AD如何清理过期电脑
  11. 实现算法2.15、2.16的程序(一个数组只生成一个静态链表)
  12. matlab的开方算法_常见算法的MATLAB实现
  13. js图片url反转file文件
  14. BZOJ3162: 独钓寒江雪
  15. Android封装拍照sdk,Android 短视频拍摄、拍照滤镜 第三方库SDK
  16. linu修改open files无效_不越狱修改运动步数,安卓苹果手机通用
  17. uvc摄像头代码解析之描述符
  18. 大数据常用的软件工具有哪些?
  19. 什么是PWM 和PFM?
  20. 智能合约通证化与 Web3 革命(1):为何智能合约没能成为区块链的杀手级应用?...

热门文章

  1. 初学者也能上手的Python数据分析案例
  2. Thinkphp6如何跨域请求
  3. 《全国土地分类》与土地利用现状分类对照表
  4. (RabbitMQ 二)Springboot项目中使用RabbitMQ的相关依赖
  5. Ghost博客手动搭建
  6. 科技云报道:从re:Invent 2022读懂亚马逊云科技的“生态棋局”
  7. 在ArcGIS中创建Python工具(三)
  8. 融云im callkit 会话遇到的坑融云研究
  9. linux 如何删除log文件,教你在Linux下如何清除系统日志
  10. 学习3D游戏建模可以做什么?主要有几个就业方向,最挣钱的是哪个?