【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(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;
    };

 

posted @ 2017-06-13 19:13  乘客  阅读(1007)  评论(0编辑  收藏  举报