表单脚步
JavaScript 较早的一个用途是承担一部分服务器端表单处理的责任。虽然 Web 和 JavaScript 都已经发展了很多年,但 Web 表单的变化不是很大。由于不能直接使用表单解决问题,因此开发者不得不使用 JavaScript 既做表单验证,又用于增强标准表单控件的默认行为。
表单基础
Web 表单在 HTML 中以 <form>
元素表示,在 JavaScript 中则以 HTMLFormElement 类型表示。 21 HTMLFormElement 类型继承自 HTMLElement 类型,因此拥有与其他 HTML 元素一样的默认属性。不过,HTMLFormElement 也有自己的属性和方法。
属性 | 描述 |
---|---|
acceptCharset | 服务器可以接收的字符集,等价于 HTML 的 accept-charset 属性。 |
action | 请求的 URL,等价于 HTML 的 action 属性。 |
elements | 表单中所有控件的 HTMLCollection 。 |
enctype | 请求的编码类型,等价于 HTML 的 enctype 属性。 |
length | 表单中控件的数量。 |
method | HTTP 请求的方法类型,通常是 "get" 或 "post",等价于 HTML 的 method 属性。 |
name | 表单的名字,等价于 HTML 的 name 属性。 |
reset() | 把表单字段重置为各自的默认值。 |
submit() | 提交表单。 |
target | 用于发送请求和接收响应的窗口的名字,等价于 HTML 的 target 属性。 |
提交表单
表单是通过用户点击提交按钮或图片按钮的方式提交的。提交按钮可以使用 type 属性为"submit" 的 <input>
或 <button>
元素来定义,图片按钮可以使用 type 属性为"image"的 <input>
元素来定义。
<!-- 通用提交按钮 -->
<input type="submit" value="Submit Form" />
<!-- 自定义提交按钮 -->
<button type="submit">Submit Form</button>
<!-- 图片按钮 -->
<input type="image" src="graphic.gif" />
以这种方式提交表单会在向服务器发送请求之前触发 submit 事件。这样就提供了一个验证表单数据的机会,可以根据验证结果决定是否真的要提交。阻止这个事件的默认行为可以取消提交表单。
let form = document.getElementById('myForm');
form.addEventListener('submit', event => {
// 阻止表单提交
event.preventDefault();
});
表单提交的一个最大的问题是可能会提交两次表单。如果提交表单之后没有什么反应,那么没有耐心的用户可能会多次点击提交按钮。结果是很烦人的(因为服务器要处理重复的请求),甚至可能造成损失(如果用户正在购物,则可能会多次下单)。解决这个问题主要有两种方式:在表单提交后禁用提交按钮,或者通过 onsubmit 事件处理程序取消之后的表单提交。
重置表单
用户单击重置按钮可以重置表单。重置按钮可以使用 type 属性为"reset"的 <input>
或 <button>
元素来创建。
<!-- 通用重置按钮 -->
<input type="reset" value="Reset Form" />
<!-- 自定义重置按钮 -->
<button type="reset">Reset Form</button>
表单字段
表单元素可以像页面中的其他元素一样使用原生 DOM 方法来访问。此外,所有表单元素都是表单 elements 属性(元素集合)中包含的一个值。这个 elements 集合是一个有序列表,包含对表单中所有字段的引用,包括所有 <input>
、<textarea>
、<button>
、<select>
和 <fieldset>
元素。elements 集合中的每个字段都以它们在 HTML 标记中出现的次序保存,可以通过索引位置和 name 属性来访问。 以下是几个例子:
let form = document.getElementById('form1');
// 取得表单中的第一个字段
let field1 = form.elements[0];
// 取得表单中名为"textbox1"的字段
let field2 = form.elements['textbox1'];
// 取得字段的数量
let fieldCount = form.elements.length;
如果多个表单控件使用了同一个 name,比如像单选按钮那样,则会返回包含所有同名元素的 HTMLCollection。比如,来看下面的 HTML 代码片段:
<form method="post" id="myForm">
<ul>
<li><input type="radio" name="color" value="red" />Red</li>
<li><input type="radio" name="color" value="green" />Green</li>
<li><input type="radio" name="color" value="blue" />Blue</li>
</ul>
</form>
let form = document.getElementById('myForm');
let colorFields = form.elements['color'];
console.log(colorFields.length); // 3
let firstColorField = colorFields[0];
let firstFormField = form.elements[0];
console.log(firstColorField === firstFormField);
// true
表单字段的公共属性
除 <fieldset>
元素以外,所有表单字段都有一组同样的属性。由于 <input>
类型可以表示多种表单字段,因此某些属性只适用于特定类型的字段。除此之外的属性可以在任何表单字段上使用。
属性 | 描述 |
---|---|
disabled | 布尔值,表示表单字段是否禁用。 |
form | 指针,指向表单字段所属的表单。这个属性是只读的。 |
name | 字符串,这个字段的名字。 |
readOnly | 布尔值,表示这个字段是否只读。 |
tabIndex | 数值,表示这个字段在按 Tab 键时的切换顺序。 |
type | 字符串,表示字段类型,如 "checkbox"、"radio" 等。 |
value | 要提交给服务器的字段值。对于文件输入字段来说,这个属性是只读的,仅包含计算机上某个文件的路径。 |
这里面除了 form 属性以外,JavaScript 可以动态修改任何属性。
let form = document.getElementById('myForm');
let field = form.elements[0];
field.value = 'Another value';
// 检查字段所属的表单
console.log(field.form === form); // true
// 给字段设置焦点
field.focus();
// 禁用字段
field.disabled = true;
// 改变字段的类型(不推荐,但对<input>来说是可能的)
field.type = 'checkbox';
表单字段的公共方法
每个表单字段都有两个公共方法: focus()
和 blur()
。focus()
方法把浏览器焦点设置到表单字段,这意味着该字段会变成活动字段并可以响应键盘事件。 blur()
用于从元素上移除焦点。
// 将输入框激活焦点
element.focus();
// 取消焦点
element.blur();
表单字段的公共事件
除了鼠标、键盘、变化和 HTML 事件外,所有字段还支持以下 3 个事件。
事件 | 描述 |
---|---|
blur | 在字段失去焦点时触发。 |
change | 在 <input> 和 <textarea> 元素的 value 发生变化且失去焦点时触发,或者在 <select> 元素中选中项发生变化时触发。 |
focus | 在字段获得焦点时触发。 |
blur
和 focus
事件会因为用户手动改变字段焦点或者调用 blur()
或 focus()
方法而触发。这两个事件对所有表单都会一视同仁。change 事件则不然,它会因控件不同而在不同时机触发。对于 <input>
和 <textarea>
元素,change 事件会在字段失去焦点,同时 value 自控件获得焦点后发生变化时触发。对于 <select>
元素,change
事件会在用户改变了选中项时触发,不需要控件失去焦点。
文本框编程
在 HTML 中有两种表示文本框的方式:单行使用 <input>
元素,多行使用 <textarea>
元素。这两个控件非常相似,大多数时候行为也一样。不过,它们也有非常重要的区别。
默认情况下,<input>
元素显示为文本框,省略 type 属性会以"text"作为默认值。然后可以通过size 属性指定文本框的宽度,这个宽度是以字符数来计量的。而 value 属性用于指定文本框的初始值, maxLength 属性用于指定文本框允许的最多字符数。
<input type="text" size="25" maxlength="50" value="initial value" />
<textarea>
元素总是会创建多行文本框。可以使用 rows 属性指定这个文本框的高度,以字符数计量;以 cols 属性指定以字符数计量的文本框宽度,类似于 <input>
元素的 size 属性。与 <input>
不同的是,<textarea>
的初始值必须包含在 <textarea>
和 </textarea>
之间,如下所示:
<textarea rows="25" cols="5">
initial value
</textarea>
同样与 <input>
元素不同的是,<textarea>
不能在 HTML 中指定最大允许的字符数。
除了标记中的不同,这两种类型的文本框都会在 value 属性中保存自己的内容。通过这个属性, 可以读取也可以设置文本模式的值。
选择文本
输入过滤
不同文本框经常需要保证输入特定类型或格式的数据。或许数据需要包含特定字符或必须匹配某个特定模式。由于文本框默认并未提供什么验证功能,因此必须通过 JavaScript 来实现这种输入过滤。组合使用相关事件及 DOM 能力,可以把常规的文本框转换为能够理解自己所收集数据的智能输入框。
屏蔽字符
有些输入框需要出现或不出现特定字符。例如,让用户输入手机号的文本框就不应该出现非数字字符。我们知道 keypress 事件负责向文本框插入字符,因此可以通过阻止这个事件的默认行为来屏蔽非数字字符。
textbox.addEventListener('keypress', event => {
event.preventDefault();
});
运行以上代码会让文本框变成只读,因为所有按键都被屏蔽了。如果想只屏蔽特定字符,则需要检查事件的 charCode 属性,以确定正确的回应方式。
textbox.addEventListener('keypress', event => {
if (!/\d/.test(String.fromCharCode(event.charCode))) {
event.preventDefault();
}
});
处理剪贴板
IE 是第一个支持剪贴板相关事件及通过 JavaScript 访问剪贴板数据的浏览器。IE 的实现成为了事实标准,这是因为 Safari、Chrome、Opera 和 Firefox 都实现了相同的事件和剪贴板访问机制,后来 HTML5 也增加了剪贴板事件 。以下是与剪贴板相关的 6 个事件。
事件 | 触发时机 |
---|---|
beforecopy | 复制操作发生前触发 |
copy | 复制操作发生时触发 |
beforecut | 剪切操作发生前触发 |
cut | 剪切操作发生时触发 |
beforepaste | 粘贴操作发生前触发 |
paste | 粘贴操作发生时触发 |
通过 beforecopy、beforecut 和 beforepaste 事件可以在向剪贴板发送或从中检索数据前修改数据。不过,取消这些事件并不会取消剪贴板操作。要阻止实际的剪贴板操作,必须取消 copy、cut 和 paste 事件。
剪贴板上的数据可以通过 window 对象(IE)或 event 对象(Firefox、Safari 和 Chrome)上的 clipboardData 对象来获取。在 Firefox、Safari 和 Chrome 中,为防止未经授权访问剪贴板,只能在剪贴板事件期间访问 clipboardData 对象;IE 则在任何时候都会暴露 clipboardData 对象。为了跨浏览器兼容,最好只在剪贴板事件期间使用这个对象。
clipboardData 对象上有 3 个方法:getData()
、setData()
和 clearData()
,其中 getData()
方法从剪贴板检索字符串数据,并接收一个参数,该参数是要检索的数据的格式。IE 为此规定了两个选项:"text"和"URL"。Firefox、Safari 和 Chrome 则期待 MIME 类型,不过会将"text"视为等价于 "text/plain"。
function getClipboardText(event) {
var clipboardData = event.clipboardData || window.clipboardData;
return clipboardData.getData('text');
}
function setClipboardText(event, value) {
if (event.clipboardData) {
return event.clipboardData.setData('text/plain', value);
} else if (window.clipboardData) {
return window.clipboardData.setData('text', value);
}
}
自动切换
JavaScript 可以通过 很多方式来增强表单字段的易用性。最常用的是在当前字段完成时自动切换到下一个字段。对于要收集数据的长度已知(比如电话号码)的字段是可以这样处理的。在美国,电话号码通常分为 3 个部分:区号、交换局号,外加 4 位数字。在网页中,可以通过 3 个文本框来表示这几个部分,比如:
<form>
<input type="text" name="tel1" id="txtTel1" maxlength="3" />
<input type="text" name="tel2" id="txtTel2" maxlength="3" />
<input type="text" name="tel3" id="txtTel3" maxlength="4" />
</form>
为增加这个表单的易用性并加速数据输入,可以在每个文本框输入到最大允许字符数时自动把焦点切换到下一个文本框。因此,当用户在第一个文本框中输入 3 个字符后,就把焦点移到第二个文本框,当用户在第二个文本框中输入 3 个字符后,把焦点再移到第三个文本框。这种自动切换文本框的行为可以通过如下代码实现:
function tabForward(event) {
let target = event.target;
if (target.value.length == target.maxLength) {
let form = target.form;
for (let i = 0, len = form.elements.length; i < len; i++) {
if (form.elements[i] == target) {
if (form.elements[i + 1]) {
form.elements[i + 1].focus();
}
return;
}
}
}
}
let inputIds = ['txtTel1', 'txtTel2', 'txtTel3'];
for (let id of inputIds) {
let textbox = document.getElementById(id);
textbox.addEventListener('keyup', tabForward);
}
let textbox1 = document.getElementById('txtTel1');
let textbox2 = document.getElementById('txtTel2');
let textbox3 = document.getElementById('txtTel3');
这个 tabForward()
函数是实现自动切换的关键。它通过比较用户输入文本的长度与 maxlength 属性的值来检测输入是否达到了最大长度。如果两者相等(因为浏览器会强制最大字符数,所以不可能出现多的情况),那么就要通过循环表单中的元素集合找到当前文本框,并把焦点设置到下一个元素。 这个函数接着给每一个文本框都指定了 onkeyup 事件处理程序。因为 keyup 事件会在每个新字符被插入到文本框中时触发,所以此时应该是检测文本框内容长度的最佳时机。在填写这个简单的表单时,用户不用按 Tab 键切换字段和提交表单。
HTML5 约束验证 API
HTML5 为浏览器新增了在提交表单前验证数据的能力。这些能力实现了基本的验证,即使 JavaScript 不可用或加载失败也没关系。这是因为浏览器自身会基于指定的规则进行验证,并在出错时显示适当的错误消息(无须 JavaScript)。
验证会根据某些条件应用到表单字段。可以使用 HTML 标记指定对特定字段的约束,然后浏览器会根据这些约束自动执行表单验证。
必填字段
第一个条件是给表单字段添加 required 属性,任何带有 required 属性的字段都必须有值,否则无法提交表单。这个属性适用于 <input>
、<textarea>
和 <select>
字段
<input type="text" name="username" required />
更多输入类型
HTML5 为 <input>
元素增加了几个新的 type 值。这些类型属性不仅表明了字段期待的数据类型, 而且也提供了一些默认验证,其中两个新的输入类型是已经得到广泛支持的"email"和"url",二者都有浏览器提供的自定义验证。比如:
<input type="email" name="email" /> <input type="url" name="homepage" />
数值范围
除了"email"和"url",HTML5 还定义了其他几种新的输入元素类型,它们都是期待某种数值输入的,包括:"number"、"range"、"datetime"、"datetime-local"、"date"、"month"、"week" 和"time"。
并非所有主流浏览器都支持这些类型,因此使用时要当心。
输入模式
HTML5为文本字段新增了 pattern
属性。这个属性用于指定一个正则表达式,用户输入的文本必须与之匹 配。例如,要限制只能在文本字段中输入数字,可以这样添加模式:
<input type="text" pattern="\d+" name="count" />
注意模式的开头和末尾分别假设有 ^
和 $
。这意味着输入内容必须从头到尾都严格与模式匹配。
与新增的输入类型一样,指定 pattern
属性也不会阻止用户输入无效内容。模式会应用到值,然后浏览器会知道值是否有效。通过访问 pattern
属性可以读取模式:
let pattern = document.forms[0].elements['count'].pattern;
// 使用如下代码可以检测浏览器是否支持 pattern 属性:
let isPatternSupported = 'pattern' in document.createElement('input');
检测有效性
使用 checkValidity()
方法可以检测表单中任意给定字段是否有效。这个方法在所有表单元素上都可以使用,如果字段值有效就会返回 true,否则返回 false。判断字段是否有效的依据是本节前面提到的约束条件,因此必填字段如果没有值就会被视为无效,而字段值不匹配 pattern 属性也会被视为无效。比如:
if (document.forms[0].elements[0].checkValidity()) {
// 字段有效,继续
} else {
// 字段无效
}
要检查整个表单是否有效,可以直接在表单上调用 checkValidity()
方法。这个方法会在所有字段都有效时返回 true,有一个字段无效就会返回 false。
checkValidity()
方法只会告诉我们字段是否有效,而 validity 属性会告诉我们字段为什么有 效或无效。这个属性是一个对象,包含一系列返回布尔值的属性。
属性 | 触发条件 |
---|---|
customError | 如果设置了 setCustomValidity() 就返回 true ,否则返回 false 。 |
patternMismatch | 如果字段值不匹配指定的 pattern 属性则返回 true 。 |
rangeOverflow | 如果字段值大于 max 的值则返回 true 。 |
rangeUnderflow | 如果字段值小于 min 的值则返回 true 。 |
stepMismatch | 如果字段值与 min 、max 和 step 的值不相符则返回 true 。 |
tooLong | 如果字段值的长度超过了 maxlength 属性指定的值则返回 true 。某些浏览器会自动限制字符数量,因此这个属性值始终为 false 。 |
typeMismatch | 如果字段值不是 "email" 或 "url" 要求的格式则返回 true 。 |
valid | 如果其他所有属性的值都为 false 则返回 true 。与 checkValidity() 的条件一致。 |
valueMissing | 如果字段是必填的但没有值则返回 true 。 |
因此,通过 validity 属性可以检查表单字段的有效性,从而获取更具体的信息,如下面的代码所示:
if (input.validity && !input.validity.valid) {
if (input.validity.valueMissing) {
console.log('Please specify a value.');
} else if (input.validity.typeMismatch) {
console.log('Please enter an email address.');
} else {
console.log('Value is invalid.');
}
}
禁用验证
通过指定 novalidate 属性可以禁止对表单进行任何验证:
<form method="post" action="/signup" novalidate>
<!-- 表单元素 -->
</form>
这个值也可以通过 JavaScript 属性 noValidate 检索或设置,设置为 true 表示属性存在,设置为 false 表示属性不存在:
document.forms[0].noValidate = true; // 关闭验证
选择框编程
选择框是使用 <select>
和 <option>
元素创建的。为方便交互,HTMLSelectElement 类型在所有表单字段的公共能力之外又提供了以下属性和方法。
属性/方法 | 描述 |
---|---|
add(newOption, relOption) | 在 relOption 之前向控件中添加新的 <option> 。 |
multiple | 布尔值,表示是否允许多选,等价于 HTML 的 multiple 属性。 |
options | 控 件中所有 <option> 元素的 HTMLCollection 。 |
remove(index) | 移除给定位置的选项。 |
selectedIndex | 选中项基于 0 的索引值,如果没有选中项则为 -1 。对于允许多选的列表,始终是第一个选项的索引。 |
size | 选择框中可见的行数,等价于 HTML 的 size 属性。 |
选择框的 type
属性可能是"select-one"或"select-multiple",具体取决于 multiple 属性是否存在。当前选中项根据以下规则决定选择框的 value 属性。
如果没有选中项,则选择框的值是空字符串;如果有一个选中项,且其 value 属性有值,则选择框的值就是选中项 value 属性的值。即使 value 属性的值是空字符串也是如此;如果有一个选中项,且其 value 属性没有指定值,则选择框的值是该项的文本内容;如果有多个选中项,则选择框的值根据前两条规则取得第一个选中项的值。
<select name="location" id="selLocation">
<option value="Sunnyvale, CA">Sunnyvale</option>
<option value="Los Angeles, CA">Los Angeles</option>
<option value="Mountain View, CA">Mountain View</option>
<option value="">China</option>
<option>Australia</option>
</select>
每个 <option>
元素在 DOM 中都由一个 HTMLOptionElement 对象表示。HTMLOptionElement 类型为方便数据存取添加了以下属性:
属性 | 描述 |
---|---|
index | 选项在 options 集合中的索引。 |
label | 选项的标签,等价于 HTML 的 label 属性。 |
selected | 布尔值,表示是否选中了当前选项。设置为 true 会选中当前选项。 |
text | 选项的文本。 |
value | 选项的值,等价于 HTML 的 value 属性。 |
let selectbox = document.forms[0].elements['location'];
// 推荐
let text = selectbox.options[0].text; // 选项文本
let value = selectbox.options[0].value; // 选项值
// 不推荐
let text = selectbox.options[0].firstChild.nodeValue; // 选项文本
let value = selectbox.options[0].getAttribute('value'); // 选项值
选项处理
对于只允许选择一项的选择框,获取选项最简单的方式是使用选择框的 selectedIndex
属性。
let selectedOption = selectbox.options[selectbox.selectedIndex];
对于允许多选的选择框,selectedIndex 属性就像只允许选择一项一样。设置 selectedIndex 会移除所有选项,只选择指定的项,而获取 selectedIndex 只会返回选中的第一项的索引。
选项还可以通过取得选项的引用并将其 selected 属性设置为 true 来选中。
selectbox.options[0].selected = true;
添加选项
可以使用 JavaScript 动态创建选项并将它们添加到选择框。
let newOption = document.createElement('option');
newOption.appendChild(document.createTextNode('Option text'));
newOption.setAttribute('value', 'Option value');
selectbox.appendChild(newOption);
另一种添加新选项的方式是使用选择框的 add()
方法。DOM 规定这个方法接收两个参数:要添加的新选项和要添加到其前面的参考选项。如果想在列表末尾添加选项,那么第二个参数应该是 null。如果要传入则必须是一个索引值,表示要在其前面添加新选项的选项。
let newOption = new Option('Option text', 'Option value');
selectbox.add(newOption, null);
移除选项
与添加选项类似,移除选项的方法也不止一种。第一种方式是使用 DOM 的 removeChild()
方法并传入要移除的选项。
selectbox.removeChild(selectbox.options[0]); // 移除第一项
第二种方式是使用选择框的 remove()
方法。这个方法接收一个参数,即要移除选项的索引。
selectbox.remove(0); // 移除第一项
最后一种方式是直接将选项设置为等于 null。这同样也是 DOM 之前浏览器实现的方式。
selectbox.options[0] = null; // 移除第一项
要清除选择框的所有选项,需要迭代所有选项并逐一移除它们。
移动和重排选项
在 DOM 之前,从一个选择框向另一个选择框移动 选项是非常麻烦的,要先从第一个选择框移除选项,然后以相同文本和值创建新选项,再将新选项添加到第二个选择框。DOM 方法则可以直接将某个选项从第一个选择框移动到第二个选择框,只要对相应选项使用 appendChild()
方法即可。如果给这个方法传入文档中已有的元素,则该元素会先从其父元素中移除,然后再插入指定位置。
let selectbox1 = document.getElementById('selLocations1');
let selectbox2 = document.getElementById('selLocations2');
selectbox2.appendChild(selectbox1.options[0]);
移动选项和移除选项都会导致每个选项的 index 属性重置。
重排选项非常类似,DOM 方法同样是最佳途径。要将选项移动到选择框中的特定位置, insertBefore()
方法是最合适的。不过,要把选项移动到最后,还是 appendChild()
方法比较方便。
let optionToMove = selectbox.options[1];
selectbox.insertBefore(optionToMove, selectbox.options[optionToMove.index - 1]);
表单序列化
随着 Ajax的崭露头角,表单序列化(form serialization)已经成为一个常见需求。表单在 JavaScript 中可以使用表单字段的 type 属性连同其 name 属性和 value 属性来进行序列化。在写代码之前,我们需要理解浏览器如何确定在提交表单时要把什么发送到服务器。 25
- 字段名和值是 URL 编码的并以和号(&)分隔。
- 禁用字段不会发送。
- 复选框或单选按钮只在被选中时才发送。
- 类型为"reset"或"button"的按钮不会发送。
- 多选字段的每个选中项都有一个值。
- 通过点击提交按钮提交表单时,会发送该提交按钮;否则,不会发送提交按钮。类型为"image" 的
<input>
元素视同提交按钮。 <select>
元素的值是被选中<option>
元素的 value 属性。如果<option>
元素没有 value 属性,则该值是它的文本。
富文本编辑
在网页上编写富文本内容是 Web 应用开发中很常见的需求。富文本编辑也就是所谓的“所见即所得”(WYSIWYG,What You See Is What You Get)编辑。虽然没有规范定义,但源自 IE 的一套事实标准已经被 Opera、Safari、Chrome 和 Firefox 所支持。基本的技术就是在空白 HTML 文件中嵌入一个 iframe。通过 designMode 属性,可以将这个空白文档变成可以编辑的,实际编辑的则是 <body>
元素的 HTML。designMode 属性有两个可能的值:"off"(默认值)和"on"。设置为"on"时,整个文档都会变成可以编辑的(显示插入光标),从而可以像使用文字处理程序一样编辑文本,通过键盘将文本标记为粗体、斜体,等等。
<iframe name="richedit" style="height: 100px; width: 100px"></iframe>
<script>
window.addEventListener('load', () => {
frames['richedit'].document.designMode = 'on';
});
</script>
使用 contenteditable
还有一种处理富文本的方式,也是 IE 最早实现的,即指定 contenteditable
属性。可以给页面中的任何元素指定 contenteditable
属性,然后该元素会立即被用户编辑。这种方式更受欢迎,因为不需要额外的 iframe、空页面和 JavaScript,只给元素添加一个 contenteditable
属性。
<div class="editable" id="richedit" contenteditable></div>
元素中包含的任何文本都会自动被编辑,元素本身类似于 <textarea>
元素。通过设置 contentEditable
属性,也可以随时切换元素的可编辑状态:
let div = document.getElementById('richedit');
richedit.contentEditable = 'true';
contentEditable
属性有 3 个可能的值:"true"表示开启,"false"表示关闭,"inherit"表示继承父元素的设置(因为在 contenteditable
元素内部会创建和删除元素)。IE、Firefox、Chrome、 Safari 和 Opera 及所有主流移动浏览器都支持 contentEditable 属性。
与富文本交互
与富文本编辑器交互的主要方法是使用 document.execCommand()
。这个方法在文档上执行既定的命令,可以实现大多数格式化任务。document.execCommand()
可以接收 3 个参数:要执行的命令、 表示浏览器是否为命令提供用户界面的布尔值和执行命令必需的值(如果不需要则为 null)。为跨浏览器兼容,第二个参数应该始终为 false,因为 Firefox 会在其为 true 时抛出错误。
以下是一些常用的 document.execCommand()
命令及其描述:
命令 | 描述 | 值(第三个参数) |
---|---|---|
bold | 切换选中文字的粗体样式。 | null |
italic | 切换选中文字的斜体样式。 | null |
underline | 切换选中文字的下划线样式。 | null |
strikeThrough | 切换选中文字的删除线样式。 | null |
justifyLeft | 将选中的文本左对齐。 | null |
justifyCenter | 将选中的文本居中对齐。 | null |
justifyRight | 将选中的文本右对齐。 | null |
justifyFull | 将选中的文本两端对齐。 | null |
insertOrderedList | 创建一个有序列表。 | null |
insertUnorderedList | 创建一个无序列表。 | null |
foreColor | 改变选中文本的前景色(字体颜色)。 | "#FF0000" (红色) |
hiliteColor | 改变选中文本的背景色。 | "yellow" |
insertImage | 插入图片,值为图片的 URL。 | "https://example.com/image.jpg" |
createLink | 创建超链接,值为链接的 URL。 | "https://example.com" |
unlink | 移除超链接。 | null |
insertHTML | 插入 HTML 代码。 | "<strong>Bold Text</strong>" |
formatBlock | 将选中文本格式化为指定的块级元素,如 <h1> 、<p> 等。 | "<h1>" |
removeFormat | 移除选中文本的格式。 | null |
cut | 剪切选中文本。 | null |
copy | 复制选中文本。 | null |
paste | 粘贴剪贴板中的内容。 | null (无法通过脚本控制) |
undo | 撤销上一步操作。 | null |
redo | 重做上一步操作。 | null |
selectAll | 选择所有内容。 | null |
// 在内嵌窗格中切换粗体文本样式
frames['richedit'].document.execCommand('bold', false, null);
// 在内嵌窗格中切换斜体文本样式
frames['richedit'].document.execCommand('italic', false, null);
// 在内嵌窗格中创建指向 www.example.com 的链接
frames['richedit'].document.execCommand(
'createlink',
false,
'http://www.example.com',
);
// 在内嵌窗格中为内容添加<h1>标签
frames['richedit'].document.execCommand('formatblock', false, '<h1>');
// 切换粗体文本样式
document.execCommand('bold', false, null);
// 切换斜体文本样式
document.execCommand('italic', false, null);
// 创建指向 www.example.com 的链接
document.execCommand('createlink', false, 'http://www.example.com');
// 为内容添加<h1>标签
document.execCommand('formatblock', false, '<h1>');
即使命令是所有浏览器都支持的,命令生成的 HTML 通常差别也很大。
富文件选择
在内嵌窗格中使用 getSelection()
方法,可以获得富文本编辑器的选区。这个方法暴露在 document 和 window 对象上,返回表示当前选中文本的 Selection 对象。每个 Selection 对象都拥有以下属性。
属性 | 描述 |
---|---|
anchorNode | 选区开始的节点。 |
anchorOffset | 在 anchorNode 中,从开头到选区开始跳过的字符数。 |
focusNode | 选区结束的节点。 |
focusOffset | focusNode 中包含在选区内的字符数。 |
isCollapsed | 布尔值,表示选区起点和终点是否在同一个地方。 |
rangeCount | 选区中包含的 DOM 范围数量。 |
方法 | 描述 |
---|---|
addRange(range) | 把给定的 DOM 范围添加到选区。 |
collapse(node, offset) | 将选区折叠到给定节点中给定的文本偏移处。 |
collapseToEnd() | 将选区折叠到终点。 |
collapseToStart() | 将选区折叠到起点。 |
containsNode(node) | 确定给定节点是否包含在选区中。 |
deleteFromDocument() | 从文档中删除选区文本。与执行 execCommand("delete", false, null) 命令结果相同。 |
extend(node, offset) | 通过将 focusNode 和 focusOffset 移动到指定值来扩展选区。 |
getRangeAt(index) | 返回选区中指定索引处的 DOM 范围。 |
removeAllRanges() | 从选区中移除所有 DOM 范围。这实际上会移除选区,因为选区中至少要包含一个范围。 |
removeRange(range) | 从选区中移除指定的 DOM 范围。 |
selectAllChildren(node) | 清除选区并选择给定节点的所有子节点。 |
toString() | 返回选区中的文本内容。 |
Selection 对象的这个方法极其强大,充分利用了 DOM 范围来管理选区。操纵 DOM 范围可以实现比 execCommand()
更细粒度的控制,因为可以直接对选中文本的 DOM 内容进行操作。
let selection = frames['richedit'].getSelection();
// 取得选中的文本
let selectedText = selection.toString();
// 取得表示选区的范围
let range = selection.getRangeAt(0);
// 高亮选中的文本
let span = frames['richedit'].document.createElement('span');
span.style.backgroundColor = 'yellow';
range.surroundContents(span);
getSelection()
方法在 HTML5 中进行了标准化。