You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

异步复制ProseMirror元素时剪贴板数据写入超时问题

异步复制ProseMirror元素时剪贴板数据写入超时问题

这个问题的核心痛点其实是浏览器的copy事件是同步触发的——当你在事件回调里发起异步请求(比如从存储拉取图片转base64)时,事件本身早就已经执行完毕了,这时候再去写剪贴板自然就赶不上了。我来给你几个可行的解决思路和具体实现方案:

核心思路:先拦截默认复制,异步完成后手动写入剪贴板

我们需要先阻止浏览器的默认复制行为,等所有异步资源处理完成后,再用现代剪贴板API手动把处理好的ProseMirror节点数据写入剪贴板。

具体实现步骤

  1. 拦截默认copy事件
    先给ProseMirror编辑器绑定copy事件,用event.preventDefault()阻止浏览器的默认复制流程,这样我们就能完全掌控剪贴板的内容了。

  2. 异步批量处理图片资源
    遍历要复制的ProseMirror节点,找出所有image类型的节点,用Promise.all并行发起异步请求获取base64资源,确保所有图片都处理完成后再继续后续操作。

  3. 用现代剪贴板API写入数据
    所有异步操作完成后,把处理好的节点序列化为你需要的JSON格式,再用navigator.clipboard.write写入自定义MIME类型的数据,同时可以额外写入纯文本/HTML格式,让其他应用也能识别粘贴内容。

完整代码示例

// 给ProseMirror编辑器绑定copy事件
editor.view.dom.addEventListener('copy', async (event) => {
  // 1. 阻止浏览器默认复制行为,接管剪贴板控制权
  event.preventDefault();

  try {
    // 2. 获取当前选中的ProseMirror内容片段
    const { state } = editor.view;
    const { from, to } = state.selection;
    const selectedContent = state.doc.slice(from, to);

    // 3. 遍历节点,收集所有图片的异步处理Promise
    const imageProcessPromises = [];
    // 先把节点转成可修改的数组,方便后续替换
    const contentNodes = selectedContent.content.toArray();

    contentNodes.forEach((node, index) => {
      if (node.type.name === 'image') {
        // 替换成你从存储获取base64的异步方法
        const processImage = async () => {
          const base64Src = await yourStorageFetchFunction(node.attrs.src);
          // 生成处理后的新图片节点
          const newImageNode = node.type.create({
            ...node.attrs,
            src: base64Src
          });
          // 替换原数组中的节点
          contentNodes[index] = newImageNode;
        };
        imageProcessPromises.push(processImage());
      }
    });

    // 等待所有图片异步处理完成
    await Promise.all(imageProcessPromises);

    // 4. 将处理好的节点序列化为目标JSON格式
    const jsonOutput = JSON.stringify(
      contentNodes.map(node => JSON.stringify(node.toJSON()))
    );

    // 5. 写入剪贴板:自定义格式 + 兼容其他应用的纯文本格式
    const clipboardContents = [
      // 写入你的应用自定义格式
      new ClipboardItem({
        'text/my-app': new Blob([jsonOutput], { type: 'text/plain' })
      }),
      // 写入纯文本,让其他应用也能粘贴到内容
      new ClipboardItem({
        'text/plain': new Blob([selectedContent.textContent], { type: 'text/plain' })
      })
    ];

    // 手动写入剪贴板
    await navigator.clipboard.write(clipboardContents);
  } catch (error) {
    console.error('复制操作失败:', error);
    // 降级处理:如果现代剪贴板API不可用,用同步方式复制纯文本
    event.clipboardData.setData('text/plain', selectedContent.textContent);
  }
});

关键细节说明

  • Promise.all的作用:确保所有图片的异步请求都完成后再处理剪贴板,避免出现部分图片未加载完成就写入的情况。
  • ClipboardItem兼容性:这个API需要HTTPS环境(localhost开发环境例外),如果要兼容旧浏览器,可保留event.clipboardData.setData作为降级方案,但旧浏览器无法支持异步场景的完整处理。
  • ProseMirror节点操作:通过slice获取选中内容,用toArray转为可修改的数组,处理后再序列化,完全匹配你原本的JSON输出格式。

这样处理后,就能完美解决异步操作赶不上剪贴板事件生命周期的问题了!

备注:内容来源于stack exchange,提问作者Gaurussel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.17 11:02:57