1. 程式人生 > >【JavaScript】富文本編輯器

【JavaScript】富文本編輯器

bject repl active last rri file data 樣式 current

這是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" />‘; } /*list_smilies.forEach(function(v) { html += ‘<img src="images/emotion/‘ + v + ‘.png" class="emotion" width="20" height="20" />‘; });*/ 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" />‘; 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(var row = 1; row < 15; ++row) // should be ‘16‘ - but last line looks so dark { var rows = document.createElement(‘tr‘); /**/ for(var col = 0; col < 25; ++col) // last column is grayscale { var color; if(col == 24) {/*使最後一列變灰,且隨著行數的增加顏色越來越深*/ var gray = Math.floor(255 / 13 * (14 - row)).toString(16); /*(255/13)*13從255變到0*/ var hexg = (gray.length < 2 ? ‘0‘ : ‘‘) + gray; /*gray的值在不斷往上疊加*/ color = ‘#‘ + hexg + hexg + hexg; } else { var hue = col / 24; var saturation = row <= 8 ? row / 8 : 1; var value = row > 8 ? (16 - row) / 8 : 1; color = HSVtoRGB(hue, saturation, value); } var td = document.createElement(‘td‘); /**/ td.setAttribute(‘title‘, color); td.style.cursor = ‘url(di.ico),crosshair‘; td.setAttribute(‘unselectable‘, ‘on‘); td.style.backgroundColor = color; td.width = 12; td.height = 12; rows.appendChild(td); } table.appendChild(rows); }; var box = document.createElement(‘div‘); box.appendChild(table); return box.innerHTML; }; this.clickEvent = function() { var tds = document.getElementById(‘color-board‘); tds = tds.childNodes[0].getElementsByTagName(‘td‘); /*每個節點都有一個childNodes屬性,其中保存著NodeList對象*/ for(var i = 0; i < tds.length; i++) {/*childNodes[0]是tbody,找到裏面所有的td,且tbody是創建table後自動添加的*/ addEvent(tds[i], ‘click‘, function() { var color = this.getAttribute(‘title‘); r.execCommand(command, color); r.closeModal(); }, false); } } }; r.init(); return r; };

【JavaScript】富文本編輯器