富文本编辑器,实现起来有很多坑,我这里主要介绍一下实现原理
富文本编辑器的实现
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
富文本编辑器的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']
};