tinymce 实现 粘贴图片自动上传
关于图片上传的踩坑记录,基本就是如下几个
不能复制本地图片然后粘贴
图片的复制粘贴,依赖于 paste
插件 文档:插件 \ paste 粘贴插件
简单的配置如下:
tinymce.init({
selector: '#tinydemo',
plugins: 'paste',
toolbar: 'paste',
paste_data_images: true // 默认是false的,记得要改为true才能粘贴
})
粘贴后的图片在显示上是这样的:
是一个二进制流链接的文件,这种保存到到后端在回显也显示不了了,所以要配置上传。还是看官方文档 上传图片和文件 | TinyMCE 中文文档中文手册 (ax-z.cn) 由于我都是用的自己的上传,这一块就没去深究
粘贴进来的图片上传问题
如何获取粘贴的图片内容: 上传图片和文件 | TinyMCE 中文文档中文手册 (ax-z.cn)
找到 images_upload_handler 函数一栏,在 init 方法中使用这个函数
tinymce.init({
images_upload_handler: function(blobInfo, success, failure, progress) {}
})
blodInfo 对象包含了几个方法:
方法名 | 执行后返回值 |
---|---|
base64 | 图片对应的 base64 编码 |
blob | 二进制流 File 对象 |
blobUri | 二进制流的显示 URL(临时 URL,页面关闭后就消失了) |
filename | 文件名称(这里是被转换过的文件名称,并非原名)如: “mceclip0.jpg” |
id | 文件 ID 如: “mceclip0” |
name | 文件名称,不带后缀的 如:“mceclip0” |
uri | 最后一个我也不知道是啥 |
如果要获取文件原名,要在 blod 对象中拿
上传图片,自然是拿 blod 对象,然后结合自己的业务逻辑去上传给后端就行
上传图片后回显
success, failure, progress 几个回调的作用
名称 | 作用 |
---|---|
success | success('http://www.xxxx.com/xxx.png') 要插入的图片 |
failure | failure() 印象中这个方法也很鸡肋,甚至最后我都用 success 了 |
progress | progress(50) 显示图片上传的进度(全局显示,没啥作用) |
图片点开大图后复制进来 2 张的问题
你以为坑到这里就结束了吗?no no no,这是各大平台都会出现的问题。
问题是这样的:window 平台下,图片在缩略图/没点开大图的时候,复制粘贴,没问题。
如果双击,打开了图片的大图在进行复制,粘贴到编辑器后,会显示 2 张一样
的图片。包括微信点开大图,等等都会
我粘贴进去的图片名称是 :img.jpg
。很明显在打印信息的 blodInfo.blod().name
中,多了一张 image.png
的图片。所以这并不属于编辑器的 bug,而是 window 平台下的机制
无论粘贴什么图片,只要点开大图都会多一张叫 image.png
的图片。可是不排除用户的确会上传 image.png
的图片,所以不能简单粗暴的过滤 image.png
的图片。
利用防抖函数的思想,过滤掉多余的 image.png
由于 bug 修复的比较紧急,代码并没有做过多的优化/封装成方法,稍微的看看逻辑就好
通常来说,image.png 都会比原图要先复制进来
- 遇到名为 image.png 的图片,延迟 30 毫秒在上传(这里用到了定时器 setTimeout)
- 如果是触发了上面说的 bug,30 毫秒内原图应该就会被复制进来了,那就是说如果第二次触发
images_upload_handler
函数时,
2.1. 如果还有定时器在等待,那就说明定时器里面的图是多余的
2.2. 如果超过 30 毫秒后还没有另外的图片触发images_upload_handler
函数,说明用户上传的就是叫image.png
的图片,让定时器执行上传完成即可。 - 30 毫秒内触发了删除,定时器肯定要清除,可是图片还在编辑器内没删除,这时候就要执行
removeFn
。在下面备注也有写了,记得要用闭包,把image.png
图片对应的success
函数存起来。因为如果图片上传成功了,还得靠这个函数设置成对应的图片
// 定义2个全局变量
let uploadTimeOut = null
let removeFn = null
tinymce.init({
// 上传函数
images_upload_handler: (blobInfo, success, failure, progress) => {
if (uploadTimeOut) {
removeFn && removeFn()
clearTimeout(uploadTimeOut)
}
let fileInfo = blobInfo.blob()
// 定义一个上传方法
var upload = () => {
// this.myUpload 是 上传文件的逻辑了,用promise包了一下
this.myUpload(fileInfo)
.then(res => {
// 上传成功后
success(res.data.url)
})
.catch(res => {
// 上传失败后,把src标记为 uploadFail ,然后在删除
// failure 函数有什么坑了,所以不想用它了
success('uploadFail')
// setTimeOut 是为了然success函数执行到位后在删除,否则可能查不到对应的图片
setTimeout(() => {
let errorImg = document.querySelectorAll('img[src="uploadFail"]')
errorImg.forEach(item => {
item.parentNode.removeChild(item)
})
}, 100)
})
}
if (fileInfo.name == 'image.png') {
uploadTimeOut = setTimeout(() => upload(), 30)
// 这里要用一下闭包把当前的success函数保存起来,具体为啥一下子说不清,存起来就是了
// 图片复制进来后唯一的标识就是 blobUri 了,到时候删除还得靠他
// removeFn 与第9行代码呼应
removeFn = (function(url, cb) {
return function() {
cb && cb(url)
let imgNode = document.querySelector('img[src="' + url + '"]')
imgNode && imgNode.parentNode && imgNode.parentNode.removeChild(imgNode)
removeFn = null
}
})(blobInfo.blobUri(), success)
} else {
upload()
}
}
})
大功告成~