zoukankan      html  css  js  c++  java
  • 【JavaScript】富文本编辑器

    这是js写的富文本编辑器,还存在一些bug,但基本功能已经实现,通过这个练习,巩固了js富文本编辑方面的知识,里面包含颜色选择器、全屏、表情、上传图片等功能,每个功能实际对应的就是一个小插件啦

    部分程序:

        var RichEditor = function(container, params) {
            params = params || {};
            var options = {
                 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;
        };
  • 相关阅读:
    《ML模型超参数调节:网格搜索、随机搜索与贝叶斯优化》
    《黎曼几何与流形学习》
    《信息几何优化,随机优化, 与进化策略》
    生产订单加反作废按钮
    生产订单新增按钮没权限
    生产订单备注字段锁定
    审核后提交物料附件
    MRP设置自动执行
    CRM系统数据授权
    复制物料时不复制安全库存
  • 原文地址:https://www.cnblogs.com/yujihang/p/7003019.html
Copyright © 2011-2022 走看看