富文本编辑器

2,421 阅读2分钟

富文本编辑器,实现起来有很多坑,我这里主要介绍一下实现原理

富文本编辑器的实现

1:实现文本编辑方法

  • 主要利用contenteditable属性,将元素变成可编辑;
  • 然后结合document.execCommand实现样式修改;
  • Range/Selection 可以让开发者知道用户鼠标选择的内容。

国外的CKEditor、百度的UEditor、优秀的后起之秀wangEditor,都是采用这种方法实现的。

2:实现插入链接,使用document.execCommand()

插入链接,需要第三个参数document.execCommand('createLink', true, url)

function creakLink(){
  const url = window.prompt('输入url')
    if (url) {
        document.execCommand('createLink', true, url)
    }
}

3:实现插入图片,使用document.execCommand('insertImage', true, url)

实现方法有两种,一种是将图片上传给服务端,服务端返回url。

第二种是,配合input type='file'URL.createObjectURL(file), 前端生成url。

let file = document.getElementById('upload').files[0]
let url = URL.createObjectURL(file)
restoreRange()
document.execCommand('insertImage', true, url)

4:编辑器实现有个麻烦点,就是需要时刻保存Range

因为在点击编辑器之外,编辑器失去焦点,此时使用document.execCommand没有效果;

解决办法:需要时刻保存Range,并在使用document.execCommand时候恢复Range, 因此需要给编辑器加一堆keyup,click,mouseup等事件,时刻保存Range对象

5: 一个编辑器demo

这里求star

富文本编辑器的xss处理

富文本编辑器的xss 主要是指,富文本编辑器可以保存草稿,用户二次编辑时候,浏览器渲染用户以前编辑的内容时候可能发生xss,或者预览时候出现xss。

富文本编辑器中防止xss主要是过滤用户输入,那过滤有两种:黑名单过滤白名单过滤

黑名单过滤

const xssFilter = (html)=> {
    if(!html) return '';
    //将script标签过滤
    html = html.replace(/<\s*\/?script\s*>/,'');
    //过滤html标签中存在的javascript所执行的内容
    html = html.replace(/javascript:[^'"]*/g,'');
    //过滤html标签中所执行的脚本
    html = html.replace(/onerror\s*=\s*['"]?[^'"]*['"]?/g,'');
    return html;
}

弊端:html标签众多,全部过滤非常困难,因此大多使用的是白名单。

白名单过滤

白名单过滤,要设置一些允许的标签和属性,不在白名单里面的就直接干掉。有比较成熟的库,不需要自己去写这个白名单,推荐:js-xss 举例一个白名单设置:

{
    a:      ['target', 'href', 'title'],
    abbr:   ['title'],
    address: [],
    area:   ['shape', 'coords', 'href', 'alt'],
    article: [],
    aside:  [],
    audio:  ['autoplay', 'controls', 'loop', 'preload', 'src'],
    b:      [],
    bdi:    ['dir'],
    bdo:    ['dir'],
    big:    [],
    blockquote: ['cite'],
    br:     [],
    caption: [],
    center: [],
    cite:   [],
    code:   [],
    col:    ['align', 'valign', 'span', 'width'],
    colgroup: ['align', 'valign', 'span', 'width'],
    dd:     [],
    del:    ['datetime'],
    details: ['open'],
    div:    [],
    dl:     [],
    dt:     [],
    em:     [],
    font:   ['color', 'size', 'face'],
    footer: [],
    h1:     [],
    h2:     [],
    h3:     [],
    h4:     [],
    h5:     [],
    h6:     [],
    header: [],
    hr:     [],
    i:      [],
    img:    ['src', 'alt', 'title', 'width', 'height'],
    ins:    ['datetime'],
    li:     [],
    mark:   [],
    nav:    [],
    ol:     [],
    p:      [],
    pre:    [],
    s:      [],
    section:[],
    small:  [],
    span:   [],
    sub:    [],
    sup:    [],
    strong: [],
    table:  ['width', 'border', 'align', 'valign'],
    tbody:  ['align', 'valign'],
    td:     ['width', 'rowspan', 'colspan', 'align', 'valign'],
    tfoot:  ['align', 'valign'],
    th:     ['width', 'rowspan', 'colspan', 'align', 'valign'],
    thead:  ['align', 'valign'],
    tr:     ['rowspan', 'align', 'valign'],
    tt:     [],
    u:      [],
    ul:     [],
    video:  ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width']
  };