JavaScript富文本编辑器
Posted 乘客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaScript富文本编辑器相关的知识,希望对你有一定的参考价值。
这是js写的富文本编辑器,还存在一些bug,但基本功能已经实现,通过这个练习,巩固了js富文本编辑方面的知识,里面包含颜色选择器、全屏、表情、上传图片等功能,每个功能实际对应的就是一个小插件啦
部分程序:
var RichEditor = function(container, params) { params = params || {}; var options = { width: 900, height: 500, borderColor: "#ddd", buttons: { heading: { title: "标题", icon: "\\uf1dc", click: function() { var h = [\'h1\', \'h2\', \'h3\', \'h4\', \'h5\', \'h6\']; r.closeModal(); var html = \'<div class="editor-heading">\'; h.forEach(function(h) { html += \'<\' + h + \' data-h="\' + h + \'">\' + h + \'</\' + h + \'>\'; }); html += \'</div>\'; function HClick() { var h = document.querySelector(\'.editor-heading\'); h = h.childNodes; /*console.log(\'h\',h);*/ /*修改,迭代Nodelist最好使用length属性初始化第二个变量,避免无限循环*/ for(var i=0, len=h.length; i < len; i++){ addEvent(h[i], \'click\', function() { var h = this.getAttribute(\'data-h\'); r.execCommand(\'formatBlock\', \'<\' + h + \'>\'); /*formatBlock使用指定的HTML标签来格式化选择的文本块*/ r.closeModal(); }, false); } /*h.forEach(function(v) { addEvent(v, \'click\', function() { var h = this.getAttribute(\'data-h\'); r.execCommand(\'formatBlock\', \'<\' + h + \'>\'); r.closeModal(); }, false); });*/ }; r.openModal.call(this, html, HClick); } }, code: { title: "引用", icon: "\\uf10d", click: function() { var html=\'<blockquote class="editor-block"><p><br></p></blockquote>\'; r.execCommand(\'insertHTML\',html); var p=document.createElement(\'p\'); p.innerHTML=\'<br>\'; et.appendChild(p); } }, bold: { title: "加粗", icon: "\\uf032", click: function() { r.execCommand(\'bold\'); } }, italic: { title: "斜体", icon: "\\uf033", click: function() { r.execCommand(\'italic\'); } }, underline: { title: "下划线", icon: "\\uf0cd", click: function() { r.execCommand(\'underline\'); } }, strikethrough: { title: "删除线", icon: "\\uf0cc", click: function() { r.execCommand(\'strikethrough\'); } }, foreColor: { title: "字体颜色", icon: "\\uf1fc", click: function() { var color = new r.colorPicker(\'foreColor\'); r.openModal.call(this, color.addColorBoard(), color.clickEvent); } }, backColor: { title: "背景色", icon: "\\uf043", click: function() { var color = new r.colorPicker(\'hiliteColor\'); r.openModal.call(this, color.addColorBoard(), color.clickEvent); } }, justifyLeft: { title: "居左", icon: "\\uf036", click: function() { r.execCommand(\'justifyLeft\'); } }, justifyCenter: { title: "居中", icon: "\\uf037", click: function() { r.execCommand(\'justifyCenter\'); } }, justifyRight: { title: "居右", icon: "\\uf038", click: function() { r.execCommand(\'justifyRight\'); } }, justifyFull: { title: "两端对齐", icon: "\\uf039", click: function() { r.execCommand(\'justifyFull\'); } }, insertOrderedList: { title: "有序列表", icon: "\\uf0cb", click: function() { r.execCommand(\'insertOrderedList\'); } }, insertUnorderedList: { title: "无序列表", icon: "\\uf0ca", click: function() { r.execCommand(\'insertUnorderedList\'); } }, indent:{ title:"indent", icon:"\\uf03c", click:function(){ r.execCommand(\'indent\'); } }, outdent:{ title:"outdent", icon:"\\uf03b", click:function(){ r.execCommand(\'outdent\'); } }, createLink: { title: "链接", icon: "\\uf0c1", click: function() { r.closeModal(); var html = \'<input type="text" placeholder="www.example.com" class="editor-link-input"/> <button type="button" class="editor-confirm">确认</button>\'; function btnClick() { var confirm = document.querySelector(\'.editor-confirm\'); addEvent(confirm, \'click\', function() { var link = document.querySelector(\'.editor-link-input\'); if(link.value.trim() != \'\') { /*获取字符串副本*/ var a = \'<a href="\' + link.value + \'" target="_blank">\' + link.value + \'</a>\'; r.execCommand(\'insertHTML\', a); r.closeModal(); }; }, false); }; r.openModal.call(this, html, btnClick); } }, insertImage: { title: "插入图片", icon: "\\uf03e", click: function() { r.closeModal(); var html = \'<div class="editor-file">图片上传<input type="file" name="photo" accept="image/*" class="editor-file-input"/></div>\'; /*指定MIME类型为图像,以便在load事件中把它保存为数据URL*/ r.openModal.call(this, html, r.fileInput); } }, emotion: { title: "表情", icon: "\\uf118", click: function() { r.closeModal(); r.drawEmotion.call(this); } }, fullscreen: { title: "全屏", icon: "\\uf066", click: function() { r.toggleFullScreen(); } }, save: { title: "保存", icon: "\\uf0c7" } } }; var selectedRange = null; var originParams = {}; var et = null; var toolbarTop = null; for(var param in params) { if(typeof params[param] === \'object\' && params[param] != null) { originParams[param] = {}; for(var deepParam in params[param]) { originParams[param][deepParam] = params[param][deepParam]; }; } else { originParams[param] = params[param]; } }; for(var def in options) { /*遍历options,看传进来的params有没有options中的属性,有就覆盖*/ if(typeof params[def] === \'object\') { for(var deepDef in options[def]) { if(typeof params[def][deepDef] === "object") { for(var ddDef in options[def][deepDef]) { if(typeof params[def][deepDef][ddDef] === \'undefined\') { params[def][deepDef][ddDef] = options[def][deepDef][ddDef]; } }; } else if(def !== "buttons") { params[def][deepDef] = options[def][deepDef]; } }; } else if(typeof params[def] === \'undefined\') { params[def] = options[def]; } }; //添加addEventlistener事件 var addEvent = function(element, type, handler, useCapture) {/*判断使用1级DOM还是2级DOM并兼容IE*/ if(element.addEventListener) { element.addEventListener(type, handler, useCapture ? true : false); } else if(element.attachEvent) { element.attachEvent(\'on\' + type, handler); } else if(element != window){ element[\'on\' + type] = handler; } }; var removeEvent = function(element, type, handler, useCapture) { /*移除事件监听*/ if(element.removeEventListener) { element.removeEventListener(type, handler, useCapture ? true : false); } else if(element.detachEvent) { element.detachEvent(\'on\' + type, handler); } else if(element != window){ element[\'on\' + type] = null; } }; // http://www.cristinawithout.com/content/function-trigger-events-javascript /*没用*/ var fireEvent = function(element, type, bubbles, cancelable) { if(document.createEvent) { var event = document.createEvent(\'Event\'); event.initEvent(type, bubbles !== undefined ? bubbles : true, cancelable !== undefined ? cancelable : false); element.dispatchEvent(event); } else if(document.createEventObject) { //IE var event = document.createEventObject(); element.fireEvent(\'on\' + type, event); } else if(typeof(element[\'on\' + type]) == \'function\'){ element[\'on\' + type](); } }; // prevent default var cancelEvent = function(e) { if(e.preventDefault){ e.preventDefault(); } else{ e.returnValue = false; } if(e.stopPropagation){ e.stopPropagation(); } else{ e.cancelBubble = true; } return false; }; var r = this; r.params = params; r.originalParams = originParams; r.drawTool = function(toolbarTop) {/*添加工具栏中的选项*/ var buttons = r.params.buttons; for(var btn in buttons) { var btnA = document.createElement("a"); btnA.className = "re-toolbar-icon"; btnA.setAttribute("title", buttons[btn]["title"]); btnA.setAttribute("data-edit", btn); btnA.innerHTML = buttons[btn]["icon"]; toolbarTop.appendChild(btnA); }; }; /*表情*/ r.drawEmotion = function() { var list_smilies = [\'smile\', \'smiley\', \'yum\', \'relieved\', \'blush\', \'anguished\', \'worried\', \'sweat\', \'unamused\', \'sweat_smile\', \'sunglasses\', \'wink\', \'relaxed\', \'scream\', \'pensive\', \'persevere\', \'mask\', \'no_mouth\', \'kissing_closed_eyes\', \'kissing_heart\', \'hushed\', \'heart_eyes\', \'grin\', \'frowning\', \'flushed\', \'fearful\', \'dizzy_face\', \'disappointed_relieved\', \'cry\', \'confounded\', \'cold_sweat\', \'angry\', \'anguished\', \'broken_heart\', \'beetle\', \'good\', \'no\', \'beer\', \'beers\', \'birthday\', \'bow\', \'bomb\', \'coffee\', \'cocktail\', \'gun\', \'metal\', \'moon\' ]; var html = \'\'; for(var i=0,len=list_smilies.length;i<len;i++){ html += \'<img src="images/emotion/\' + list_smilies[i] + \'.png" class="emotion" width="20" height="20" alt="" />\'; } /*list_smilies.forEach(function(v) { html += \'<img src="images/emotion/\' + v + \'.png" class="emotion" width="20" height="20" alt="" />\'; });*/ r.openModal.call(this, html); function add() { /*必须有服务器才能显示*/ console.log(\'this.src\',this.src); var img = \'<img src="\' + this.src + \'" class="emotion" width="20" height="20" alt="" />\'; document.execCommand(\'insertHTML\', true, img); r.closeModal(); }; var emotion = document.querySelectorAll(\'.emotion\'); for(var i=0,len=emotion.length;i<len;i++){ addEvent(emotion[i], \'click\', add, false); } /*emotion.forEach(function(e) { addEvent(e, \'click\', add, false); });*/ }; /*全屏*/ r.toggleFullScreen = function() { if(!document.fullscreenElement && !document.mozFullScreenElement && !document.webkitFullscreenElement) { var docElm = document.documentElement; if(docElm.requestFullscreen) { docElm.requestFullscreen(); } else if(docElm.mozRequestFullScreen) { docElm.mozRequestFullScreen(); } else if(docElm.webkitRequestFullScreen) { docElm.webkitRequestFullScreen(); } else if(elem.msRequestFullscreen) { elem.msRequestFullscreen(); }; } else {/*已经开启全屏*/ if(document.exitFullscreen) { document.exitFullscreen(); } else if(document.mozCancelFullScreen) { document.mozCancelFullScreen(); } else if(document.webkitCancelFullScreen) { document.webkitCancelFullScreen(); } else if(document.msExitFullscreen) { document.msExitFullscreen(); } }; }; r.execCommand = function(command, param) { r.selections.restoreSelection(); et.focus(); if(!arguments[1]) { param = null; }; document.execCommand(command, false, param); }; r.selections = { getCurrentRange: function() {/*获取选中的范围*/ //获取当前range if(window.getSelection) { //使用 window.getSelection() 方法获取鼠标划取部分的起始位置和结束位置 var sel = window.getSelection(); if(sel.rangeCount > 0){ //通过selection对象的getRangeAt方法来获取selection对象的某个Range对象 return sel.getRangeAt(0); } } else if(document.selection) {/*如果没有window.getSelection*/ var sel = document.selection; return sel.createRange(); } return null; }, saveSelection: function() { selectedRange = r.selections.getCurrentRange(); }, restoreSelection: function() { //当你点击了工具条时,当前焦点也就改变了,所以我们需要恢复前一个焦点位置 var selection = window.getSelection(); /*获得selection对象*/ if(selectedRange) { try { selection.removeAllRanges(); /*移除选区*/ } catch(ex) { document.body.createTextRange().select(); document.selection.empty(); }; selection.addRange(selectedRange); /*将指定的DOM范围添加到选区*/ } }, getSelectionHTML: function() { if(window.getSelection) { var sel = window.getSelection(); if(sel.rangeCount > 0) { return sel; } } } }; /*没用*/ var getSelectionRect = function() { if(window.getSelection) { var sel = window.getSelection(); if(!sel.rangeCount) { return false; } var range = sel.getRangeAt(0).cloneRange(); } }; /*上传文件*/ r.fileInput = function() { var fi = document.querySelector(\'.editor-file-input\'); function change(e) { var files = e.target.files; var file = null; var url = null; /*var reader=new FileReader(); if(files && files.length > 0){ reader.readAsDataURL(files[0]); console.log(\'reader.result\',reader.result); var img = \'<img src="\' + reader.result + \'"/>\'; document.execCommand(\'insertHTML\', false, img); }*/ if(files && files.length > 0) { file = files[0]; try { var fileReader = new FileReader(); fileReader.onload = function(e) { url = e.target.result; console.log(\'url\',url); var img = \'<img src="\' + url + \'"/>\'; document.execCommand(\'insertHTML\', false, img); /*document.execCommand(\'insertimage\', false, url);*/ } fileReader.readAsDataURL(file); } catch(e) { } } r.closeModal(); }; fi.onchange = change; }; r.toolClick = function() { var toolbtn = document.querySelectorAll(\'a[data-edit]\'); /*匹配CSS选择符,含All找所有*/ for(var i = 0; i < toolbtn.length; i++) { addEvent(toolbtn[i], "click", function(e) { var btn = r.params.buttons; var name = this.getAttribute("data-edit"); if(typeof btn[name]["click"] !== \'undefined\') { /*每个工具栏选项都含有一个click属性*/ r.selections.restoreSelection(); /*重置为上个range*/ btn[name].click.call(this); /*使用call方法扩充函数,调用btn[name].click函数*/ r.selections.saveSelection(); } else { } e.stopPropagation(); }, false); /*冒泡阶段被调用*/ } }; r.getStyle = function(dom, attr) {/*getComputedStyle()方法,返回一个对象,其中包含当前元素的所有计算的样式; IE不支持getComputedStyle()方法,在IE中每个具有style属性的元素还有一个currentStyle属性, 它包含当前元素全部计算后的样式*/ var value = dom.currentStyle ? dom.currentStyle[attr] : getComputedStyle(dom, false)[attr]; return parseFloat(value); }; r.openModal = function(html, fn) { /*打开模态框*/ r.modal = document.createElement(\'div\'); r.modal.className = \'editor-modal\'; r.modal.innerHTML = html; /*每个模态框内容不同*/ r.parent.appendChild(r.modal); var left = this.offsetLeft + (r.getStyle(this, \'width\') - r.getStyle(r.modal, \'width\')) / 2; /*按钮的宽度,模态框的宽度*/ left < 0 ? left = 3 : \'\'; r.modal.style.left = left + \'px\'; if(fn) { fn(); } }; r.closeModal = function() { /*关闭模态框*/ if(r.modal != null) { r.parent.removeChild(r.modal); r.modal = null; } }; r.isInModal = function(e) { if(r.modal != null) { var node = e.target; /*点的谁就是谁*/ var isIn = false; var modal = document.querySelector(\'.editor-modal\'); while(typeof node !== \'undefined\' && node.nodeName != \'#document\') { if(node === modal) { /*判断点击的是不是模态框*/ isIn = true; break; } node = node.parentNode; }; if(!isIn) { /*点的模态框之外的范围,则关闭*/ r.closeModal(); } } }; r.init = function() { r.parent = document.getElementById(container.replace("#", "")); /*替换掉#号*/ var defaultValue = r.parent.innerHTML; r.parent.innerHTML = \'\'; r.parent.className += " re-container"; /*前面有空格*/ r.parent.style.boxSizing = "border-box"; r.parent.style.border = "1px solid " + r.params.borderColor; r.parent.style.width = r.params.width + "px"; r.parent.style.height = r.params.height + "px"; et = document.createElement("div"); et.className = "re-editor"; /*位于工具栏下的文本编辑框*/ et.setAttribute("tabindex", 1); et.setAttribute("contenteditable", true);/*contenteditable 属性的出现,让我们可以将任何元素设置成可编辑状态。*/ et.setAttribute(\'spellcheck\', false); et.innerHTML = defaultValue; /*将默认HTML写进该编辑框*/ toolbarTop = document.createElement("div"); toolbarTop.className = "re-toolbar re-toolbar-top"; /*工具栏*/ toolbarTop.style.backgroundColor = r.params.toolBg; r.parent.appendChild(toolbarTop); r.parent.appendChild(et); r.drawTool(toolbarTop); r.toolClick(); addEvent(window, \'click\', r.isInModal, false); addEvent(et, "keyup", function(e) {/*键盘鼠标up获取选区*/ r.selections.saveSelection(); }, false); addEvent(et, "mouseup", function(e) { r.selections.saveSelection(); }, false); var addActiveClass = function() { this.parentNode.classList.add(\'active\'); }; var removeActiveClass = function() { this.parentNode.classList.remove(\'active\'); }; addEvent(et, "focus", addActiveClass); addEvent(et, "blur", removeActiveClass); var topHeight = document.querySelector(".re-toolbar-top").offsetHeight; /*offsetHeight元素的高度*/ et.style.height = (r.params.height - topHeight) + "px"; }; /*颜色选择*/ r.colorPicker = function(command) { var HSVtoRGB = function(h, s, v) { var r, g, b, i, f, p, q, t; i = Math.floor(h * 6); f = h * 6 - i; p = v * (1 - s); q = v * (1 - f * s); t = v * (1 - (1 - f) * s); switch(i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } var hr = Math.floor(r * 255).toString(16); /*转换为16进制*/ var hg = Math.floor(g * 255).toString(16); var hb = Math.floor(b * 255).toString(16); return \'#\' + (hr.length < 2 ? \'0\' : \'\') + hr + /*转换为字符串*/ (hg.length < 2 ? \'0\' : \'\') + hg + (hb.length < 2 ? \'0\' : \'\') + hb; }; this.addColorBoard = function() { var table = document.createElement(\'table\'); table.setAttribute(\'cellpadding\', 0); table.setAttribute(\'cellspacing\', 0); table.setAttribute(\'unselectable\', \'on\'); table.style.border = \'1px solid #d9d9d9\'; table.setAttribute(\'id\', \'color-board\'); for(深入理解javascript中的富文本编辑