tinymce 实现 粘贴图片自动上传

6/26/2021 富文本编辑器tinymce

# tinymce 实现 粘贴图片自动上传

tinymce 中文文档:上传图片和文件 (opens new window)

关于图片上传的踩坑记录,基本就是如下几个

# 不能复制本地图片然后粘贴

图片的复制粘贴,依赖于 paste 插件 文档:插件 \ paste 粘贴插件 (opens new window)

简单的配置如下:

tinymce.init({
  selector: '#tinydemo',
  plugins: 'paste',
  toolbar: 'paste',
  paste_data_images: true // 默认是false的,记得要改为true才能粘贴
})
1
2
3
4
5
6

粘贴后的图片在显示上是这样的:

是一个二进制流链接的文件,这种保存到到后端在回显也显示不了了,所以要配置上传。还是看官方文档 上传图片和文件 | TinyMCE 中文文档中文手册 (ax-z.cn) (opens new window) 由于我都是用的自己的上传,这一块就没去深究


# 粘贴进来的图片上传问题

如何获取粘贴的图片内容: 上传图片和文件 | TinyMCE 中文文档中文手册 (ax-z.cn) (opens new window)

找到 images_upload_handler 函数一栏,在 init 方法中使用这个函数

tinymce.init({
  images_upload_handler: function(blobInfo, success, failure, progress) {}
})
1
2
3

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 都会比原图要先复制进来

  1. 遇到名为 image.png 的图片,延迟 30 毫秒在上传(这里用到了定时器 setTimeout)
  2. 如果是触发了上面说的 bug,30 毫秒内原图应该就会被复制进来了,那就是说如果第二次触发 images_upload_handler 函数时,
    2.1. 如果还有定时器在等待,那就说明定时器里面的图是多余的
    2.2. 如果超过 30 毫秒后还没有另外的图片触发images_upload_handler 函数,说明用户上传的就是叫 image.png 的图片,让定时器执行上传完成即可。
  3. 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()
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

大功告成~

Last Updated: 6/26/2021, 6:02:40 PM