西游记大鹏鸟扮演者:浏览器 HTML 编辑: (1) | Opera 中国
简述
Tim Berners-Lee 1990年创建的第一个浏览器支持所见即所得( WYSIWYG )方式编辑网页。Web 被设计为可读可写的媒介。然而后来的浏览器只能读取网页,只能通过表单控件输入简单的纯文本信息。
Internet Explorer 5 又重新开始支持 WYSIWYG 编辑:designMode
属性允许用户修改整个页面。然而此属性没有得到普遍使用,也许是因为它被淹没在一堆 Windows-专用的 IE 扩展中。
最近几年微软的竞争者—— Mozilla, Safari 和 Opera ——也实现了类似的编辑功能。WHATWG-工作组正致力于标准化网页编辑系统—— HTML 5
中引入了 designMode 和
contentEditable
DOM 属性。网页的 WYSIWYG 编辑功能将是未来 Web 的重要组成部分。
本文介绍在浏览器中使用 HTML 5 编辑功能的基本方法,及可能碰到的问题。包括以下内容:
- 不同的开启编辑功能的方法
- 编辑命令
- 编辑产生的 HTML 代码
- 与 DOM 交互
本文是两篇系列文章的第一篇,第二篇将详细介绍如何实现 HTML 编辑器。
注意: 我只介绍最新版本的浏览器(Opera 9.5, Firefox 2+ 和 Safari 3)支持的编辑功能,之前版本的浏览器充满了 bug 。IE 浏览器的编辑功能从 IE 5.5 开始就没有重大改进。)
编辑系统概览
编辑系统允许编辑整个网页或部分网页。其包含以下几个特点:
- 插入光标( caret )显示目前插入点。用户可通过键盘或鼠标移动光标、选择文本、输入或者删除内容。
- 有些浏览器提供用户界面以改变元素大小或位置;这些可编辑元素包括图像、表格和表单控件。
- 浏览器内建了一些标准编辑命令——
Bold
,Italic
,InsertLink
,Paste
,Undo
等。可以通过快捷键或者通过 command API 利用脚本使用这些命令。使用 command API 可以轻松的实现 HTML 编辑工具栏。 - 通过 Range 和 Selection API 可以任意修改 HTML 文件;可以用此方法实现自定义编辑命令。
- 编辑系统允许用户修改 HTML 。系统不管你如何使用修改后的 HTML 文档。比如你想把修改后的网页复制到服务器中,可通过脚本实现。
在使用编辑系统时需要注意:
- 编辑命令的定义不是很清晰,不同的浏览器生成的 HTML 有较大差异。
- IE 编辑功能自从2000年的 IE 5.5 以后就未有重大变化。其产生的 HTML 代码可能令人吃惊——你很可能在其中发现 标签!
启用编辑功能
有两种方法可在网页中创建可编辑部分 —— designMode
和 contentEditable
属性。
通过设置 document
对象的 designMode
属性为 true
,窗口或框架就变为可编辑的。(注意:在 IE 中此操作会使文档引用失效;应从 window
对象中重新获取)。通常会产生一个编辑窗口( designMode
模式的 IFrame )。
所有包含文字的元素都可以通过设置 contentEditable
为 true 变为可编辑的。( Firefox 2不支持 contentEditable
,但是 Firefox 3 和 IE, Opera 和 Safari 支持)。
键盘编辑
和其他编辑器类似,浏览器 HTML 编辑也可以使用鼠标和键盘。当文档获取 focus 后会显示插入光标,可通过鼠标或键盘移动插入光标。可通过键盘输入或删除字符。可通过键盘或鼠标移动、删除或替换选中文本。
一个很好的特性是所有的键盘编辑动作都被记录而且可以撤销。(如何使用 Undo 命令参看下文)
按下 Enter/Return 键后的处理方法比较复杂。很难定义此动作应该产生怎样的 HTML 代码,不同的上下文将产生不同的结果;不同的浏览器产生的 HTML 代码也存在很大差异。如果插入光标位于 (非空的) p
元素中,所有浏览器都应该闭合此 p
元素,插入新的(使用相同属性) 并把插入光标置于新的 p 元素。( Mozilla 会在插入光标后插入 (多余的) br
元素。)
例子如下 (在下面的例子中|符号代表插入光标位置):
bla bla|
在 IE 或 Safari 中按下回车后:
bla bla
|
如果插入光标在 (非空) h1
元素之后,所有浏览器都会关闭 h1
,但 IE 和 Opera 会插入新的 p
元素并将光标置于 p 元素中;Safari 会插入新的 h1
元素并将光标置于新元素中; Mozilla 不创建任何元素,但会在光标后插入两个 br
元素。如:
bla bla|
在 IE 或 Opera 中按下回车后:
bla bla
|
但在 Mozilla 中变为:
bla bla
|
在 Safari 中变为:
bla bla
|
如果你直接在 body
元素中输入 (而不使用其他容器元素)并点击确定,在 Mozilla 中会插入 br
元素。IE 和 Opera 会把前面的文字转换成 p
元素并插入新的 p
元素;而 Safari 将插入 div
元素。
如果在 div
元素中输入“回车”, Safari, Opera 和 IE 会关闭当前的 div
并插入新的 div
。Mozilla 会在当前 div
中插入一个 br
元素。
如果光标在嵌套的块级元素中( block level element),所有浏览器都会关闭最内层的元素并把光标置于次外层块元素中。
结论:这真复杂!令人惊奇的是 IE 居然是最好的实现,其总能保证块级元素的正确性。Mozilla 最烂,总用br
代替块元素,这样就无法为文字设置样式了。
光标位置
插入光标可在字符间移动。但是无法看到光标相对于标签的位置。所有浏览器的处理方法一致。光标相对于块级元素的处理方法:光标总是位于最内层的块元素。不能把光标置于两段之间。
如下所示,| 符号显示可能的插入点位置:
|P|1|
|P|2|
|P|3|
|P|4|
光标相对于 inline 元素处理方法:如果光标在文字左边则被认为在所有元素外;如果光标在文字右边则被认为在元素内:
|A|B|C|
所以如果你在加粗文字 range 左边输入文字,新文字不是加粗的。如果你在 range 右边添加字符,则新文字也是加粗的。
删除
如果删除段落边界,结果是一致的 —— 左边的块 “获胜” 右边的内容会被加入到左边元素中:
Overskrift
|Text
按删除后结果如下:
Overskrift|Text
Safari 浏览器的实现方法比较聪明 (或者比较糟糕,这取决于你的喜好):
Overskrift|Text
编辑 Object
浏览器提供用户界面以编辑特殊的 HTML 对象。
IE 允许改变图像、表格、表单元素大小,或通过拖拽改变元素位置 (当选中元素后,出现拖拽图标)。
Mozilla 允许改变图像和表格大小,并允许通过附加控件创建新的行和列。Mozilla 也支持改变元素位置,此功能的 UI 是专利保护的并只能用于 Mozilla 浏览器,且无法定制使用。
编辑命令 (Editing command)
不同浏览器支持不同编辑命令。这些命令产生的 HTML 代码没有被标准化,因此不同浏览器产生结果不同。如,在 IE 中“加粗” 命令生成代码为:
Hello!
而 Safari 生成代码为:
hello!
产生的代码都是比较老式的代码风格,至少在 IE 浏览器中如此。很多编辑命令会产生已遭弃用的 font
标签 (如 23
);产生的 HTML 无法通过 XHTML 验证,有时甚至不是合法的 HTML!
Opera 的 HTML 编辑命令类似于 IE,也使用 元素。Safari 通过 和内联 CSS修改文本样式。Safari 方法的优点是生成的 HTML 代码可以通过 HTML 4.01 Strict 验证。
Mozilla 支持上面两种方法——既可以和 IE/Opera 产生显示性元素,也可以如 Safari 一样产生样式属性。
如果你重视 HTML 验证,你应该在服务器端实现检查功能,以把修改后网页变为合法 (X)HTML文件(无论如何你都应该这样做,以防止 XSS-攻击)。
键盘快捷键
许多编辑命令可以通过快捷键实现:如 Ctrl/Cmd + B 设置加粗,Ctrl/Cmd + Z 是撤销命令等。但不同浏览器中有不同的快捷键。
不能重新设置快捷键,但可以通过脚本截获键盘事件,以重新定义快捷键功能。
command API
比如你想实现一个文本编辑工具栏,用户可以使用此工具栏改变文字格式。这可以通过命令 API 实现。此 API 和常见的 DOM API 不同,其实际上是允许脚本的 IOleCommandTarget
接口——此接口是微软用于同步工具栏的 COM 接口。
command API 在 Document
对象中,由 execCommand
方法和一系列以 “query” 开头的方法组成(这些方法返回 command 相关信息)。
所有的方法的第一个参数是 command ID ,即是字符串形式的 command 名称。command API 的方法如下所示。
ExecCommand
对选中元素执行 command 。有些命令设置/取消属性——如 bold
命令如果作用于已经加粗的文本,则会取消加粗。有些命令要求参数值——如 forecolor
命令需要颜色值。有些命令使用标准对话框——如 link
命令会显示对话框以允许用户输入 URL。无法自定义对话框,但是可以不用:
result = document.execCommand(command, useDialog, value)
上例详细解释:
command
: 字符串;命令名称。useDialog
: 布尔值;是否显示 built-in 对话框 (并不是所有命令都需要对话框)。value
: 命令参数值。并不是所有命令都需要参数值;如果使用了 built-in 对话框,则此值将来自对话框。result
:如果命令被执行了则返回true
;否则返回false
(如用户在对话框中选择了取消,或者命令无法执行)。
如果未选中任何内容 (只有插入光标),不同浏览器对于文本格式命令的解释不同。 如果插入光标位于单词中,IE 会将格式命令作用于整个词;而其他浏览器只作用于下一个字符。
QueryCommands
查询命令主要用于获取关于选中内容的信息。
QueryCommandEnabled
查询此命令是否能用于当前选中内容。如只有选中元素位于链接中才能使用 “删除链接” 命令。如果所选内容并非位于可编辑区域中,所有的命令都将被禁用。
QueryCommandState
查询此命令是否已经作用过选中内容。如当前选中内容为粗体,则 bold
命令的返回值为 true 。
QueryCommandValue
此函数返回值即为 execCommand
命令中使用的值,如 ForeColor
返回当前选中内容的颜色值 (字符串形式) 。
不同浏览器的返回值格式不同。如 ForeColor
在IE中返回十六进制颜色码 (如 #ff0000
),而在其他浏览器中返回 RGB 形式的颜色值,如 Rgb(255,0,0)
。
有些返回值甚至根据浏览器 locale不同而不同,如 FormatBlock
在 IE 中返回浏览器所用语言表示的段落名称。
像 bold
这样的命令没有返回值,只返回 false。(API 另外包含两个方法,queryCommandSupported
和 queryCommandIndeterminate
,但这两个函数实现普遍质量不高,不堪重用。)
Range 和 Selection API
built-in 命令对处理一些简单问题比较有用,但无法随心所欲编辑网页。通过 Range 和 Selection API 可以任意的修改 HTML ,也可以实现自定义命令。
所有通过 DOM 的网页修改都会破坏撤销栈( undo-stack ),撤销栈用于实现撤销/重做命令。这虽然很不方便,但却是为了实现自定义命令必须付出的代价。
range/selection API 有两个核心类:
Range
——网页中的字符之集。Range 可以跨多个元素。Range 有起点和终点。如果起点和终点相同,则称 range 为收缩的(collapsed)。Selection
——代表当前选中内容。selection 包含一个高亮显示的 range 。如果选中的 range 是收缩的,则显示为一个插入光标(caret)。
(Range 和 selection 只能用于可编辑区域内。可在只读文档中创建 selection 。但是只读文档中的selection 不能是收缩的,因为只读文档无法显示 caret。)
这些概念在所有浏览器中都是一样的,但是具体的 API 却不完全相同——与其他浏览器不同,IE 使用自己定义的 range 和 selection API,而其他浏览器使用的是W3C DOM Range API 和未标准化的 selection API。
最大的不同时 IE 中 range 内容是字符串型式的 HTML 标记代码。而在 W3C DOM Range API 中 Range 内容通过 DOM 树访问。
Range 示例
下面是两种不同的实现方法:
在 IE ( editWindow
即是 designMode
模式下的框架)中:
var rng = editWindow.document.selection.createRange();rng.pasteHTML("" + rng.htmlText + "
");
在 Mozilla中
var rng = editWindow.getSelection().getRangeAt(0);rng.surroundContents(document.createElement("code"));
Control 选择
IE 支持控件选择,但和 range 选择不同。点击如图形、表单控件或表格边框等对象时将被视为控件选择。
可以通过 Ctrl 键在 IE 中选择多个控件。其他浏览器中不存在控件选择概念;在这些浏览器中所有选择都是 text range 。