來源司徒正美javascript
難點在於 ie的光標位置修復, 須要用到markbook 保存快照css
<!doctype html> <html dir="ltr" lang="zh-CN"> <head> <meta charset="utf-8"/> <meta http-equiv="X-UA-Compatible" content="IE=Edge"> <style type="text/css"> #textarea {width:600px;height:300px;background:#F2F1D7;} </style> <script type="text/javascript"> var addSheet = function(){ var doc,cssCode; if(arguments.length == 1){ doc = document; cssCode = arguments[0]; }else if(arguments.length == 2){ doc = arguments[0]; cssCode = arguments[1]; }else{ alert("addSheet函數最多接受兩個參數!"); } var headElement = doc.getElementsByTagName("head")[0]; var styleElements = headElement.getElementsByTagName("style"); if(styleElements.length == 0){/*若是不存在style元素則建立*/ if(!+"\v1"){ //ie doc.createStyleSheet(); }else{ var tempStyleElement = doc.createElement('style');//w3c tempStyleElement.setAttribute("type", "text/css"); headElement.appendChild(tempStyleElement); } } var styleElement = styleElements[0]; var media = styleElement.getAttribute("media"); if(media != null && !/screen/.test(media.toLowerCase()) ){ styleElement.setAttribute("media","screen"); } if(!+"\v1"){ //ie styleElement.styleSheet.cssText += cssCode; }else if(/a/[-1]=='a'){ styleElement.innerHTML += cssCode;//火狐支持直接innerHTML添加樣式表字串 }else{ styleElement.appendChild(doc.createTextNode(cssCode)) } } var Class = { create: function() { return function() { this.initialize.apply(this, arguments); } } } var extend = function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; } var RichTextEditor = Class.create();//咱們的富文本編輯器類 RichTextEditor.prototype = { initialize:function(options){ this.setOptions(options); this.drawEditor(this.options.textarea_id); }, setOptions:function(options){ this.options = { //這裏集中設置默認屬性 id:'jeditor_'+ new Date().getTime(), textarea_id:null//用於textarea的ID,也就是咱們的必選項 } extend(this.options, options || {});//這裏是用來重寫默認屬性 }, ID:function(id){return document.getElementById(id) },//getElementById的快捷方式 TN:function(tn){ return document.getElementsByTagName(tn) },//getElementsByTagName的快捷方式 CE:function(s){ return document.createElement(s)},//createElement的快捷方式 hide:function(el){el.style.display = 'none';}, show:function(el){el.style.display = 'block';}, fontPickerHtml:function(type,array){ var builder = []; for(var i = 0,l = array.length;i<l;i++){ builder.push('<a unselectable="on" style="'); if(type == 'fontname'){ builder.push('font-family'); builder.push(':'); builder.push(array[i]); /*呈現一行(一行就是一種字體)*/ builder.push(';" href="javascript:void(0)">'); builder.push(array[i]); }else if(type == 'fontsize'){ /*呈現一行(一行就是一種字號)*/ builder.push('font-size'); builder.push(':'); builder.push(array[i][1]); builder.push(';" sizevalue="'); builder.push(array[i][0]); builder.push('" href="javascript:void(0)">'); builder.push(array[i][2]); } builder.push("</a>"); } return builder.join(''); }, iconsHtml : function(){ var builder = []; var j = 0; var _drawRow = function(builder,i){ builder.push('<tr>'); for(var i=0;i<6;i++){ j++; _drawCell(builder,j); } builder.push('</tr>'); } var _drawCell = function(builder,j){ var url = 'http://images.cnblogs.com/cnblogs_com/rubylouvre/202906/o_face'+j+'.gif'; builder.push('<td style="background:url('+url+') center center no-repeat; width:21px;height:21px;"'); builder.push(' url="'+url+'"> </td>') } builder.push('<table border=1>'); for(var i=0 ;i<6;i++){ _drawRow(builder,i); } builder.push('</table>'); return builder.join('') }, tableHtml: function(){ var _drawInput = function(builder, name, value){ builder.push('<input id="'); builder.push(name); builder.push('" value="'); builder.push(value); builder.push('" />'); }; var builder = []; builder.push('<table>'); // 標題 builder.push('<tr><td colspan="2" style="padding:2px" bgcolor="#D0E8FC">'); builder.push('插入表格'); builder.push('</td></tr>'); // 行數 builder.push('<tr><td>行數</td><td>'); _drawInput(builder, 'rows', 3); builder.push('</td></tr>'); // 列數 builder.push('<tr><td>列數</td><td>'); _drawInput(builder, 'cols', 5); builder.push('</td></tr>'); // 寬度 builder.push('<tr><td>寬度</td><td>'); _drawInput(builder, 'width', 300); builder.push('</td></tr>'); // 提交 builder.push('<tr><td colspan="2" style="padding-top:6px;">'); builder.push('<input type="button" id="rte_submit" value="提交" unselectable="on" />'); builder.push('<input type="button" id="rte_cancel" value="取消" unselectable="on" />'); builder.push('</td></tr>'); builder.push('</table>'); return builder.join(''); }, createTable: function(rows, cols, width){ var builder = []; builder.push('<table border="1" width="'); builder.push(width); builder.push('">'); for(var r = 0; r < rows; r++){ builder.push('<tr>'); for(var c = 0; c < cols; c++){ builder.push('<td> </td>'); } builder.push('</tr>'); } builder.push('</table>'); return builder.join(''); }, //用於生成顏色選擇器的具體內容 colorPickerHtml : function(){ var _hex = ['FF', 'CC', '99', '66', '33', '00']; var builder = []; // 呈現一個顏色格 var _drawCell = function(builder, red, green, blue){ builder.push('<td bgcolor="'); builder.push('#' + red + green + blue); builder.push('" unselectable="on"></td>'); }; // 呈現一行顏色 var _drawRow = function(builder, red, blue){ builder.push('<tr>'); for (var i = 0; i < 6; ++i) { _drawCell(builder, red, _hex[i], blue) } builder.push('</tr>'); }; // 呈現六個顏色區之一 var _drawTable = function(builder, blue){ builder.push('<table class="cell" unselectable="on">'); for (var i = 0; i < 6; ++i) { _drawRow(builder, _hex[i], blue) } builder.push('</table>'); }; //開始建立 builder.push('<table><tr>'); for (var i = 0; i < 3; ++i) { builder.push('<td>'); _drawTable(builder, _hex[i]); builder.push('</td>'); } builder.push('</tr><tr>'); for (var i = 3; i < 6; ++i) { builder.push('<td>'); _drawTable(builder, _hex[i]) builder.push('</td>'); } builder.push('</tr></table>'); builder.push('<table id="color_result"><tr><td id="color_view"></td><td id="color_code"></td></tr></table>'); return builder.join(''); }, addEvent:function(el, type, fn ) { if(!+"\v1") { el['e'+type+fn]=fn; el.attachEvent( 'on'+type, function() { el['e'+type+fn](); } ); }else{ el.addEventListener( type, fn, false ); } }, drawEditor:function(id){ var $ = this, textarea = this.ID(id), toolbar = this.CE('div'), br = this.CE('br'),//用於清除浮動 iframe = this.CE('iframe'); $.hide(textarea); textarea.parentNode.insertBefore(toolbar,textarea); textarea.parentNode.insertBefore(br,textarea); textarea.parentNode.insertBefore(iframe,textarea); br.style.cssText = "clear:both"; toolbar.setAttribute("id","RTE_toolbar"); iframe.setAttribute("id","RTE_iframe"); iframe.frameBorder=0; var iframeDocument = iframe.contentDocument || iframe.contentWindow.document; iframeDocument.designMode = "on"; iframeDocument.open(); iframeDocument.write('<html><head><style type="text/css">body{ font-family:arial; font-size:13px;background:#DDF3FF;border:0; }</style></head></html>'); iframeDocument.close(); var buttons = {//工具欄的按鈕集合 'fontname':['字體',-120,-40,86,20], 'fontsize':['文字大小',-220,-40,86,20], 'removeformat':['還原',-580,0,20,20], 'bold':[ '粗體',0,0,20,20], 'italic':[ '斜體',-60,0,20,20], 'underline': ['下劃線',-140,0,20,20], 'strikethrough':['刪除線',-120,0,20,20], 'justifyleft': ['居左', -460,0,20,20], 'justifycenter':[ '居中',-420,0,20,20], 'justifyright':['居右',-480,0,20,20], 'justifyfull':['兩端對齊',-440,0,20,20], 'indent':['縮進',-400,0,20,20], 'outdent':['懸掛',-540,0,20,20], 'forecolor':['前景色',-720,0,20,20], 'backcolor':['背景色',-760,0,20,20], 'createlink':['超級鏈接',-500,0,20,20], 'insertimage':['插入圖片',-380,0,20,20], 'insertorderedlist':['有序列表',-80,0,20,20], 'insertunorderedlist':['無序列表',-20,0,20,20], 'html':['查看',-260,0,20,20], 'table':['表格',-580,-20,20,20], 'emoticons':['表情',-60,-20,20,20] }; var fontFamilies = ['宋體','經典中圓簡','微軟雅黑', '黑體', '楷體', '隸書', '幼圓', 'Arial', 'Arial Narrow', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia', 'New Roman Times', 'Verdana'] var fontSizes= [[1, 'xx-small', '特小'], [2, 'x-small', '很小'], [3, 'small', '小'], [4, 'medium', '中'], [5, 'large', '大'], [6, 'x-large', '很大'], [7, 'xx-large', '特大']]; var buttonClone = $.CE("a"), fragment = document.createDocumentFragment(); buttonClone.className = 'button'; for (var i in buttons){/*添加命令按鈕的名字,樣式*/ var button = buttonClone.cloneNode("true"); if(i == 'backcolor'){/*特殊處理背景色按鈕*/ if (!+"\v1"){ button.setAttribute("title","background") }else{ button.setAttribute("title","hilitecolor") } } button.style.cssText = "background-position: "+buttons[i][1]+"px "+buttons[i][2]+"px; width: "+buttons[i][3]+"px;height: "+buttons[i][4]+"px;" button.setAttribute("title",buttons[i][0]); button.setAttribute("command",i);/*把execCommand的命令參數放到自定義屬性command中*/ // button.innerHTML = buttons[i]; button.setAttribute("unselectable", "on");/*防止焦點轉移到點擊的元素上,從而保證文本的選中狀態*/ toolbar[i] = button; /*★★★★把元素放進一個數組,用於下一個循環綁定事件!★★★★*/ fragment.appendChild(button); } toolbar.appendChild(fragment); $.addEvent(toolbar, 'click', function(){ var e = arguments[0] || window.event, target = e.srcElement ? e.srcElement : e.target, command = target.getAttribute("command"); switch (command){ case 'createlink': case 'insertimage': var value = prompt('請輸入網址:', 'http://'); _format(command,value); break; case 'fontname'://這六個特殊處理,不直接執行fontEdit命令! case 'fontsize': case 'forecolor': case 'backcolor': case 'html': case 'table': case 'emoticons': return; default://其餘執行fontEdit(cmd, null)命令 _format(command,''); break; } }); /******************************************************************/ var popup = $.CE('div'); toolbar.appendChild(popup); /******************************************************************/ $.addEvent(toolbar['fontname'], 'click', function(){ popup.innerHTML = $.fontPickerHtml('fontname',fontFamilies); bind_select_event(this,popup,"fontpicker"); }); /******************************************************************/ $.addEvent(toolbar['fontsize'], 'click', function(){ popup.innerHTML = $.fontPickerHtml('fontsize',fontSizes); bind_select_event(this,popup,"fontpicker"); }); /******************************************************************/ $.addEvent(toolbar['emoticons'],'click',function(){ popup.innerHTML = $.iconsHtml(); bind_select_event(this,popup, 'iconinsertor'); }); $.addEvent(toolbar['table'],'click',function(){ popup.innerHTML = $.tableHtml(); bind_select_event(this,popup, 'tablecreator'); }); /******************************************************************/ $.addEvent(toolbar['forecolor'],'click',function(){ popup.innerHTML = $.colorPickerHtml(); bind_select_event(this,popup,"colorpicker"); }); $.addEvent(toolbar['backcolor'],'click',function(){ popup.innerHTML = $.colorPickerHtml(); bind_select_event(this,popup,"colorpicker"); }); /******************************************************************/ $.addEvent(popup,'click',function(){ var e = arguments[0] || window.event, element = e.srcElement ? e.srcElement : e.target, command = this.getAttribute("title"), id = this.getAttribute("id"), tag = element.nodeName.toLowerCase(); switch (id){ case "fontpicker": if(tag == 'a'){ var value; if('fontsize' == command){ value = element.getAttribute('sizevalue'); }else{ value = element.innerHTML; } _format(command,value); $.hide(this); } break; case "colorpicker": if(tag == 'td'){ var value = element.bgColor; _format(command,value); $.hide(this); } break; case "tablecreator": var submit = $.ID('rte_submit'), cancel = $.ID('rte_cancel'), rows = $.ID('rows').value, cols = $.ID('cols').value, width = $.ID('width').value; if(element==cancel) { $.hide(this); }else if(element==submit){ var html = $.createTable(rows, cols, width); _insertHTML(html); $.hide(this); } break; case "iconinsertor": if(tag == 'td'){ var url = element.getAttribute("url");/*★★★取出url★★★*/ _insertHTML("<img src='"+url+"'/>"); $.hide(this); } break; } }); $.addEvent(popup,'mouseover',function(){ var id = this.getAttribute("id"); if(id == "colorpicker"){ var e = arguments[0] || window.event, element = e.srcElement ? e.srcElement : e.target, tag = element.nodeName.toLowerCase(), colorView = $.ID('color_view'), colorCode = $.ID('color_code'); if( 'td' == tag){ colorView.style.backgroundColor = element.bgColor; colorCode.innerHTML = element.bgColor; } } }); /********切換回代碼界面********************************************/ var _doHTML = function() { $.hide(iframe); $.show(textarea); textarea.value = iframeDocument.body.innerHTML; textarea.focus(); }; /********切換回富文本編輯器界面*************************************/ var _doRich = function() { $.show(iframe); $.hide(textarea); iframeDocument.body.innerHTML = textarea.value; iframe.contentWindow.focus(); }; /********切換編輯模式的開關*******************************************/ var switchEditMode = true; $.addEvent(toolbar['html'], 'click', function(){ if(switchEditMode){ _doHTML(); switchEditMode = false; }else{ _doRich(); switchEditMode = true; } }); var _insertHTML = function(html){ iframe.contentWindow.focus(); if(!+"\v1"){ /****這裏須要解決IE丟失光標位置的問題,詳見核心代碼四**************/ iframeDocument.selection.createRange().pasteHTML(html); }else{ var selection = iframe.contentWindow.getSelection(); var range; if (selection) { range = selection.getRangeAt(0); }else { range = iframeDocument.createRange(); } var oFragment = range.createContextualFragment(html), oLastNode = oFragment.lastChild ; range.insertNode(oFragment) ; range.setEndAfter(oLastNode ) ; range.setStartAfter(oLastNode ); selection.removeAllRanges();//清除選擇 selection.addRange(range); } } /*******************核心代碼之一******************************************/ /********************處理富文本編輯器的格式化命令**************************/ var _format = function(x,y){ try{ iframeDocument.execCommand(x,false,y); iframe.contentWindow.focus(); }catch(e){} } /***********核心代碼之二*************************************************/ /***********隱藏與顯示彈出層**********************************************/ var bind_select_event = function(button,picker,id){ button.style.position = 'relative'; var command = button.getAttribute("command"); if('backcolor' == command){ command = !+"\v1" ? 'backcolor':'hilitecolor'; } picker.setAttribute("id",id); picker.setAttribute("title",command);//轉移命令 if( picker.style.display=='' ||picker.style.display == 'none'){ $.show(picker); picker.style.left = button.offsetLeft + 'px'; picker.style.top = (button.clientHeight + button.offsetTop)+ 'px'; }else{ $.hide(picker); } } /*******************核心代碼之三******************************************/ /**********************獲取iframe的內容************************************/ $.addEvent(iframe.contentWindow,"blur",function(){ textarea.value = iframeDocument.body.innerHTML; }); /*******************核心代碼之四******************************************/ /*當光標離開iframe再進入時默認放在body的第1個節點上了,因此要記錄光標的位置***/ if(!+"\v1"){ var bookmark; //記錄IE的編輯光標 $.addEvent(iframe,"beforedeactivate",function(){//在文檔失去焦點以前 var range = iframeDocument.selection.createRange(); bookmark = range.getBookmark(); }); //恢復IE的編輯光標 $.addEvent(iframe,"activate",function(){ if(bookmark){ var range = iframeDocument.body.createTextRange(); range.moveToBookmark(bookmark); range.select(); bookmark = null; } }); } /****************************************************************/ addSheet('\ #RTE_iframe{width:600px;height:300px;}\ #RTE_toolbar{float:left;width:600px;background:#D5F3F4;}\ #RTE_toolbar select{float:left;height:20px;width:60px;margin-right:5px;}\ #RTE_toolbar .button{display:block;float:left; text-decoration:none;border:1px solid;\ border-color:#ccc #f3f8fc #f3f8fc #ccc;margin:2px 2px 5px;background-image:\ url(http://images.cnblogs.com/cnblogs_com/rubylouvre/202906/o_tinymce.gif); }\ #RTE_toolbar .button:hover{color:#fff;border-color:#fff #aaa #aaa #fff;}\ div#fontpicker{display:none;height:150px;width:150px;overflow:auto;position:absolute;\ border:2px solid #c3c9cf;background:#F1FAFA;}\ div#fontpicker a{display:block;text-decoration:none;color:#000;background:#F1FAFA;padding:2px;}\ div#fontpicker a:hover{color:#999;background:#e3e6e9;}\ div#colorpicker {display:none;position:absolute;width:216px;border:2px solid #c3c9cf;}\ div#colorpicker table{border-collapse:collapse;margin:0;padding:0;}\ div#colorpicker .cell td{height:12px;width:12px;}\ #color_result{width:216px;}\ #color_view{width:110px;height:25px;}\ div#tablecreator{display:none;width:176px;position:absolute;border:2px solid #c3c9cf;padding:1px;}\ div#tablecreator table{border:1px solid #69f;line-height:12px;font-size:12px;border-collapse:collapse;width:100%;}\ div#tablecreator td{font-size:12px;color:#777;text-align:center;}\ #rte_submit,#rte_cancel{font-size:12px;color:#777;border:1px solid #777;background:#f4f4f4;margin:5px 3px;}\ #rows, #cols, #width{width:80px;height:14px;line-height:12px;font-size:12px;border:1px solid #69f;background:#F1FAFA;}\ div#iconinsertor {display:none;position:absolute;width:150px;height:150px;background:#F1FAFA;}'); } } window.onload = function(){ new RichTextEditor({ id:'editor', textarea_id:'textarea' }); } </script> <title>富文本編輯器</title> </head> <body> <form action="#"> <textarea id="textarea" wrap="on"></textarea> </form> </body> </html>