富文本编辑器

最后更新于:2022-04-01 23:54:04

## 富文本编辑器 **1、富文本编辑器简介** 富文本编辑(WYSIWYG(What You See Is What You GET,所见即所得))。 最先的富文本编辑,就是在页面中嵌入一个包含空HTML页面的`iframe`,然后通过设置`designMode`属性,这个空白的HTML页面就可以编辑了,编辑对象则是该页面``元素的HTML代码。 designMode属性有两个可能的值:“off”和“on”,默认为“off”。 给iframe指定一个非常简单的HTML页面作为编辑框: ``` Edit ``` 要将其设置成可编辑,不是在iframe页面内,而是它嵌入的那个页面,而且必须等待页面完全加载后才能设置`designMode`属性: ``` window.onload = function(){ window.frames['richedit'].document.designMode = 'on'; } ``` 等到上面的代码执行完,就可以使用可编辑区了。 **1.1 使用contenteditable属性** 可以给页面中的任何元素添加`contenteditable`属性,使其成为可编辑区: ```
``` 当然,也可以通过JavaScript设置: ``` var richedit = document.getElementById('richedit'); richedit.contentEditable = 'true'; ``` **1.2 获取编辑区里的HTML内容** ``` // iframe frames['richedit'].document.body.innerHTML // 设置contenteditable的元素 richedit.innerHTML ``` **2 富文本选区** 在富文本编辑器中,使用框架(iframe)的`getSelection()`方法,可以确定实际选择的文本,这个方法是window对象和document对象是属性,调用它会返回一个表示当前选择文本的Selection对象。 每个`Selection`对象可能包含一个或多个`Range`对象。 `Range` 对象表示文档的连续范围区域,简单的说就是高亮选区。一个Range的开始点和结束点位置任意,开始点和结束点也可以是一样的(空Range)。最常见的就是用户在浏览器窗口中用鼠标拖动选中的区域。 不过,不同的浏览器,`Range`对象是不一样的,在Chrome、Mozilla、Safari等主流浏览器上,Range属于`selection`对象(表示range范围),而在IE下,Range属于`TextRange`对象(表示文本范围)。 在下面的所有列子中,皆以selection对象为主,同时会加上textRange对象的兼容代码。 **2.1 拖动选择获取** 每一个浏览器窗口都有一个selection(或text Range)对象,我们可以通过`window.getSelection()`方法来获取: ``` var selectedRange; function saveSelection(){ if(window.getSelection){ /*主流的浏览器,包括chrome、Mozilla、Safari*/ return window.getSelection(); }else if(document.selection){ /*IE下的处理*/ return document.selection.createRange(); } return null; }; ``` 注意:标准dom是从window中获取selection对象,而ie是从document对象中获取。 实例(可以试试在不同浏览器下的执行,点击里面的按钮btn1):Demo **2.2 优化获取代码** 一个selection对象有可能不是只有一个Range对象,有可能有多个,每一个Range对象代表用户鼠标所选取范围内的一段连续区域。(在Firefox中,可以通过 ctrl键可以选取多个连续的区域,因此在Firefox中一个selection对象有多个range对象,在其他浏览器中,用户只能选取一段连续的区 域,因此只有一个range对象。) 如何获取某个Range对象呢?我们可以通过selection对象的getRangeAt方法来获取: ``` range = window.getSelection().getRangeAt(index) ``` `getRangeAt()`方法接受一个参数,代表该Range对象的序列号,也可以说你拖动选择的顺序号,它的值有如下几种情况: - 当用户没有按下鼠标时候,该参数的值为0. - 当用户按下鼠标的时候,该参数值为1. - 当用户使用鼠标同时按住ctrl键时选取了一个或者多个区域时候,该参数值代表用户选取区域的数量。 - 当用户取消区域的选取时,该属性值为1,代表页面上存在一个空的Range对象; 要获取Range对象,一般我们会判断是否有Range对象,我们可以通过selection对象的rangeCount属性(类似数组的length,返回Range对象的数量)来判断是否有多个Range对象,然后再去调用getRangeAt()方法。 对于富文本编辑器来说,一般情况下,我们只需要一个选择区域(Range对象),优化后的代码如下: ``` function saveSelection(){ if(window.getSelection){ /*主流的浏览器,包括chrome、Mozilla、Safari*/ var sel = window.getSelection(); if(sel.rangeCount > 0){ return sel.getRangeAt(0); } }else if(document.selection){ /*IE下的处理*/ return document.selection.createRange(); } return null; }; ``` 实例(可以试试在不同浏览器下的执行,点击里面的按钮btn2):Demo **2、Range对象的属性和方法** **2.1 创建Range对象(范围)** `document.createRange()`:用于创建一个Range对象(范围) **在IE下**: `document.body.createTextRange()`:用于创建一个textRange对象 **2.2 Range对象的属性** ``` endContainer:返回范围的结束点的Document节点,通常是文本节点。 endOffset:返回endContainer中的结束点位置。 startContainer: 返回范围的开始点中的Document节点,通常是文本节点。 startOffset:返回startContainer中的开始点位置。 collapsed:用于判断范围的开始点与结束点是否处于相同的位置,如果相同,该属性值返回true,即范围是空的或折叠的。 commonAncestorContainer:范围的开始点和结束点的(即它们的祖先节点)、嵌套最深的 Document 节点。 ``` 注意:所有属性都是只读的。如果范围中存在空格,也会计算在偏移量内。 实例(点击里面的按钮btn3):Demo **2.3 Range对象的方法** **2.3.1 范围选择** ``` selectNode(node):设置该范围的边界点,使它包含指定节点和指定节点的所有子孙节点。 selectNodeContents(node):设置该范围的边界点,使它包含指定节点的子孙节点,但不包含指定节点本身。 ``` **2.3.2 操作范围** ``` deleteContents():将Range对象中所包含的内容从页面中删除 setStart(node,index):将指定节点中的某处位置指定为Range对象所代表区域的起点位置 setEnd(node,index):将指定节点中的某处位置指定Range对象所代表区域的结束位置 setStartBefore(node):将指定节点的起点位置指定为Range对象所代表区域的起点位置。 setStartAfter():将指定节点的终点位置指定为Range对象所代表区域的起点位置。 setEndBefore():将指定节点的起点位置指定为Range对象所代表区域的终点位置。 setEndAfter(): 将指定节点的终点位置指定为Range对象所代表区域的终点位置。 cloneRange():对当前的Range对象进行复制,该方法返回一个复制的Range对象 cloneContents():复制当前Range对象所代表区域中的HTML代码并返回新的DocumentFragment对象。 extractContents():将Range对象所代表区域中的html代码克隆到一个DocumentFragment对象中,然后从页面中删除这段HTML代码 detach():释放Range对象。 insertNode(node):将指定的节点插入到某个Range对象所代表的区域中,插入位置为Range对象所代表区域的起点位置,如果该节点已经存在于页面中,该节点将被移动到Range对象代表的区域的起点处。 ``` 实例(关于setStart和setEnd,点击里面的按钮btn6):Demo 实例(关于deleteContents,点击里面的按钮btn7):Demo 实例(关于extractContents,点击里面的按钮btn8):Demo **2.3.3 其他方法** ``` collpase(boolean) 用于使范围的边界点重合。当为true时,将范围的结束点设为与开始点相同的值;当为false时,将范围的开始点设为与结束点相同的值。 compareBoundaryPoints(how,sourceRange):用来比较两个Range对象,返回1,0,-1(0表示相等,等于1时,当前范围在sourceRange之后,等于-1时,当前范围在sourceRange之前) toString():返回该范围表示的文档区域的纯文本内容。 ``` **(1)compareBoundaryPoints()** how的常量: ``` START_TO_START 用指定范围的开始点与当前范围的开始点进行比较。 START_TO_END 用指定范围的开始点与当前范围的结束点进行比较。 END_TO_END 用指定范围的结束点与当前范围的结束点进行比较。 END_TO_START 用指定范围的结束点与当前范围的开始点进行比较。 ``` **4、selection对象** selection对象可看作是Range对象的集合,包含一个或多个Range对象。 **4.1 属性** ``` anchorNode:返回范围的开始点的Document节点,和range对象的endContainer作用一样。 anchorOffset:返回startContainer中的开始点位置,和range对象的startOffset作用一样。 focusNode:返回范围的结束点的Document节点,和range对象的endContainer作用一样。 focusOffset:返回endContainer中的结束点位置,和range对象的endOffset作用一样。 isCollapsed:范围的开始点与结束点是否重叠 这是新标准中selection的属性,通过这些属性,我们省却了先获取range对象再获取偏移量和节点的繁琐。 ``` 实例(点击里面的按钮btn4):Demo **4.2 方法** ``` removeAllRanges():删除selection中原有的所有range addRange(range):将新的range添加到selection中 ``` **5、HTMLInputElement的属性方法** **5.1 在IE下Range对象** **(1)属性 ``` htmlText:返回字符串,为textRange的HTML内容,与innerHTML作用一样,只读 text:返回字符串,为textRange的文本内容,相当于innerText,可读写。 ``` **(2)方法** ``` moveStart("character",index):选定范围的开始点向后移动index个字符 moveEnd("character",index):选定范围的结束点向后移动index个字符 pasterHTML():黏贴HTML到一个文本节点时,该文本节点自动分隔。 ``` **5.2 在其他主流浏览器上** **(1)属性** ``` selectionStart:获取范围的开始点,可读写 selectionEnd:获取范围的结束点,可读写 selectionDiraction ``` **(2) 方法** ``` select():在焦点状态下,移动光标至第一个字符后面 setSelectionRange(start,end):设置范围的开始点和结束点。 注意:selectionStart和selectionEnd会记录元素最后一次selection的相关属性,意思就是当元素失去焦点后,使用selectionStart和selectionEnd仍能够获取到元素失去焦点时的值。 setSelectionRange(start,end): 如果textbox没有selection时,selectionStart和selectionEnd相等,都是焦点的位置。 在使用setSelectionRange()时 如果end小于start,会讲selectionStart和selectionEnd都设置为end; 如果start和end参数大于textbox内容的长度(textbox.value.length),start和end都会设置为value属性的长度。 ``` **3 操作富文本** 使用`document.execCommand()`方法是与富文本编辑器交互的重要方式,它可以对文档执行预定义的命令,而且可以应用大多数格式。 语法: ``` bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument) ``` 参数说明: - aCommandName:String类型,命令名称 - aShowDefaultUI:Boolean类型,默认为false,是否展示用户界面,一般设为false。Mozilla没有实现。 - aValueArgument:String类型,一些命令需要额外的参数值(比如insertHTML需要提供HTML内容),默认为null,一般不需要参数时都使用null。 命令介绍: ``` backColor (用法:document.execCommand('backColor',bfalseb,Color); ) 改变文档的背景颜色。 在styleWithCss模式,它影响的是包含元素的背景。 这个命令要求提供一个颜色值作为第三个参数 (Internet Explorer 使用这个命令设置文本背景色) bold (用法: document.execCommand('Bold','false',null); ) 对选中文本或者插入元素设置、取消粗体显示. (Internet Explorer 使用标签 而不是标签。) contentReadOnly 转化文档进入只读或者可编辑模式. 这个命令要求提供给一个boolean值给第3个参数(ie不支持)。 copy 用法:document.execCommand('copy','false',null); 把当前选中区域复制到系统剪贴板。由于启用这个功能的条件因浏览器不同而不同,所以使用此功能时,请先检查浏览器是否支持。 createLink 当有选中区域的时候,将选中区域创建为一个锚点,需要提供一个URI给第3个参数. 这个URI必须至少包含一个字符,空白字符也可。(Internet Explorer会创建一个URI为空的a标签) cut 用法:document.execCommand('cut','false',null); 剪切选中文本到剪切板. 同copy一样需要开启剪切板功能。 decreaseFontSize 给选中文本或者插入元素添加一个small标签。(Internet Explorer不支持) delete 删除当前选中区域 enableInlineTableEditing 开启或禁用表的行和列的插入删除功能 ( Internet Explorer不支持) enableObjectResizing 开启或禁用图片或者其他可调整元素大小的(resize)功能 ( Internet Explorer不支持) fontName 用法:document.execCommand('fontName','false',sFontName); 改变选中文本或者插入元素的字体。需要给第3个参数提供一个字体值(例如:"Airal") fontSize 用法:document.execCommand('fontSize','false',sSize|iSize); 改变选中文本或者插入元素的字体大小。需要给第3个参数提供一个数字 foreColor 改变选中文本或者插入元素的字体颜色。需要给第3个参数提供一个颜色值 formatBlock 添加一个HTML块式标签在包含当前选择的行,如果已经存在了,更换包含该行的块元素(在Firefox中的blockquote例外,它将包含任何包含块元素)。需要提供一个标签名称作为参数(比如:H1、P、DL、BLCOKQUOTE等)(IE仅支持标题标签H1-H6,ADDRESS和PRE,使用时还必须包含标签分隔符<>,比如

) forwardDelete 删除光标所在位置的字符,和按下删除键一样。 heading 添加一个标题标签在光标处或所选文字上。需要提供标签名称字符串作为参数(比如:H1、H6)(IE和Safari不支持) hiliterColor 更改选择或插入点的背景颜色。需要提供一个颜色值作为参数。(IE不支持) increaseFontSize 在选择或插入点周围添加一个BIG标签(IE不支持) indent 取消或缩进选择或插入点所在的行。 insertBrOnReturn 控制当按下Enter键时,是插入br标签还是把当前块元素变成两个(IE不支持) insertHorizontalRule 在插入点插入一个水平线(删除选中的部分) insertHTML 在插入点插入一个HTML字符串(删除选中部分)。需要提供一个HTML字符串作为参数(IE不支持) insertImage 在插入点插入一张图片(删除选中部分)。需要提供一个image src URI作为参数。这个URI至少包含一个字符(空字符也行)。(IE会创建一个值为null的链接) insertOrderedList 在插入点或选中文字上创建一个有序列表 insertUnorderedList 在插入点或选中文字上创建一个无序列表 insertParagraph 在选择或当前行周围插入一个段落。(IE会在插入点插入一个段落并删除选中部分) insertText 在光标插入位置插入文本内容或覆盖所选的文本内容 italic 在光标插入点开启或关闭斜体字。(IE使用标签,而不是) justifyCenter 对光标插入位置或所选内容进行文字居中 justifyFull 对光标插入位置或所选内容进行文本对齐(两端对齐) justifyLeft 对光标插入位置或所选内容进行文本左对齐 justifyRight 对光标插入位置或所选内容进行文本右对齐 outdent 对光标插入行货所选行内容减少缩进量 paste 在光标位置黏贴剪贴板的内容。(对选中内容替换) redo 重做被撤销的操作 removeFormat 对所选内容去除所有格式(比如:加粗)。 selectAll 选中编辑区里的全部内容 strikeThrough 在光标插入点开启或关闭删除线 subscript 在光标插入点开启或关闭下角标 superscript 在光标插入点开启或关闭上角标 underline 在光标插入点开启或关闭下划线 undo 撤销最近执行的命令 unlink 去除所选的锚链接的标签 useCSS 切换使用HTML tags还是CSS来生成标记。需要一个布尔值作为参数。该属性已废除,使用styleWithCSS代替。 styleWithCSS 用这个取代useCSS命令。切换使用HTML tags还是CSS来生成标记。需要一个布尔值作为参数。(false使用CSS,true使用HTML) ``` 这个方法使用于iframe类型的编辑器,也适用于将`contenteditable`设置为true的元素: ``` // iframe frames['richedit'].document.execCommand('bold', false, null); // 设置contenteditable为true的元素 document.execCommand('bold', false, null); ``` **3.1 与命令相关的方法** - queryCommandEnabled():可以用它来检测是否可以针对当前选择的文本,或者当前插入字符所在位置执行某个命令,它接受一个参数,即要检测的命令,如果单亲编辑区允许执行传入的命令,则返回true,否则返回false ``` document.queryCommandEnabled('bold') ){} ``` - queryCommandState():用于确定是否已将指定命令应用到了选择的文本。比如:判断当前选择的文本是否已将转换成了粗体: ``` isBold = document.queryCommandState('bold'); ``` - queryCommandValue():用于取得执行命名时传入的值(即document.execCommand()的第三个参数) **4、常用案例代码** **6.1 针对div(contenteditable="true")相关操作** **(1) 获取用户选择内容** ``` function saveSelection(){ if(window.getSelection){ /*主流的浏览器,包括chrome、Mozilla、Safari*/ var sel = window.getSelection(); if(sel.rangeCount > 0){ return sel.getRangeAt(0); } }else if(document.selection){ /*IE下的处理*/ return document.selection.createRange(); } return null; }; var selectedRange = saveSelection(); // 保存获取到的Range对象 ``` 注意:如果是在IE下需要获取内容,需要使用selection.text来获取。 实例(可以试试在不同浏览器下的执行,点击里面的按钮btn2):Demo **(2) 恢复光标位置** ``` function restoreSelection() { var selection = window.getSelection(); if (selectedRange) { try { selection.removeAllRanges(); /*清空所有Range对象*/ } catch (ex) { /*IE*/ document.body.createTextRange().select(); document.selection.empty(); }; /*恢复保存的范围*/ selection.addRange(selectedRange); } } ``` 实例(点击里面的按钮btn5):Demo **(3)将光标移至文本最后** ``` function selectAllText(elem){ if(window.getSelection){ elem.focus(); var range = window.getSelection(); range.selectAllChildren(elem); range.collapseToEnd(); }else if(document.selection){ var range = document.selection.createTextRange(); range.moveToElementText(elem); range.collapse(false); range.select(); /*避免产生空格*/ } } ``` **6.2 表单元素(input、textarea)相关操作** **(1) 将光标置于表单元素的最后** ``` function toTextEnd(elem){ if(window.getSelection){ elem.setSelectionRange(elem.value.length,elem.value,length); elem.focus() }else if(document.selection){ /*IE下*/ var range = elem.createTextRange(); range.moveStart('character',elem.value.length); range.collapse(true); range.select(); } } ``` 实例(点击里面的按钮btn10):Demo **(2) 选中所有文字** ``` function selectAllText(elem){ if(window.getSelection){ elem.setSelectionRange(0,elem.value.length); elem.focus(); }else if(document.selection){ var range = elem.createTextRange(); range.select(); } } ``` 实例(点击里面的按钮btn11):Demo **(3)获取光标位置** ``` function getCursorPosition(elem){ if(window.getSelection){ return elem.selectionStart; }else if(document.selection){ elem.focus(); var range = document.selection.createTextRange(); range.moveStart('character',-elem.value.length); return range.text.length; } return elem.value.length; } ``` **(4)设置光标位置** ``` function setCursorPosition(elem, position){ if(window.getSelection){ elem.focus(); elem.setSelectionRange(position,position); }else if(document.selection){ var range = elem.createTextRange(); range.collapse(true); range.moveEnd('character',position); range.moveStart('character',position); range.select(); } } ``` 一个简单的富文本编辑器:[富文本编辑器](https://github.com/IronPans/TGeditor)

';