原生API编写简单富文本编辑器005
创始人
2024-03-29 10:28:22
0

原生API编写简单富文本编辑器005

系列文章快速阅读:
富文本编辑器开发系列-1-基础概念
富文本编辑器开发系列2-document.execCommand 的API
富文本编辑器开发系列3-selection
富文本编辑器开发系列4——Range对象
富文本编辑器开发系列5——浏览器Selection API探究
富文本编辑器开发系列6——Range API 探究
富文本编辑器开发系列7——textRange对象详解
富文本编辑器开发系列8——常用DOM API
原生API编写简单富文本编辑器001
原生API编写简单富文本编辑器002
原生API编写简单富文本编辑器003
原生API编写简单富文本编辑器004
原生API编写简单富文本编辑器005

经过前几节的层层递进,我们了解了一个利用原生的contenteditable 属性和 execCommand 方法来实现一个简单版的富文本编辑器,但是目前版本的编辑器虽然功能上基本实现了,但是作为一个应用或者工程,还很低级,它的部分变量和全部方法暴露在全局变量下,并且无法在同一个页面实例化多个互不干扰的编辑器。

本节我们就利用面向对象方法来对该编辑器进行封装设计。

设计分析

如果每一个编辑器是对象,那么我们所编写的程序一定是一个对象工厂,可以根据传入的配置来生成不同的编辑器实例对象,并且要将生成的编辑器实例插入指定的DOM元素中,生成方式应该类似于下面这种:

	var editor = new Editor($el, options);

目前,每一个编辑器实例至少应该包含以下部分:

  • 工具条
  • 编辑区

而为了保证工具条与编辑区不会抢夺焦点,编辑区必须位于一个框架(iframe)内.

而为了完整的封装性,我们需要保证编辑器的各个组件DOM都动态生成,所以我们不能再像前面几节一样,把编辑器的骨架HTML写在html文件中,所以,每个组件都要有自己的UI模板,需要有一个获取模板的方法。

为了更好的扩展性,我们还需要为每一个组件提供显示/隐藏方法。

工具条上的按钮又分以下几种:

  • 直接执行类,如:加粗、斜体等
  • 弹出对话框,填写后执行类: 如添加链接、图片、前景色、背景色等
  • 下拉选择类,如:字体、字号等,这些是需要传入选项数组的。

这里面,我们又可以抽象出一个新的组件:对话框,同样,它也有自己的模板方法,显示/隐藏方法。

当然,我们还需要一个事件系统,用来为组件(按钮)增加事件监听,并且在组件销毁(隐藏)时移除监听。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ak7EHSqM-1670513320945)(https://gitee.com/hjb2722404/tuchuang/raw/master/img/202211170937952.png)]

实现

编辑器类实现

class Editor {constructor($el, options) {this.id = options.id || this.createId();this.$el = $el;this.options = options;this.init();}init() {this.editorBar = new EditorBar(this.id, this.options);this.editorContent = new EditorContent(this.id, this.options);this.getTemplate();this.show();this.initEvents();}getTemplate() {const editorBarTpl = this.editorBar.getTemplate();const editorContentTpl = this.editorContent.getTemplate();return `
this.id}" editor>${editorBarTpl}${editorContentTpl}
`;}show() {document.getElementById('editor-' + this.id).display = 'block';}/*** [initEvents 初始化事件绑定]** @return {[type]} [return description]*/initEvents() {const btns = document.getElementById(this.editorBar.id).getElementsByTagName('button');let btnsArray = [];for(let i=0; ibtnsArray.push(btns[i]);}const that = this;for (let i=0; iconst btn = btnsArray[i];const command = btn.getAttribute('command');btn.onclick = function(e) {switch (command) {case 'fontColor':that.dialog = new editorDialog(that.id);that.colorPicker = new colorPicker(that.id, btn, 'foreColor');that.dialog.setCom(that.colorPicker.getTemplate());that.colorPicker.show();that.dialog.location(command);that.dialog.show();that.colorPicker.initEvents(that);break;case 'backColor':that.dialog = new editorDialog(that.id);that.colorPicker = new colorPicker(that.id, btn, 'backColor');that.dialog.setCom(that.colorPicker.getTemplate());that.colorPicker.show();that.dialog.location(command);that.dialog.show();that.colorPicker.initEvents(that);break;case 'createLink':that.dialog = new editorDialog(that.id);that.linkEditor = new linkEditor(that.id);that.dialog.setCom(that.linkEditor.getTemplate());that.linkEditor.show();that.dialog.location(command);that.dialog.show();that.linkEditor.initEvents(that);break;case 'insertImage':that.dialog = new editorDialog(that.id);that.imageEditor = new imageEditor(that.id);that.dialog.setCom(that.imageEditor.getTemplate());that.imageEditor.show();that.dialog.location(command);that.dialog.show();that.imageEditor.initEvents(that);break;default:that.editor.document.execCommand(command, 'true', '');}};}this.editorBar.events.map((event) => {let el = document.getElementById(event.id);el.addEventListener(event.type, function (e) {console.log(event.key);console.log(e.target.value);that.editor.document.execCommand(event.key, false, e.target.value);});});} }

编辑器工具条类实现

class EditorBar {constructor(id, options) {this.id = `editorBar-${id}`;this.options = options;this.init();}init() {this.getTemplate();}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}getTemplate() {const $tpl = this.createEditorBar();return `
this.id}" class="editor-toolbar">${$tpl}
`;}createEditorBar() {let $tpl ='
    ';for (key in this.options.btns) {if (commandsMap[key].options) {let id = this.id + '-' + key + 'Selector';let customStyleName = commandsMap[key].styleName;$tpl += getSelectTpl(id, commandsMap[key].options, customStyleName);} else {$tpl += `
  • `;}}$tpl += '
';return $tpl;}}

编辑器内容区类实现

class EditorContent {constructor(id, options) {this.id = `editorContent-${id}`;this.options = options;this.init();}init() {this.getTemplate();}getTemplate() {return ``;}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}}

弹窗容器类

class editorDialog {constructor(id) {this.id = 'editorDialog-' + id;this.editorId = 'editor-' + idthis.init();}getSelf() {return document.getElementById(this.id);}init() {const div = document.createElement('div');div.innerHTML = this.getTemplate();document.getElementById(this.editorId).appendChild(div);}setCom(tpl) {document.getElementById(this.id).innerHTML = tpl;}getTemplate() {return `
this.id}">
`;}show() {document.getElementById(this.id).style.display = 'block';}hide() {document.getElementById(this.id).style.display = 'none';}location(cmd) {const btns = document.getElementsByClassName(cmd += '-btn');if (btns.length) {const btn = btns[0];const style = {top: (btn.offsetTop + btn.offsetHeight + 15) + 'px',left: btn.offsetLeft + 'px',};const dialog = this.getSelf();dialog.style.top = style.top;dialog.style.left = style.left;}}}

颜色选择器类

class colorPicker {constructor(id, btn, type) {this.id = 'colorPicker-' + id;this.btn = btn;this.type = type;}getSelf() {return document.getElementById(this.id);}getTemplate() {return `this.id}" />`;}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}initEvents(editInstance) {const colorPicker = this.getSelf();const that = this;colorPicker.addEventListener("input", function(event){editInstance.editor.document.execCommand(that.type, 'false', event.target.value);editInstance.dialog.hide();}, false);colorPicker.addEventListener("change", function(event){editInstance.editor.document.execCommand(that.type, 'false', event.target.value);editInstance.dialog.hide();}, false);}}

超链接弹窗类

class linkEditor {constructor(id, btn, type) {this.id = 'linkEditor-' + id;}getSelf(id) {return document.getElementById(id || this.id);}getTemplate() {return `this.id}" />`;}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}initEvents(editInstance) {const linkEditorBtn = this.getSelf(this.id + '-okbtn');const linkEditor = this.getSelf();const that = this;linkEditorBtn.addEventListener("click", function(event){editInstance.editor.document.execCommand('createLink', 'false', linkEditor.value);editInstance.dialog.hide();}, false);}}

图片插入类

class imageEditor {constructor(id, btn, type) {this.id = 'imageEditor-' + id;}getSelf(id) {return document.getElementById(id || this.id);}getTemplate() {return `this.id}" />`;}show() {document.getElementById(this.id).display = 'block';}hide() {document.getElementById(this.id).display = 'none';}initEvents(editInstance) {const imageEditorBtn = this.getSelf(this.id + '-okbtn');const imageEditor = this.getSelf();const that = this;imageEditorBtn.addEventListener("click", function(event){editInstance.editor.document.execCommand('insertImage', 'false', imageEditor.value);editInstance.dialog.hide();}, false);}}

相关内容

热门资讯

落户政策居然考虑放开! 怎么,我能落户北京了? 大家好,我是孙少睡,这是我的第467篇楼市评论。 很多人的第一反应肯定是有没...
股市必读:ST泉为股东因涉嫌违... 截至2025年12月26日收盘,ST泉为(300716)报收于9.96元,下跌0.8%,换手率0.9...
日本公布犯罪白皮书:2024年... 日本法务省19日公布的2025年版犯罪白皮书显示,日本2024年刑事犯罪案件数量明显上升,其中性犯罪...
中央广电总台副台长王晓真,黑龙... 据央视新闻报道,12月28日,中央广播电视总台《2026年春节联欢晚会》分会场发布。黑龙江哈尔滨、浙...
聚焦全国财政工作会议丨明年财政... (央视财经《经济信息联播》)明年是“十五五”规划的开局之年,财政政策将聚焦哪些关键领域精准发力? ...
原创 中... 12月26日,中国对美国实施了一次重磅反制,针对美国政府前不久批准的111亿美元对台军售,中方决定出...
徐杰11分王少杰遭驱逐 张宁缺... [搜狐体育战报]北京时间12月28日消息,2025-26赛季CBA常规赛继续第7轮角逐。王少杰第三节...
《今日说法》主持人李晓东买茶叶... 12月28日,《今日说法》栏目主持人李晓东发布视频称,此前“被骗1000元买茶叶”事件迎来新进展:该...
3-0领先终于能休息了!莫德里... 在意甲第17轮的一场焦点战中,AC米兰迎战维罗纳。比赛进行到第70分钟时,AC米兰在3-0领先的情况...