富文本编辑器涉及到的知识与一些坑

笔者最近在负责公司项目中的富文本编辑器的部分,由于是自己公司的产品,为了性能等方面的考虑要求笔者自己写原生代码实现。过程当中遇到了很多坑,现在来与大家一起交流一下。

目录:

  1. 要完成的功能:
    1. 富文本编辑器的功能实现
    2. 选中后映射属性到属性栏
    3. 实现复制粘贴保留支持样式
  2. 涉及到的知识:
    1. 富文本编辑器底层实现原理(contenteditable属性)
    2. 鼠标选中时获取选中文本的样式集合(getSelection)
    3. 过滤粘贴样式(递归)
  3. 遇到的坑:
    1. 获取鼠标选中文本的样式

首先先说富文本编辑器的实现原理

富文本编辑器的实现方法有俩种,以前是通过iframe的document,而现在基本上都是使用document.execCommand(),现在很多的富文本编辑器的实现原理都是使用的这个方法,比如wangEditor,UEidtor,WYSIWYG
接下来我们就简单的做一个简单的富文本编辑器小demo,效果如下:

富文本编辑器涉及到的知识与一些坑_第1张图片
代码如下:


<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Documenttitle>
    <style>
        .editorBox {
            width: 500px;
            min-height: 200px;
            border: 1px solid;
        }
    style>
head>
<body>
    <div class="editorBox" contenteditable="true">
        hello world!
    div>
    <button onclick="handleStyle('Bold')">加粗button>
    <button onclick="handleStyle('Italic')">斜体button>
    <button onclick="handleStyle('Underline')">下划线button>
    <button onclick="handleStyle('StrikeThrough')">删除线button><br/>
    字间距值:<input type="text" id="letterSpacingValue" value="50">px
    <button onclick="setStyle('letterSpacing')">设置字体间距button><br/>
    <script>
        //设置简单的字体风格
        function handleStyle(type,val) {
            document.execCommand('StyleWithCSS',true,true)
            document.execCommand(type,false,val)
        }

        //设置比较复杂的字体样式,笔者只写了一个字间距,如果向设置其他的可按此规则重复利用此模块
        function setStyle(type) {
            let letterSpacingVal = document.getElementById('letterSpacingValue').value;
            document.execCommand('styleWithCSS',true,true)
            document.execCommand('Bold')
            //获取选中的内容节点
            let select = document.getSelection().anchorNode.parentElement;
            select.style[type] = val + 'px';
            document.execCommand('Bold')
        }
    script>
body>
html>

首先是将div变成了可编辑状态。使用的是contenteditable这个属性,这个属性赋值为true的时候就是可编辑状态赋值为false的时候就是不可编辑的状态
其次是加了五个按钮一个输入框。五个按钮分别添加了事件,对应改变选中文本的不同状态。核心功能是document.execCommand() 这个方法。而我们使用document.execCommand(‘StyleWithCSS’,true,true) 是为了不用b标签,i标签等等。当然了,这句话也可以忽略,省略之后使用document.execCommand()命令的时候会将选中文本变成指定的标签,比如你是 bold命令 那么就会变成 b 标签,如果是 italic命令 就会变成 i 标签等等,而使用了document.execCommand(‘StyleWithCSS’,true,true)这个命令是为了将标签统一变成 span标签,然后在span标签里面 添加style属性 放样式。

现在我们来做属性映射的功能

首先我们要在div当中添加俩个事件,分别是onmouseup和onkeyup事件,

<div 
	id="editorBox" 
	contenteditable="true"
	onmouseup="getSelectionMessage()"
	onkeyup="getSelectionMessage()"
>
    hello world!
div>

注意:
事件千万不要搞错了,笔者刚开始用的onkeydown事件被折磨了将近俩天。
当鼠标或者键盘在div进行操作的时候就会触发此事件而后获取选中文本或者光标前一个文本的信息。

可以用俩种方法,但是各有利弊。

  1. 第一种是使用官方提供的方法, document.queryCommandState() 或者 document.queryCommandValue() 。这俩个方法用处都是一样的,只不过针对的东西不一样,document.queryCommandState()是查看bold,italic等等命令的状态,如果选中的文本有此命令就会返回true,没有此命令就会返回false。
    document.queryCommandValue()是查看比如说 ForeColorFontName 等等命令的值,如果有就返回值,没有就返回false。(我们这个例子没有使用,所以不需要这个命令)
function getSelectionMessage() {
    let arr = ['Bold','Italic','Underline','StrikeThrough'];
    let buttons = document.getElementsByTagName('button');
    arr.forEach((item, index) => {
        document.queryCommandState(item)
        ? buttons[index].style.color = 'red'
        : buttons[index].style.color = 'black'
    })
}

下面为效果图:
富文本编辑器涉及到的知识与一些坑_第2张图片
富文本编辑器涉及到的知识与一些坑_第3张图片
这时你会发现我们已经实现了简单的属性映射。但是还是有问题的。但之后的技术涉及到公司核心技术我就不再去写了。笔者写的这些如果还是有朋友们 没有看懂可以在下方评论 ,笔者一一回复。

此处为特殊情况:
富文本编辑器涉及到的知识与一些坑_第4张图片
这种时候你会发现它没有进行映射了,是因为这个方法本身提供的这个技术,就是 当出现冲突的时候就会全部为false ,全都不映射。
2. 第二种方法就是自己去使用getSelection方法结合递归去遍历。逻辑稍微复杂一点。

在之后的一个需求就是过滤掉本富文本编辑器不支持的内容。

如果不过滤的话在别的地方粘贴过来的信息就会变成这个样式:
富文本编辑器涉及到的知识与一些坑_第5张图片
而这些标签和样式肯定是我们不支持的,如果把这些东西放到富文本编辑器里面是很难去进行处理的。所以我们要过滤掉不需要的东西。最开始笔者是用三重递归去解决的,先过滤标签然后过滤样式,在过滤子元素的标签和样式。后来的话领导给我介绍了个好东西,专门用来过滤标签和样式的。链接如下:过滤标签和样式的包
首先我们需要下载这个包:

npm i sanitize-html -S

然后在你的富文本编辑器文件中引用这个包:

const sanitizeHtml = require('sanitize-html');

之后在你的编辑器外层div上面添加一个onpaste事件:

<div 
	id="editorBox" 
	contenteditable="true"
	onmouseup="getSelectionMessage()"
	onkeyup="getSelectionMessage()"
	onpaste="filterPasteContent()"
>
    hello world!
div>

最后在你的js部分加入此方法:

filterPasteContent(event) {
  event.preventDefault();		//阻止浏览器默认行为
  event.stopPropagation();		//阻止事件冒泡
  let data = event.clipboardData.getData('text/html');		//获取粘贴板的信息
  //通过sanitizeHtml包的方法去过滤标签和样式
  data = sanitizeHtml(data, {
    allowedTags: ['b','i','u','strike','font','ol','ul','div'],
    allowedAttributes: {
      'font': ['color','face','font','style'],
    }
  })
  const selection = window.getSelection();		//获取对象

  if (!selection.rangeCount) return false;		//如果没有就退出,不进行操作

  let div = document.createElement("div");		//创建一个div
  div.innerHTML = data;		//div的innerHTML就是我们刚刚过滤完粘贴信息的内容
  
  selection.getRangeAt(0).insertNode(div);		//将刚刚的div插入到富文本编辑器中
}

这样我们就把我们不需要的标签和样式所过滤掉了,效果如下:
富文本编辑器涉及到的知识与一些坑_第6张图片

如果朋友们的富文本编辑器还有更炫的东西或者更深的需求欢迎下方评论,我们一起学习一起研究!

你可能感兴趣的:(吴小迪专栏之原生JS)