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

news2024/11/15 11:35:07

原生API编写富文本编辑器004

遗留的问题:

  1. 设置的字体是使用 font属性,而非CSS
  2. 设置的字号只接受1-7, 并且是以 size 属性而非 CSS控制,超出大小无法设置。
  3. color使用HTML的input时,始终有一个input框在那里,并且如果手动触发click显示调色板,则调色板的位置无法自动跟随
  4. link 只能创建或取消,无法修改,无法指定是以何种方式打开
  5. link和image填写框聚焦时编辑器选区会被取消

设置字体字号使用的是HTML属性与标签,而非CSS

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

可以看到,在默认情况下,我们对文本的大多数操作都是使用HTML属性或标签的方式完成样式设置的。

如果想让浏览器使用CSS来设置这些样式,那么在编辑器加载前,执行styleWithCSS 命令,将设置的模式设置为css模式即可:

window.onload= function() {

 document.execCommand('styleWithCSS', false, '');
	//...

在这里插入图片描述

可以看到,这样浏览器就使用css来设置对应样式了,但又有新的问题,即字号不是我们想的按照像素设置的,而是按照浏览器定义的大小描述来设置的。

link和image填写框聚焦时编辑器选区会被取消

这个问题可以通过两种方式解决:

  1. 我们现在的可编辑区域是一个div,而我们的input框与该div同属一个文档,所以当input获得焦点时,可编辑区域就会失去焦点从而失去选区,所以我们只需要将div换成一个frame,将可编辑区放置到iframe里的文档中,这样就不会抢夺焦点了。
  2. 输入框不使用自己写的input,而是使用浏览器的prompt 框,这样也不会与div抢夺焦点。

我们后面使用第一种方式改造,第二种方式有兴趣的读者朋友可以自行尝试。

// index.html
<iframe id="editorContent" class="editor-content" contenteditable="true" frameborder="0"></iframe>
//index.css
.editor-content {

 width: 100%;

 height: 500px;

 overflow: auto;

 padding-top: 20px;

}
// index.js

var editor;

  

window.onload= function() {

 editor = document.getElementById("editorContent").contentWindow;//获取iframe Window 对象

 editor.document.designMode = 'On'; //打开设计模式

 editor.document.contentEditable = true;// 设置元素为可编辑

 editor.document.execCommand('styleWithCSS', false, '');
	

// 后续文件中所有document.execCommand 改为 editor.document.execCommand, 例如:
	
	const rs = editor.document.execCommand('fontName', true, target.value);


在这里插入图片描述

其它问题

要解决其它问题,则需要引入浏览器的另外两个API:rangeselection;

我们下一节再说。

本系列文章代码可从gitee获取

以上代码可在1.0.5 分支上找到。

代码优化

之前我们为了讲解功能实现的具体逻辑和原理,使用的是过程式编码方式,看着很不优雅,而且有很多冗余,下来我们就一步一步优化一下实现方式。

工具条动态生成

我们现在的工具条所有按钮,都是写死在html中的,每个按钮一个li标签,但是这样,一是按钮越多,代码就越多,二是不方便扩展,每次新增一个功能按钮,都要去改html模板。

我们改为使用js动态生成dom的方式来改写。

// index.js
window.onload= function() {

 createEditorBar();

// ...
	
	function createEditorBar() {

 let $tpl ='<ul>';

 const commandsMap = {

 'undo': {

 icon: 'chexiao',

 title: '撤销',

 },

 'redo': {

 icon: 'zhongzuo',

 title: '重做',

 },

 'copy': {

 icon: 'fuzhi',

 title: '复制',

 },

 'cut': {

 icon: 'jianqie',

 title: '剪切',

 },

 'fontName': {

 icon: 'ziti',

 title: '字体',

 },

 'fontSize': {

 icon: 'zihao',

 title: '字号',

 },

 'bold': {

 icon: 'zitijiacu',

 title: '加粗',

 },

 'italic': {

 icon: 'zitixieti',

 title: '斜体',

 },

 'underline': {

 icon: 'zitixiahuaxian',

 title: '下划线',

 },

 'strikeThrough': {

 icon: 'zitishanchuxian',

 title: '删除线',

 },

 'superscript': {

 icon: 'zitishangbiao',

 title: '上标',

 },

 'subscript': {

 icon: 'zitixiabiao',

 title: '下标',

 },

 'fontColor': {

 icon: 'qianjingse',

 title: '字体颜色',

 },

 'backColor': {

 icon: 'zitibeijingse',

 title: '字体背景色',

 },

 'removeFormat': {

 icon: 'qingchugeshi',

 title: '清除格式',

 },

 'insertOrderedList': {

 icon: 'youxuliebiao',

 title: '有序列表',

 },

 'insertUnorderedList': {

 icon: 'wuxuliebiao',

 title: '无序列表',

 },

 'justifyLeft': {

 icon: 'juzuoduiqi',

 title: '居左对齐',

 },

 'justifyRight': {

 icon: 'juyouduiqi',

 title: '居右对齐',

 },

 'justifyCenter': {

 icon: 'juzhongduiqi',

 title: '居中对齐',

 },

 'justifyFull': {

 icon: 'liangduanduiqi',

 title: '两端对齐',

 },

 'createLink': {

 icon: 'charulianjie',

 title: '插入链接',

 },

 'unlink': {

 icon: 'quxiaolianjie',

 title: '取消链接',

 },

 'indent': {

 icon: 'shouhangsuojin',

 title: '首行缩进',

 },

 'insertImage': {

 icon: 'tupian',

 title: '插入图片',

 },

 };

 for (key in commandsMap) {

 $tpl += `<li><button command="${key}"><i class="iconfont icon-${commandsMap[key].icon}" title="${commandsMap[key].title}"></i></button></li>`;

 }

 $tpl += '</ul>';

 const editorBar = document.getElementById('editorBar');

 editorBar.innerHTML = $tpl;

}
	
	
// index.html
<div id="editorBar" class="editor-toolbar"></div>

统一的下拉框生成方法

目前的下拉框,我们都是新生成按钮,然后再在编辑器初始化的时候动态生成将按钮替换掉的,而且每一个下拉框都有一个单独的生成方法,代码冗余比较多,我们统一使用相同方法生成下拉框的dom,并且在生成工具条的时候直接渲染。

// index.js

 const commandsMap = {
 	//...
	 'fontName': {

 icon: 'ziti',

 title: '字体',

 options: [

 {

 key: '仿宋',

 value: "'仿宋'",

 },

 {

 key: '黑体',

 value: "'黑体'",

 },

 {

 key: '楷体',

 value: "'楷体'",

 },

 {

 key: '宋体',

 value: "'宋体'",

 },

 {

 key: '微软雅黑',

 value: "'微软雅黑'",

 },

 {

 key: '新宋体',

 value: "'新宋体'",

 },

 {

 key: 'Calibri',

 value: "'Calibri'",

 },

 {

 key: 'Consolas',

 value: "'Consolas'",

 },

 {

 key: 'Droid Sans',

 value: "'Droid Sans'",

 },

 {

 key: 'Microsoft YaHei',

 value: "'Microsoft YaHei'",

 },

 ],

 styleName: 'font-family',

 },

 'fontSize': {

 icon: 'zihao',

 title: '字号',

 options: [

 {

 key: '12',

 value: '12px',

 },

 {

 key: '13',

 value: '13px',

 },

 {

 key: '16',

 value: '16px',

 },

 {

 key: '18',

 value: '18px',

 },

 {

 key: '24',

 value: '24px',

 },

 {

 key: '32',

 value: '32px',

 },

 {

 key: '48',

 value: '48px',

 },

 ],

 styleName: 'font-size',

 },
 }
 
 //...
 for (key in commandsMap) {

 if (commandsMap[key].options) {

 let id = key + 'Selector';

 let customStyleName = commandsMap[key].styleName;

 $tpl += getSelectTpl(id, commandsMap[key].options, customStyleName);

 } else {

 $tpl += `<li><button command="${key}"><i class="iconfont icon-${commandsMap[key].icon}" title="${commandsMap[key].title}"></i></button></li>`;

 }

 }

function getSelectTpl(id, options, customStyleName) {

 let $tpl= `<li><select id="${id}">`;

 for (let i = 0; i < options.length; i++) {

 $tpl += `<option value="${options[i].value}" style="${customStyleName}: ${options[i].value}">${options[i].key}</option>`;

 }

 $tpl += '</select></li>';

 return $tpl;

}


const editorBar = document.getElementById('editorBar');

    editorBar.innerHTML = $tpl;

    addSelectorEventListener('fontName');

    addSelectorEventListener('fontSize');


function addSelectorEventListener(key) {

    const $el = document.getElementById(key + 'Selector');

    $el.addEventListener('change', function(e) {

        eval('select' + key.substr(0, 1).toUpperCase() + key.substr(1) + '()');

    });

}

  

function selectFontName() {

    const target = document.getElementById('fontNameSelector');

    const rs = editor.document.execCommand('fontName', true, target.value);

}

  

function selectFontSize() {

    const valueMap = {

        '12px': 1,

        '13px': 2,

        '16px': 3,

        '18px': 4,

        '24px': 5,

        '32px': 6,

        '48px': 7,

    };

    const target = document.getElementById('fontSizeSelector');

    const value = valueMap[target.value];

    const rs = editor.document.execCommand('fontSize', true, value);

}


统一的对话框生成方法

目前输入超级链接和网络图片地址都使用了一个简单的对话框,这两部分的代码有很多重复和冗余,需要进行优化。

var dialogFun;

case 'createLink':

	showDialog(btn, 'link');

	break;

case 'insertImage':

	showDialog(btn, 'image');

	break;


function showDialog(btn, type) {

    const upperType = firstLetterToUppercase(type);

    const tpl = getDialogTpl(type);

    showDialogTpl(btn, tpl);

    const dialog = document.getElementById(type + 'Dialog');

    dialog.focus();

    const createDialogBtn = document.getElementById('create' + upperType + 'Btn');

    dialogFun = createDialog.bind(this, type);

    createDialogBtn.addEventListener('click', dialogFun, false);

}

  

function getDialogTpl(type) {

    const upperType = firstLetterToUppercase(type);

    const tpl = `

        <input type="text" id="${type}Dialog" />

        <button id="create${upperType}Btn">确定</button>

    `;

    return tpl;

}

  

function showDialogTpl(btn, tpl) {

    const $dialog = document.getElementById('editorDialog');

    $dialog.innerHTML = tpl;

    $dialog.style.top = (btn.offsetTop + btn.offsetHeight + 15) + 'px';

    $dialog.style.left = btn.offsetLeft + 'px';

    $dialog.style.display = 'block';

}

  

function createDialog(type) {

    const upperType = firstLetterToUppercase(type);

    const dialog = document.getElementById(type + 'Dialog');

    editor.document.execCommand('create' + upperType, 'false', dialog.value);

    const createDialogBtn = document.getElementById('create' + upperType + 'Btn');

    createDialogBtn.removeEventListener('click', dialogFun, false);

    hideDialog();

}

  

function firstLetterToUppercase(str) {

    return str.substr(0, 1).toUpperCase() + str.substr(1);

}

  

function hideDialog() {

    const $dialog = document.getElementById('editorDialog');

    $dialog.innerHTML = '';

    $dialog.style.display = 'none';

}

至此,我们完成了基础的代码优化,其实就是提取了一些公共方法,通过参数不同来控制不同的输出。

本系列文章代码可从gitee获取

以上代码可以在 1.0.6 分支上找到

问题

现在又有新的问题了,现在我们的所有方法都是暴露在全局环境下的,甚至还有一些全局变量,如果我们的应用中只有一个编辑器实例还好,但是如果同一个页面有两个编辑器,就会很麻烦。

所以,下一节我们将对代码进行面向对象的改造,让同一个页面可以生成多个不同的编辑器实例,各个实例之间可以互不干扰。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/73742.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Oracle项目业务表单设计:Oracle PrimaveraUnifier BP

目录 基本介绍 Basic Introduction 业务流程组件 Business Process Components 数据定义 Data Definitions 数据要素 Data Elements 状态 Status 表单 Forms 工作流程 &#xff08;可选&#xff09;Workflow 日志 Log 上部表单 Upper Form 详细表单 Detail Form 行项…

找不到msvcr110dll,无法继续执行代码,解决方法分享

找不到msvcr110dll,无法继续执行代码&#xff0c;电脑出现这种情况&#xff0c;主要是缺失了msvcr110dll这个文件。 要解决这个问题&#xff0c;其实不难&#xff0c;有多种方法 第一种解决msvcr110dll的方法 1在百度搜索下载msvcr110.dll文件 2下载后将文件放在c盘windows…

Stimulsoft Dashboards.PHP 2022.4.5 Crack

Stimulsoft Dashboards.PHP 是一个用于设计和查看仪表板的完整软件包。您可以使用该工具集成到您的应用程序中或作为独立的解决方案。同时&#xff0c;不需要复杂的配置或第三方模块。您可以轻松地将仪表板集成到几乎任何 PHP 应用程序中。 仪表板设计器是一个直接影响分析面板…

【推荐学习收藏】9种回归算法及实例总结的太详细了

我相信很多人跟我一样&#xff0c;学习机器学习和数据科学的第一个算法是线性回归&#xff0c;它简单易懂。由于其功能有限&#xff0c;它不太可能成为工作中的最佳选择。大多数情况下&#xff0c;线性回归被用作基线模型来评估和比较研究中的新方法。 在处理实际问题时&#…

Web大学生网页作业成品——游戏主题HTM5网页设计作业成品 (HTML+CSS王者荣耀8页)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

043-推箱子游戏源代码3

上一讲:042-推箱子游戏源代码2 摘要: 1、使用JAVA基础知识 2、GUI界面编程实现推箱子界面,常用控件的综合应用; 3、使用JAVA绘图技术实现推箱子过程的绘图功能; 4、使用键盘事件,通过方向键实现推箱子过程; 5、使用音频技术,实现播放背景音乐功能; 6、使用IO流技…

从西北工业大学被攻击说起,谈网络安全的最后一道防线—密码

一、背景 据央视2022年9月5日报道&#xff0c;我国西北工业大学&#xff08;以下简称西工大&#xff09;遭到美国国家情报局特定入侵办公室&#xff08;代号TAO&#xff09;非法入侵&#xff0c;目前已查明涉案人员13人&#xff0c;攻击次数一千余次&#xff0c;大量关键核心数…

保姆级微信双开教程

不知道大家是不是和我一样&#xff0c;两个微信账号&#xff0c;一个用于工作&#xff0c;一个用于私人。 一般来说&#xff0c;日常生活中使用的登录微信的设备也就3种&#xff0c;PC、Android、IOS。这三种设备中&#xff0c;Android经过各种厂商对OS的优化后&#xff0c;基本…

动态规划入门-01背包问题

动态规划入门-01背包问题 问题描述 假设你有个最大载重量为300kg300kg300kg的背包&#xff0c;有4个物品。它们的重量分别为123kg,88kg,93kg,100kg123kg,88kg,93kg,100kg123kg,88kg,93kg,100kg&#xff0c;价值分别为$$10,$19,$8,$20$。 请问背包内最大可以放入多少价值的物品…

第二证券|ChatGPT被“玩坏”,美图大涨45%,AIGC赛道风口来了?

AIGC&#xff08;人工智能主动生成内容&#xff09;近期被ChatGPT带火了&#xff01; 近来明星人工智能公司OpenAI发布了全新的谈天机器人模型ChatGPT。该模型能够主动生成代码以及绘画、答复一系列问题、承认自己的错误、质疑不正确的假设&#xff0c;乃至回绝不合理的要求&a…

Caspase-1活性分析:艾美捷FAM-FLICA试剂盒解决方案

艾美捷FAM-FLICA Caspase-1 (YVAD) Assay Kit FAM-FLICA Caspase-1 活性分析试剂盒检测方案&#xff1a; 1、凋亡诱导&#xff1a; 在开始实验之前&#xff0c;确定可重复的方法用于通过触发胱天蛋白酶活性获得阳性对照。此过程随每个细胞系而显著变化。例如&#xff0c;细胞…

JUC并发编程第九篇,原子操作类分类解析,LongAdder为什么这么快原理分析?

JUC并发编程第九篇&#xff0c;原子操作类分类解析&#xff0c;LongAdder为什么这么快原理分析&#xff1f;一、基本类型原子类二、数组类型原子类三、引用类型原子类四、对象的属性修改原子类五、原子操作增强类六、原理分析&#xff0c;LongAdder 为什么这么快&#xff1f;位…

JS获取音频的总时长,解决Audio元素duration为NaN || Infinity 问题

当我们在加载一个线上mp3地址或者获取audio的duration的时候&#xff0c;会发现有拿到duration是Infinity的情况&#xff0c;这时如果我们动态的展示录音时间时候就会有问题。首先明确一下这是chrome浏览器自己的存在的一个bug&#xff0c;因为我们拿到的录音数据流没有定义长度…

商务与经济统计 | 推断统计

一.概率 事件 若干样本点的集合 事件的概率 等于事件中所有的样本点概率之和 条件概率 贝叶斯定理 二.离散型概率分布 随机变量 是一次试验的结果的数值性描述 离散型随机变量 指的是有穷个数值或一系列无穷的数值的随机变量 连续型随机变量 代表某一区间或多个区间…

通配符的应用

我们使用通配符描述切入点&#xff0c;主要的目的就是简化之前的配置&#xff0c;具体都有哪些通配符可以使用? *:单个独立的任意符号&#xff0c;可以独立出现&#xff0c;也可以作为前缀或者后缀的匹配符出现 execution&#xff08;public * com.itheima.*.UserService.find…

webpack基础配置教程

文章目录1.初识Webpack2.开启项目3.处理js和json文件webpack小试牛刀webpack打包js/json文件webpack默认不能处理css4.webpack配置文件1.初识Webpack 什么是webpack? Webpack是一个模块打包器&#xff08;意思同构建工具&#xff0c;所谓构建︰将程序员写完的【源代码】&#…

Sentinel服务熔断功能

Sentinel服务熔断功能 sentinel整合ribbonopenFeignfallback 1、环境搭建&#xff08;新建模块&#xff09; 1.1、启动nacos和sentinel 1.2、新建服务提供者cloudalibaba-provider-payment9003/9004模块 1、引入pom.xml文件 <?xml version"1.0" encoding&quo…

21.前端笔记-CSS-字体图标

1、字体图标产生 使用场景&#xff1a;用于显示网页中通用的小图标iconfont 为什么不用精灵图&#xff1a; &#xff08;1&#xff09;图片文件还是比较大的 &#xff08;2&#xff09;图片本身放大或缩小会失真 &#xff08;3&#xff09;一旦图片制作完毕想要更换&#xf…

ThingsBoard 3.1.1版本在window本地运行之设备直连(二)

目录 前言 1、Thingsboard 框架 2、MQTT设置 1.MQTT概念 2.MQTT在TB里担任的角色 3.MQTT配置 3、结果 前言 ThingsBoard是一个物联网管理平台&#xff0c;这个平台可以让其他企业入驻进来&#xff0c;这些入驻的企业或者个人就是租户&#xff08;tenant&#xff09;&#…

入门系列 - Git工作流程

Git工作流程 Git的工作流程一般如下&#xff1a; 克隆 Git 资源作为工作目录。在克隆的资源上添加或修改文件。如果其他人修改了&#xff0c;你可以更新资源。在提交前查看修改。提交修改。在修改完成后&#xff0c;如果发现错误&#xff0c;可以撤回提交并再次修改并提交。 …