富文本编辑器的基本原理与实践
作者:realazy 来源:realazy 发布时间:2008-06-13 13:28:00
富文本编辑器,Rich Text Editor, 简称 RTE, 它提供类似于 Microsoft Word 的编辑功能,容易被不会编写 HTML 的用户并需要设置各种文本格式的用户所喜爱。它的应用也越来越广泛。最先只有 IE 浏览器支持,其它浏览器相继跟进,在功能的丰富性来说,还是 IE 强些。虽然没有一个统一的标准,但对于最基本的功能,各浏览器提供的 API 基本一致,从而使编写一个跨浏览器的富文本编辑器成为可能。
在很多开发者看来,富文本编辑器的编写是一件很神秘或者复杂的事情。神秘倒没有,复杂的话,确实如此。但是它的基本原理并不复杂,入门也不难。今天我们的主题是讲述基本原理,并逐步演示一个简单富文本编辑器的产生。这是我在 D2 上的一个分享内容,在台上的演讲效果不佳,固写下来,希望能够对感兴趣的读者有所帮助。
富文本编辑器的基本原理
这个原理实在是太简单了!对于支持富文本编辑的浏览器来说,其实就是设置 document
的 designMode
属性为 on
后,再通过执行 document.execCommand('commandName'[, UIFlag[, value]])
即可。commandName
和 value
可以在 MSDN 上和MDC 上找到,它们就是我们创建各种格式的命令,比方说,我们要加粗字体,执行 document.execCommand('bold', false)
即可。很简单是吧?但是值得注意的是,通常是选中了文本后才执行命令,被选中的文本才被格式化。对于未选中的文本进行这个命令,各浏览器有不同的处理方式,比方 IE 可能是对位于光标中的标签内容进行格式化,而其它浏览器不做任何处理,这超出本文的内容,不细述。同时需要注意的是,UIFlag
这个参数设置为 true
表示 display any user interface triggered by the command (if any), 在我们今天的教程中都是 false
, 而 value
也只在某些 commandName
中才有,具体参考以上刚给出的两个链接。
为了不影响当前 document
, 通常的做法是在页面中嵌入一个 iframe
元素,然后对这个 iframe
内的 document
(通过 iframe.contentWindow.document
获得)进行操作。
十分简单,是吧?下面我们来动手做一个。
编写一个简单的富文本编辑器
这个例子使用了 YUI. 即使你对它不是很熟悉也没有关系,我在这里只使用了它的 DOM 和 Event 的一些跨平台基本方法。
搭架
在此强调一下很久未曾提及的 unobtrusive. 我们的编辑器是对 textarea
元素的一个增强(enhencement),就是说,即使 JavaScript 被禁用了,用户还可以通过 textarea
编辑内容。
在这个例子中,我们将使用 YAHOO.realazy
的命名空间,在之下实现一个 RTE
的类。我们今天的编辑器很简单,因此构造器(constructor) 的参数也只有 textarea
一个。我们使用一个实例变量来保存工具条的各个项目。实例初始化放到一个叫 render
的方法中。这一步的页面和代码见第 1 步。
创建 iframe
并替换 textarea
搭好架子,正如我在前面所说,建立一个 iframe
, 编辑器的所有操作都在 iframe
的 document
内执行。并且把 textarea 隐藏起来。从第 2 步中可以看到,我们已经有了一个 iframe
, 但不能输入任何东西,很正常,我们没有打开它的 designMode
嘛。
开启 designMode
这一步涉及的东西挺多,也是关键。我们会创建获取 iframe
的 document
的方法,并通过程序的方式向 iframe
写入空页而非使用一个外接的 blank.html. 我们使用一个类属性 YAHOO.realazy.RTE.htmlContent
来保存空页的 html
. 在准备好一切后,就可以开启 designMode
了。页面和代码详见第 3 步。看,我们已经可以在 iframe
里输入东西了。
构建工具条
我们需要操作的工具条!这样才可以控制 iframe
里的内容,才能称之为编辑器。在此我并不打算实现太多的功能,只是选择字形、字号、加粗、斜体、下划线、居左、居中、居右、超链接和插图作为示例。对于跨平台,Mozilla Midas Specification 是不错的参考。ok, 请看第 4 步,我们的工具条出来了,虽然很丑。我同时用 CSS 对 iframe
的宽度做出了一些调整。
给工具条加上事件
嗯,工具条出来了,编辑器看起来也“人模狗样”了,你兴奋的点啊点,没什么效果……意料中嘛。我们接着给工具条绑定一些事件,让编辑器内容能够响应工具条。在这一步,我们把 execCommand
再封一层,前面说过,我们用不上 UIFlag
,让它永远是 false
好了。好,有代码就有真相,请看第 5 步。如果是正使用 IE, 请先暂时转移到其它浏览器。看到了吧,工具条生效了!
解决 IE 的问题
well, 如果你没有听我的劝告,依然使用 IE, 你会发现除了字型和字号其它的都不能用。为什么呢?你观察一下,有没有发现,其它浏览器选择文本后,再点击工具条上的项目,被选中的文本是否依然选中的?而 IE 呢,在点击工具条时,选中的文本马上失去选中的状态,所以它们就失败了。所以,如果我们能够保证点击工具条文本保持选中状态,就可以解决 IE 的问题了。
Microsoft 给 HTML 标签一个很奇怪的属性 unselectable
, 只要设置为 on
, 焦点不会转移到点击的元素上,从而保证文本的选中状态。
请看第 6 步。这也是解决 IE 头痛问题的关键所在。我曾经在这上面费了很大脑筋。
高级主题展望
good, 看看我们现在的代码,224 行。相比其它动辄上万行的编辑器,你可能会觉得不可思议。因为我们这个最基本的编辑器,连 selection
都没有用到。很多很酷的效果,比如 Google Doc 里能够动态改变链接文本,使用页内层而非弹出的 prompt
来操作等高级功能,基本上都要用到 TextRange
(IE) 或者 Range
(W3C). 要命的是这两个东西互补兼容,只是相似而已。入门推荐看PPK 的 Introduction to Range.
在此我们就不深入了,等我有时间我会总结一些奇技淫巧(呜呼,前端开发需要的奇技淫巧太多了,这不是好事情)出来。
猜你喜欢
- canal简介由阿里巴巴开源 github地址:https://github.com/alibaba/canalCanal是阿里巴巴开源的一
- IntelliJ IDEA 2020.1 插件中心一直打不开,鉴于有部分同学反馈设置http proxy不能解决,所以可按以下顺序检查一、设
- Python 中迭代器与生成器实例详解本文通过针对不同应用场景及其解决方案的方式,总结了Python中迭代器与生成器的一些相关知识,具体如下
- 本文实例讲述了Python实现的查询mysql数据库并通过邮件发送信息功能。分享给大家供大家参考,具体如下:这里使用Python查询mysq
- 解决方案1.安装django-cors-headerspip install django-cors-headers2.配置settings
- 本文实例讲述了vue使用@scroll监听滚动事件时,@scroll无效问题的解决方法。分享给大家供大家参考,具体如下:在网上看了一下vue
- 在django1.9之前,数据库同步只需要一条命令:python manage.py syncdb在djang1.9以后,数据库同步执行指令
- 简易画图工具(Python),供大家参考,具体内容如下小黑最近在努力的入门python,正好学习到了Python的tkinker模块下的Ca
- 本文实例为大家分享了python绘制散点图和折线图的具体代码,供大家参考,具体内容如下#散点图,一般和相关分析、回归分析结合使用import
- 打印在使用go写一些小程序时,我们没必要引入额外的包,直接使用fmt标准包打印即可:import "fmt"func m
- 一、官方文档介绍官网nn.Conv2d:对由多个输入平面组成的输入信号进行二维卷积二、torch.nn.Conv2d()函数详解参数详解to
- 一、介绍我想做的是基于人脸识别的表情(情绪)分析。看到网上也是有很多的开源库提供使用,为开发提供了很大的方便。我选择目前用的比较多的dlib
- 基于socket的文件传输并进行MD5值校验,供大家参考,具体内容如下文件传输分为两个类,一个是服务端,一个是客户端。客户端发起发送文件或接
- mysql select into给多个字段变量赋值在into之后顺序写上要赋值的变量就行SELECT
- 一、动机(Motivate)在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为(方法),如果直接在基类中做这样的更改,将
- 比如有文章表 Article(Id,Category,InsertDate),现在要用SQL找出每种类型中时间最新的前N个数据组成的集合,一
- 1.第一种就是直接调用 window.print()方法这种方法的坏处就是 默认打印整个页面,不能打印局部页面。2.第二种使用v-print
- 本篇博文主要讲解Python爬虫实例,重点包括爬虫技术架构,组成爬虫的关键模块:URL管理器、HTML下载器和HTML解析器。爬虫简单架构程
- 一个协程里可以启动另外一个协程,并等待它完成返回结果,采用await关键字,例子如下:import asyncioasync def out
- 常用方法浅拷贝copya = {"ilpy1": {"company": "aaa&quo