富文本编辑器初探

长期以来,作为用户我是富文本编辑器的使用者,作为前端开发,我也只是富文本插件的使用者,对内部实现细节不甚了解,使用上也只停留在调用插件提供的API,实现一些业务逻辑。最近的项目,需要开发一个简易富文本编辑器,也算是让我有机会对其一窥究竟。

可编辑富文本的方式

我们知道form表单中的input、textarea之类标签是支持内容可编辑的,但并不支持富文本,如果在这些标签里粘贴带格式的内容,会被去格式,只保留文本内容。如果想设置可编辑富文本,有两种方式:

  • 嵌入空页面的iframe,并设置designMode属性值为“on”,这样整个文档就变得可以编辑。
<iframename="richtext" src="blank.html"></iframe>window.addEventListener("load"function (){frames("richtext").document.designMode = "on"
});
复制代码

需要在嵌入页面加载之后,动态设置iframe文档的designMode属性。

  • 使用contenteditable属性

该属性最早是由IE实现,且可以作用于页面中的任何标签,只需要在文档里给标签设置以上属性即可,无需嵌入iframe、设置js属性,所以这种方式也是目前富文本编辑器插件中更多采用的方式;

 <div class="editbox" id="richtext" contenteditable><p></p><p contenteditable="false"></p>   </>
复制代码

这样,此div元素中包含的内容就可以编辑了,当然也可以设置子元素(如第二个P元素)为不可编辑。通过js设置元素的该属性,也可以改变编辑模式:

var elm = document.getElementById('richtext');
elm.contentEditable = 'true';复制代码

contenteditable属性有三个可能的值:'true'表示打开编辑模式,'false'表示关闭,'inherit'表示从父元素继承此属性值。contenteditable属性兼容性较好,在主流浏览器包括IE以及目前大部分的移动端浏览器上,都得到支持。

操作富文本

常见的富文本编辑器插件,如wangEditor、百度的UEditor,都有各种丰富的菜单区域来设置编辑内容及格式,如常规的设置标题、文字加粗、超链接等,更胜者插入图片、视频及自定义的内容结构等,而实现这些功能的API就是document.execCommand(),这个方法是与富文本编辑器进行交互的主要方式。

语法:

bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
复制代码
  • 返回值:布尔型,false表示操作不支持或未被启用
  • aCommandName,命令名称,如“bold”
  • aShowDefaultUI,是否为该命令提供用户界面,一般设为false,主流浏览器没实现该功能
  • aValueArgument,某些命令的额外参数(insertImage命令需要提供插入的图片的url)

所有支持的commands,可查阅MDN;其中,与剪贴板相关的命令(copy、cut、paste)各浏览器实现差异较大,使用时需关注浏览器差异。

常用的命令举例:

// 加粗
document.execCommand('bold', false, null);
// 超链接
document.execCommand('createlink', false, 'https://www.kaola.com');
// 格式化为h1标题
document.execCommand('formatblock', false, '<h1>');复制代码

【注意】虽然所有浏览器都支持以上命令,但这些命令生成的html结构仍有差别。如bold命令,IE和Opera会使用<strong>标签包裹文本,而Safari和Chrome则使用<b>标签,firefox使用<span>

与命令相关的方法:
  • queryCommandEnabled 返回布尔值,用于检测是否可以针对当前选择的文本或当前光标位置执行某个命令;
var canBold = document.queryCommandEnabled("bold");
复制代码
  • queryCommandState 返回布尔值,用于判断当前选择的文本是否已经应用了指定的命令;
var isBold = document.queryCommandState("bold");
复制代码

可以使用这个方法,来设置编辑器中加粗、斜体等按钮的状态。

  • queryCommandValue 用于获取执行某个命令时,传入的值(即execCommand()方法的第三方参数)

富文本选区 Seletion

Seletion对象是指用户选中的文本范围或鼠标的当前位置,通过window.getSelection()来获取该对象。

Seletion对象的属性如下:

  • anchorNode:选区起点所在节点;
  • anchorOffset:anchorNode中包含在选区内的字符数;
  • focusNode:选区终点所在节点;
  • focusOffset:focusNode中包含在选区内的字符数;
  • isCollapsed:boolean,选区的起点与终点是否重合,如果是,可以认为当前没有内容选中;
  • rangeCount:选区中包含的DOM范围的数量;
  • type:描述当前选区的类型

Selection对象的方法参阅MDN。这些方法在富文本编辑器插件里都是很有用的方法,比如控制光标的方法collapse()、collapseToEnd()、collapseToStart(),可以设置插入内容之后光标的位置; 获取选区包含的文本的方法toString()getRangeAt(index)方法返回索引对应的选区中的DOM范围,即range对象

来看一个例子:

// 获取选区内容的位置
function  getSelPos () {let sel = window.getSelection()let rg = sel.getRangeAt(0)let elmRect = rg.getClientRects()[0]let editorRect = $('.j-editor')[0].getBoundingClientRect() // 编辑器容器let pos = {}if (elmRect) {// 选区内容居中位置距容器的左距离pos.x = elmRect.left - editorRect.left + elmRect.width / 2 pos.y = elmRect.top - editorRect.top}return pos
}复制代码

上述方法,可以获取当前选区相对于编辑器容器的位置,可以用来设置在选区附近出现的工具条等。 想实时监测选区的变化,可以监听onselectionchange事件

// 高频事件,做好节流
document.onselectionchange = _.debounce(this.onSelect, 100)
复制代码

处理paste内容

如果往富文本编辑器里粘贴内容,是会把内容的样式也粘贴进来的,浏览器自动会把应用到某个标签的样式内联到此标签的style属性。但更多的时候我们只是需要保留里面的部分格式,需要针对剪贴板中的内容进行过滤、格式化以及特定内容保留等。

editorElem.on('paste', event => {event.preventDefault();let clipboardData = event.clipboardData || event.originalEvent && event.originalEvent.clipboardData || {};let text = clipboardData.getData('text/plain');let html = clipboardData.getData('text/html');})复制代码

通过侦听paste事件,能获取到事件对象上的clipboardData对象,获取粘贴的内容,可以通过getData方法获取剪切版上的纯文本或html结构。 有了html结构,就可以转成dom对象,针对处理了。默认情况下,剪贴板中的

下面重点说下对剪贴板中图片的处理。如果剪贴板中的网页元素包含图片,即img标签,如果直接粘贴到编辑器,该图片的链接地址是原网页所在的图片地址,这里就要考虑到,如果外链别人网站的图片,就有可能有朝一日这个图片不可用,所以还是要放到自家的服务器上才放心。这里就涉及到,已知一张图片的可访问的链接地址,如何把该图片上传到自己的服务器上呢?

不考虑兼容性,给出一种可行的方案:通过canvas画布获取图片的数据,并将数据转为blob对象并进行上传。步骤如下:

  1. new一个Image对象,设置src属性为已知图片的url;
  2. 在图片对象的onload事件里,创建canvas画布,通过其toDataURL方法获取图片数据;
  3. 将图片数据转为一个blob对象,并调用图片上传接口上传该blob对象;
// 根据图片的url,上传图片
export function uploadImgWithUrl(imgUrl,  editor) {/*** 数据转blob对象*/function dataToBlob(data) {var bytes = void 0;bytes = data.split(",")[0].indexOf("base64") >= 0 ? window.atob(data.split(",")[1]) : unescape(data.split(",")[1]);var paramType = data.split(",")[0].split(":")[1].split(";")[0];var uArr = new Uint8Array(bytes.length);for (let i=0; i < bytes.length; i++) {uArr[i] = bytes.charCodeAt(i);}return new Blob([uArr], {type: paramType,name: 'blob.png'});}var options = this;return new Promise(function(resolve, reject) {var img = new Image;img.setAttribute("crossOrigin", "anonymous");img.onload = function() {var canvas = document.createElement("canvas");canvas.width = img.width;canvas.height = img.height;canvas.getContext("2d").drawImage(img, 0, 0);var data = canvas.toDataURL("image/png");var blob = dataToBlob(data);blob.name = 'blob.jpg'// 调用编辑器的上传图片接口 or 也可自行实现一个图片上传方法editor.uploadImg.uploadImg([blob], function(result){if (result && result.body) {var link = result.body.imageUrlList || [];var item = {resourceType: 'image',imageUrl: link[0]}resolve(item);}})};img.onerror = function() {reject();Message.error('图片不允许跨域访问,请手动下载后添加')};imgUrl = -1 !== imgUrl.indexOf("?") ? imgUrl + "&time=" + (new Date).getTime() : imgUrl + "?time=" + (new Date).getTime();img.src = imgUrl;});
}复制代码

以上是将在线的图片上传到服务器的一种解决方法,也在项目中进行了实践。对于剪贴板内存中的图片内容,可以通过getAsFile()方法来获取进而上传:

// 处理内存中的图片
if (clipboardData.items[0]) {let item = clipboardData.items[0]let type = item.type;let regResult = type.match(/image\/(.+)/)if (regResult) {let blob = item.getAsFile();// 调用编辑器的通用上传接口editor.uploadImg.uploadImg([blob])}
}复制代码

最后

以上,只算上对富文本编辑器的基本知识点进行了初步的梳理,如果想自己造轮子,撸一个编辑器出来,需要解决的问题还有很多,可以看下知乎上的讨论为什么都说富文本编辑器是天坑?,里面提到实现一个令人满意的编辑器需要各种填坑,以及良好的设计模式,路漫漫其修远兮……


参考文献

  • JavaScript标准Selection操作
  • html元素contenteditable属性如何定位光标和设置光标
  • 关于 HTML5 的文件上传处理,兼容,以及 BLOB 对象的使用
  • 《JavasSript高级程序设计》 - 富文本编辑
  • 文件和二进制数据的操作

by lzf

尽量关注网易考拉前端团队微信公众号

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

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

相关文章

特殊的求和(函数和循环)

【问题描述】 编写函数int fun(int a,int n)求Sn a aa aaa … aa…a 的值&#xff08;最后一个数中 a 的个数为 n &#xff09;&#xff0c;其中 a 是一个1~9的数字&#xff0c;例如&#xff1a;2 22 222 2222 22222 &#xff08;此时 a2 n5 &#xff09; 。参数由主函…

ms project 入门_Microsoft Project 2010入门

ms project 入门Would you like to keep your projects on track and keep track of how time and resources are used? Let’s take a look at Microsoft Project 2010 and how it can help you stay on top of your projects. 您想保持项目进度并了解如何使用时间和资源吗&…

mysql基本的增删改查和条件语句

增 insert into 表名&#xff08;列名,列名。。。。。。&#xff09; values("test1",23),("test2",23),("test3",24); 这条命令可以一次增加一条数据&#xff0c;也可以同时增加多条数据 还可以从插入其他的表到数据到当前表 insert into 插入的…

后端model传入前端JSP页面中的值判断后再取值

所遇到的问题后端model传入前端JSP页面中的值通过foreach循环内要满足条件才能取值给Div中&#xff0c;我们知道jsp页面中可以直接用EL表达式取值&#xff0c;格式就是${"model中传来的数据"},但是我要把传过来的数据判断后再取值就遇到了问题&#xff0c;通过查百度…

黑莓os软件下载_在PC上试用BlackBerry OS

黑莓os软件下载There’s a wider selection of smart phones and mobile OS’s than ever before, but you can’t just go buy every phone available and try them all out. Here’s how you can test out the latest version of the BlackBerry OS for free on your PC. 智…

IO流之转换流

一 转换流 1 OutputStreamWriter类 是字符流通向字节流的桥梁&#xff1a; 可使用指定的字符编码表&#xff0c;将要写入流中的字符编码成字节。它的作用的就是&#xff0c;将字符串按照指定的编码表转成字节&#xff0c;在使用字节流将这些字节写出去。 public static void m…

Spring事务管理(三)-PlatformmTransactionManager解析和事务传播方式原理

2019独角兽企业重金招聘Python工程师标准>>> Spring在事务管理时&#xff0c;对事务的处理做了极致的抽象&#xff0c;即PlatformTransactionManager。对事务的操作&#xff0c;简单地来说&#xff0c;只有三步操作&#xff1a;获取事务&#xff0c;提交事务&#x…

div方框弯曲边样式_使用弯曲样式编辑文本

div方框弯曲边样式Would you like a new Notepad replacement that incorporates the latest technologies while staying slim and fast? Text editors are usually bland, boring programs, but here’s a new one that makes your text come to life beautifully. 您是否想…

分布式锁的几种实现原理

分布式锁主流有三种模式&#xff1a; 实现方式 功能要求 实现难度 学习成本 运维成本 MySQL 的方案借助表锁/行锁实现 满足基本要求 不难 熟悉 小量OK、大量影响现有业务、1主多从架构&#xff0c;不方便扩容 通过 ZK 创建数据节点的方式实现 满足要求 熟悉 ZK API 即可 需要学…

如何破解您忘记的Windows密码

Here at How-To Geek, we’ve covered many different ways to reset your password for Windows—but what if you can’t reset your password? Or what if you’re using drive encryption that would wipe out your files if you changed the password? It’s time to cr…

sql语句练习50题(Mysql版)

表名和字段–1.学生表Student(s_id,s_name,s_birth,s_sex) –学生编号,学生姓名, 出生年月,学生性别–2.课程表Course(c_id,c_name,t_id) – –课程编号, 课程名称, 教师编号–3.教师表Teacher(t_id,t_name) –教师编号,教师姓名–4.成绩表Score(s_id,c_id,s_score) –学生编号…

OpenCV3 识别图中表格-JAVA 实现

2019独角兽企业重金招聘Python工程师标准>>> 关于 JAVA 学习 OpenCV 的内容&#xff0c;函数讲解。内容我均整理在 GitHubd的OpenCV3-Study-JAVA OpenCV 3 识别图中表格-Java 实现 1. 说明 网上大部分资料&#xff0c;都是针对 C的&#xff0c;python、java 的例子太…

内存泄露 体现在哪个数字上_Microsoft刚刚泄漏了一个新的开始菜单。 你喜欢哪个?...

内存泄露 体现在哪个数字上NTAuthority on TwitterTwitter上的NTAuthorityMicrosoft messed up today, releasing an internal build of Windows 10 to Windows Insiders. This build was never meant to see the light of day, but it features a new Start menu design—with…

简述 Spring Cloud 是什么

很多同学都了解了Spring &#xff0c;了解了 Spring Boot, 但对于 Spring Cloud 是什么还是比较懵逼的。 本文带你简单的了解下&#xff0c;什么是Spring Cloud。 Spring Cloud 是什么 从字面理解&#xff0c;Spring Cloud 就是致力于分布式系统、云服务的框架。 Spring Cloud …

js DOM节点

元素节点 4种方式获取 var oDiv document.getElementById("box");        //通过Id获取元素var oDiv document.getElementsByClassName("div")[0];   //通过类名获取元素  --》[ 0 ] 必须写var oDiv document.getElementsByTagName("…

python web scraping

2019独角兽企业重金招聘Python工程师标准>>> 最近在看《Web Scraping with Python》&#xff0c;借此来熟悉Python2.7如何开始编程。 发现书上主要使用的 http://example.webscraping.com/ 网站有部分变化&#xff0c;书中的代码有点无法对照使用&#xff0c;因此稍…

傅里叶变换的物理意义

用三角函数表示周期函数 傅里叶的相关理论始于下面假设&#xff1a;对于周期为1的信号$f(t)$&#xff0c;可以由不同频率的三角函数组成&#xff0c; $f(t) \frac{a_0}{2}\displaystyle{\sum^{\infty}_{k1}}(a_kcos(2\pi kt)b_ksin(2\pi kt))$ 组成的基础波形为一个信号对&…

天猫年度总结

2019独角兽企业重金招聘Python工程师标准>>> 鲁大师天猫工作总结 时间&#xff1a;2017年10月22日-1月30日 1、对代理商进行6大区域的划分管理&#xff0c;有专门的客服指导。 2、加班费申请和车费报销制度。 3、简化了特权订金2阶段改成1阶段&#xff0c;极大的方便…

因特网使用期限_Internet死亡时使用PC的其他方式

因特网使用期限Nothing is more annoying than getting your Internet connection shut down, due to weather, or perhaps forgetting to pay your bill. Let’s take a look at some ways you can be productive and entertained without the Internet. 没有什么比由于天气原…

【基础操作】线性基详解

线性基是一个奇妙的集合&#xff08;我摘的原话&#xff09; 这里以非 $OI$ 的角度介绍了线性基 基础部分 模板题 给你 $n$ 个数的集合&#xff0c;让你选出任意多个不重复的数&#xff0c;使得它们的异或和最大。 线性基是什么 我们称集合 $B$ 是集合 $S$ 的线性基&#xff0c…