TinyMCE 编辑器调用 input file 上传图片丢失光标焦点完美修复方案

2026-06-24 188 浏览 0 评论

在基于 TinyMCE 富文本编辑器开发网页图文编辑功能时,图片上传是高频基础需求,常规实现方案为创建隐藏 type="file" ​ 的 input 标签,通过 JS 获取 DOM 后调用 click () 唤起系统文件选择窗口完成图片选取。

在普通浏览器环境下该逻辑运行正常,但在 PC 端微信内置 WebView 中会出现典型兼容性缺陷:唤起文件选择弹窗后页面全局焦点被系统弹窗夺走,TinyMCE 编辑器内部选区标记、光标焦点直接清空,文件选择弹窗关闭返回页面后,编辑器无法自动恢复原有光标位置,插入图片时会直接追加至文档开头,严重影响编辑使用体验。

经过多场景验证、多套修复方案对比,全局捕获点击事件、预存编辑器选区、页面重新激活后自动恢复焦点的拦截修复方案兼容性最强、代码侵入性最低,无需修改原有上传业务逻辑,下文完整落地该实现方案并附带可直接运行 Demo 代码。

问题成因分析

  1. 执行 fileInput.click() 唤起系统原生文件选择弹窗时,WebView 页面失去顶层焦点,TinyMCE 内部 hasFocus() 判定永久返回 false;
  2. TinyMCE 依赖内部缓存记录光标选区,页面失焦瞬间缓存的选区数据被清空;
  3. PC 微信 WebView 存在渲染延迟,弹窗关闭后同步执行编辑器 focus 无法生效,必须添加延时等待页面渲染完成;
  4. 常规局部重写 input 点击、页面可见性监听、替换微信 JSSDK 选图等方式均存在场景局限性,全局事件捕获方案适配所有上传触发入口,无需改造原有上传代码。

完整解决方案

核心实现逻辑:通过捕获阶段全局监听页面所有点击事件,识别文件上传 input 触发动作,在弹窗唤起前调用 TinyMCE 原生 API 保存当前光标选区;注册一次性窗口焦点恢复监听,页面重新获取焦点后延时执行编辑器聚焦与选区还原,自动修复光标丢失问题。

完整可运行 Demo 代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>TinyMCE 上传图片光标修复 Demo</title>
  <!-- 引入 TinyMCE 官方 CDN -->
  <script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/6/tinymce.min.js" referrerpolicy="origin"></script>
</head>
<body>
  <!-- 富文本编辑器容器 -->
  <textarea id="editor"></textarea>
  <!-- 隐藏文件上传 input -->
  <input type="file" id="fileInput" style="display: none;">
  <!-- 上传触发按钮 -->
  <button id="uploadBtn">上传图片</button>

  <script>
    // 初始化 TinyMCE 编辑器
    tinymce.init({
      selector: '#editor',
      height: 400,
      plugins: 'image link lists',
      toolbar: 'undo redo | bold italic | image',
      setup: editor => {
        // 绑定按钮点击唤起文件选择弹窗(业务原有逻辑不变)
        document.getElementById('uploadBtn').addEventListener('click', () => {
          let fileInput = document.getElementById('fileInput');
          fileInput.setAttribute('accept', 'image/*');
          fileInput.click();
        });

        // ========== 核心光标修复逻辑(方案 1 全局拦截)==========
        let saveRangeFlag = null;
        document.addEventListener('click', function(e) {
          const targetDom = e.target;
          // 判断点击目标为文件上传 input
          if (targetDom.tagName === 'INPUT' && targetDom.type === 'file') {
            // 唤起弹窗前保存编辑器当前光标选区
            editor.save();
            saveRangeFlag = true;

            // 一次性窗口焦点恢复回调
            const recoverEditorFocus = () => {
              setTimeout(() => {
                if (saveRangeFlag) {
                  editor.focus();
                  editor.restore();
                  saveRangeFlag = null;
                }
              }, 200);
              // 执行完成移除监听,避免重复触发
              window.removeEventListener('focus', recoverEditorFocus);
            };
            // 注册窗口焦点监听,页面重新激活时执行恢复逻辑
            window.addEventListener('focus', recoverEditorFocus, { once: true });
          }
        }, true); // 使用捕获阶段监听,优先拦截上传点击事件

        // 文件选择完成监听,模拟图片插入业务逻辑
        const fileInput = document.getElementById('fileInput');
        fileInput.addEventListener('change', function(e) {
          const file = e.target.files[0];
          if (!file) return;
          const url = URL.createObjectURL(file);
          // 插入图片到原有光标位置
          editor.insertContent(`<img src="${url}" style="max-width:100%;">`);
          // 清空 input,允许重复选择同一张图片
          fileInput.value = '';
        })
      }
    });
  </script>
</body>
</html>

代码分段详细说明

1. 编辑器初始化与原有上传业务代码保留

文中上传按钮点击逻辑为项目原始业务代码,未做任何修改,依旧通过获取隐藏 file 类型 input、设置图片文件限制、调用 click () 唤起选择弹窗,保证原有业务逻辑无侵入,无需重构上传模块。

2. 全局捕获点击事件核心拦截逻辑

document.addEventListener 第三个参数传入 true,开启事件捕获阶段监听,页面任意位置点击 DOM 元素都会优先进入当前回调函数,精准识别所有 input[type=file] 触发动作,覆盖编辑器工具栏上传按钮、页面自定义上传按钮等全部上传入口,不存在遗漏场景。

3. 选区预存机制

检测到文件 input 触发时,执行 editor.save() ,该 TinyMCE 内置 API 会缓存当前编辑器内光标位置、文本选区数据,并通过标记变量 saveRangeFlag 标记存在待恢复选区,为后续还原光标做数据储备。

4. 一次性窗口焦点监听

通过 { once: true } 配置监听仅执行一次,文件弹窗关闭、页面 WebView 重新获取系统焦点时触发恢复函数,执行完成后主动移除监听,防止多次叠加注册监听造成重复执行、光标错乱等副作用。

5. 200ms 延时的必要性

PC 微信 WebView 在关闭系统文件弹窗后存在页面重绘、焦点同步延迟,同步调用 editor.focus() 无法识别编辑器容器,设置 200 毫秒延时等待页面 DOM 渲染、焦点状态稳定后,再执行聚焦与选区还原,保证光标精准回到上传前位置。

6. 文件选中回调业务逻辑

监听 input 的 change 事件获取选中图片文件,通过 URL.createObjectURL 生成本地预览地址,调用 editor.insertContent 插入图片标签,此时因前置完成选区缓存与焦点恢复,图片会插入至用户上传前光标所在位置,不会固定插入文档头部。

方案优势总结

  1. 零业务侵入 :原有唤起 fileInput.click () 上传逻辑完全保留,无需修改上传模块代码;
  2. 全场景覆盖 :全局捕获点击事件,适配编辑器内置上传按钮、自定义页面上传按钮等所有上传触发场景;
  3. 兼容性稳定 :针对 PC 微信 WebView 失焦延迟问题做延时适配,主流浏览器、移动端 WebView 均可正常运行;
  4. 无内存泄漏 :使用一次性窗口监听,执行完毕自动移除事件监听,不会持续堆积监听函数;
  5. 实现轻量化 :仅依赖 TinyMCE 原生内置 API,无需引入第三方插件、不增加项目打包体积。

实际线上部署注意点

  1. 项目中需保证修复代码注册在 TinyMCE 初始化 setup 回调内部,确保编辑器实例加载完成后再执行选区相关 API;
  2. 多编辑器页面场景,每一个独立编辑器实例内部单独注册一套全局拦截监听,各编辑器选区数据隔离互不干扰;
  3. 若项目存在图片上传后端接口,可在 change 回调中新增文件 FormData 上传逻辑,不影响光标修复核心代码运行;
  4. 隐藏 file 输入框不可移除,该 DOM 为唤起系统文件弹窗必备载体,仅通过 CSS 隐藏展示即可。

发布评论

发布评论前请先 登录
0 评论
点赞
收藏

评论列表 0

暂无评论