代码改变世界

javascript简易编辑器

  BlueDream  阅读(2336)  评论(1编辑  收藏  举报

程序原理

文本编辑器具有的基本功能就是对编辑区选中的文字通过工具栏提供的功能对文字或选区进行操作.比如文字加粗,斜体,创建表格,插入链接和图片等一系列的操作.然后可以通过源码查看查看编辑后产生的源代码.

可以先通过Rich-Text_Editing大致了解下实现富文本编辑器的主要技术.这些功能就是我们代码设计的主线.

编辑区

我们需要一个iframe.并将其designMode属性设置为"on".这个iframe就具备了编辑功能.也是我们实现编辑器的基本容器.动态创建一个iframe代码如下

复制代码
createIframe: function() {
    
var _this = this;
    
this.ifr = co.append(this.container, 'iframe', {'frameborder'0'style''border:0; vertical-align:bottom''class''econtent' });
    
this.doc =  this.ifr.contentDocument || this.ifr.contentWindow.document; // W3C || IE
    this.doc.designMode = 'on';
    
this.doc.open();
    
// margin为了消除iframe中的html上部的空白
    this.doc.write('<html><head><style>body{ margin:3px; word-wrap:break-word; word-break: break-all; }</style></head><body>GoodNessEditor</body></html>');
    
this.ifr.contentWindow.focus();
    
this.doc.close();
    
// 当iframe失去焦点.偷偷将代码存入textare中
    co.addEvent(this.ifr.contentWindow, 'blur'function() {
        _this.txtarea.value 
= _this.doc.body.innerHTML;
    });
},
复制代码

 

这里我们创建了一个空白的iframe并将其document文档的designMode设置为"on".然后通过doc.write添加自定义的body和样式可以方便的设置编辑区的样式.然后记得要将焦点落到iframe中,焦点设置很重要,是为我们离开编辑区后获取源码做好准备.

这里有三个知识点:

1. 获取iframe的document文档.在W3C标准中用contentDocument(IE8开始支持.但IE6,7不支持),在IE6,7下用contentWindow.document获取.

2. 我们的焦点需要落在contentWindow即iframe的窗体中.

3. 我们绑定contentWindow的blur事件.当失去焦点的时候.就将代码存到textarea中.

    注意: (1) 这里绑定contentWindow事件.不可以用contentWindow.onblur的形式.因为firefox不支持.只能用addEventListener方法去绑定.

            (2) 将代码放入到textArea的原因就是利用了textArea可以显示HTML原生代码的这个优势.而使得源代码查看这个功能变得轻而易举.

命令工具栏

命令工具栏是我们发布命令的集结地.首先我们需要创建这个工具栏.为了让用户体验更好.避免让命令图片间断的出现.这里用css sprite技术将图片合并.通过position来控制图片显示.具体创建方法请参考源代码createToolBar方法.这里主要讲下命令的实现方式.比如我们点击字体加粗按钮.字体加粗是怎么实现的.

这里主要运用的技术就是execCommand命令函数

复制代码
execCommand(String aCommandName, Boolean aShowDefaultUI, String aValueArgument)

Arguments
    String aCommandName
        the name of the command

    Boolean aShowDefaultUI
        whether the 
default user interface should be shown. This is not implemented in Mozilla.

    String aValueArgument
        some commands (such as insertimage) require an extra value argument (the image
's url). Pass an argument of null if no argument is needed.
复制代码

这个方法提供了3个参数.中间的参数由于Mozilla没有实现我们就默认为false. 剩余两个参数就是我们实现命令的整个过程了.这些命令有的需要1个参数有的需要两个参数.我篇首给的链接里对于各个命令参数需求写的很清楚.比如我们实现字体加粗只需要ifrDoc.execCommand('bold' ,false, null)即可.

源码里我用静态变量存储了需要一个参数的命令:

复制代码
editor.NO_ARG_COMMAND = {
    BOLD: 
'bold',                                      // 加粗
    ITALIC: 'italic',                                  // 斜体 
    UNDERLINE: 'underline',                            // 下划线
    CUT: 'cut',                                        // 剪切            
    COPY: 'copy',                                      // 复制
    JUSTIFYLEFT: 'justifyleft',                        // 靠左
    JUSTIFYRIGHT: 'justifyright',                      // 靠右
    JUSTIFYCENTER: 'justifycenter',                    // 居中
    INSERTUNORDEREDLIST: 'insertunorderedlist',        // 项目符号
    INSERTORDEREDLIST: 'insertorderedlist',            // 编号
    OUTDENT: 'outdent',                                // 减小缩进
    INDENT: 'indent',                                  // 增加缩进
    REMOVEFORMAT: 'removeformat'                       // 清除格式
};
复制代码

 除了这些剩下的几个方法就需要两个参数了,也是相对比较麻烦的几个功能.

插入链接.插入图片.插入表情.字体变色.插入表格.这几个功能首先都是使用了pop弹窗的形式实现的.

pop弹窗

首先取得所点击的图标的绝对位置

复制代码
// 获取元素绝对位置
co.getPos = function(o) {
    
for(var _pos = {x: 0, y: 0}; o; o = o.offsetParent) {
        _pos.x 
+= o.offsetLeft;
        _pos.y 
+= o.offsetTop;
    }
    
return _pos;
};
复制代码

然后将pop窗口的left和top定位到获取的x, y值即可

// 定位弹窗 
fixPop: function(fwin, tar) {
    co.setProperties(fwin, {
'style''top:' + (co.getPos(tar).y + tar.offsetHeight) + 'px; left:' + co.getPos(tar).x + 'px' });
},

单例工厂

由于我们每次点击弹窗的时候都会动态创建一个pop窗口.那么点击多少次就会创建多少个窗口.这样就会加大页面体积.最后导致崩溃.这里我们需要提供一个单例工厂模式.保证指定id窗口实例一经创建.就不需再次创建而重复利用.

复制代码
var newFactory = function() {
    
var coll = {};
    
return {
        create: 
function(fn, cfg, content/* POP_Body*/) {
            
if(coll[cfg.id]) {
                
return coll[cfg.id];
            } 
else {
                
var win = fn(cfg, content); 
                coll[cfg.id] 
= win;
                
return win;
            }
        }
    }
}();
复制代码

隐藏弹窗

隐藏弹窗是当我们点击弹窗以外的元素位置就将该窗口隐藏.所以我们需要监听iframe和页面document的onclick事件.但由于事件冒泡机制.当你点击POP窗口的时候也会冒泡至document而导致弹窗无法输入.所以要在pop窗口点击事件的时候禁止冒泡. 

var t = co.target(e);
if(title == '插入链接' || title == '插入图片' || title == '插入表格') { co.cancelBubble(e); } // 插入链接和图片禁止冒泡

清除选择

在IE下比如选择颜色以及点击图标等会导致selection丢失.所以要记住在相应的元素上设置unselectable = "on".

事件委托

在我们为工具栏的每个图片绑定事件以及为我们的颜色拾取控件的每个表格绑定事件的时候.如果循环所有的控件然后每个空间依次绑定事件.那么就会非常耗费资源.这里要得益于JS所具有的事件委托技术.这样只需要绑定父元素然后通过target去获取就可以了.

颜色拾取器

简易的颜色拾取器.就是通过['00', '33', '66', '99', 'CC', 'FF']6个色值循环组合.6 x 6的色盘. 具体细节可以参考源码.

插入表格

插入表格在firefox下提供了insertHTML这个命令用于复杂的HTML插入.但IE下却不支持.这个就为我们向指定光标处插入HTML增加了困难.因为你需要记住光标的位置.然后再将生成的HTML插入到光标的位置.那么怎么才能记住光标的位置呢.这里有个getBookmark方法

Saves the current TextRange object into a string (bookmark) that can be used for the moveToBookmark method to return to the original TextRange object.

用这个就可以保存我们选择的光标位置.然后当插入HTML的时候用moveToBookmark移动到当前光标位置进行插入即可

复制代码
saveBookMark: function() {
    
var _this = this;
    co.addEvent(_this.ifr, 
'beforedeactivate'function() {
        
var rng = _this.doc.selection.createRange();
        
if(rng.getBookmark) {
            _this.bookmark 
= _this.doc.selection.createRange().getBookmark(); // 保存光标用selection下的createRange();
        }
    });
    co.addEvent(_this.ifr, 
'activate'function() {
        
if(_this.bookmark) {
            
// Moves the start and end points of the current TextRange object to the positions represented by the specified bookmark.
            // 将光标移动到 TextRange 所以需要用 body.createTextRange();
            var rng = _this.doc.body.createTextRange();                
            rng.moveToBookmark(_this.bookmark);
            rng.select();
            _this.bookmark 
= null;
        }
    });
},
   
复制代码

这里用了beforedeactivate和activate方法来检测焦点的离开,一旦检测到离开就将当前光标选区存入到Bookmark中.用这个事件原因就是只有在光标移到外面的文档我们记录才有意义.具体见司徒正美的教程.

取到了光标位置.我们就可以运用IE下的pasteHTML方法.将HTML插入到光标处.

_this.doc.selection.createRange().pasteHTML(_html);

这样就完成了表格插入的操作.

源码显示区

上面的步骤都做完.编辑器的基本功能就快完成了.最后我们需要一个显示源码的功能.上面分析已经说过textArea与生俱来就具备显示HTML原生代码的条件.而且我们上面也实现了当iframe失去焦点的时候悄悄的将代码存入到了textArea中了.那么我们在这里只需要做iframe和textArea的切换和互相赋值就可以了.

这里还有一点就是Chrome和Safari浏览器的textArea是可以拖拽的.这里可以通过css的resize:none禁止其resize.

创建源码显示栏以及源码显示

复制代码
createToolFoot: function() {
    
var _this = this;
    co.append(
this.container, 'div''efoot').innerHTML = '<input type="checkbox" id="showCode" /><label for="showCode">显示源码</label>';
    
// 绑定显示源码事件
    co.getId('showCode').onclick = function() { 
        
if(this.checked) {
            _this.layer.style.display 
= 'block';
            co.getId(
'bgcode').style.display = 'block';
            _this.ifr.style.display 
= 'none';
        } 
else {
            _this.layer.style.display 
= 'none';
            co.getId(
'bgcode').style.display = 'none';
            _this.doc.body.innerHTML 
= co.getId('bgcode').value;
            _this.ifr.style.display 
= 'block';                
        }
    };
},
复制代码

源码下载

Editor

编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述
点击右上角即可分享
微信分享提示