TinyMCE 编辑器实战:自定义视频上传与交互优化

2026-03-13 80 浏览 0 评论

在内容编辑场景中,TinyMCE 作为一款功能强大的富文本编辑器,默认的视频上传功能往往无法满足个性化需求。本文将详细讲解如何基于 TinyMCE 实现 自定义视频上传按钮、专属视频显示样式、禁止视频元素被编辑 等核心功能,同时解决图片选中样式、视频块删除异常等常见问题。

一、核心需求拆解

本次定制开发的核心目标包括:

  1. 新增自定义“上传视频”按钮,替代编辑器默认视频功能;
  2. 视频上传后生成自定义样式的显示块,且块内元素禁止编辑;
  3. 点击视频播放按钮可弹窗播放视频;
  4. 移除视频占位图时自动删除整个视频块;
  5. 清除图片选中后的默认边框样式,优化视觉体验。

二、分步实现自定义功能

1. 自定义编辑器视频上传按钮

首先需要在 TinyMCE 工具栏中新增专属的“上传视频”按钮,替代默认按钮,实现按钮样式和点击逻辑的自定义。

// TinyMCE 初始化配置中新增自定义按钮
{
  toolbar:'myvideo', // 自定义按钮标识,需与下方 addButton 的名称一致
  setup: editor => {
    // 注册自定义按钮
    editor.ui.registry.addButton('myvideo', {
      // 按钮显示内容:自定义视频图标(可替换为自有图标路径)
      text: '<img src="img/video.png?t=1" style="width:22px; height:22px; margin: 0 2px; position: relative; top:2px" />',
      tooltip: '上传视频', // 鼠标悬浮提示文字
      onAction: () => {
        // 点击按钮触发隐藏的文件选择框
        let fileInput = document.getElementById('fileInput');
        // 限制仅可选择视频格式,避免上传错误文件
        fileInput.setAttribute('accept', '.mp4,.mov,.avi');
        fileInput.click();
      },
    });
  }
}

代码说明

  • toolbar:'myvideo' :声明工具栏显示自定义按钮, myvideo 为自定义标识,需保持唯一;
  • addButton('myvideo') :注册按钮,第一个参数需与 toolbar 中的标识一致;
  • text :按钮的显示内容,这里使用图片作为按钮样式,替代默认文字;
  • onAction :按钮点击事件,触发隐藏的文件选择框,并限制仅可选择 MP4/MOV/AVI 格式视频。

2. 添加隐藏的文件选择域

需要在页面中添加一个不可见的文件输入框,用于接收用户选择的视频文件,避免直接在编辑器中处理文件选择逻辑。

<!-- 隐藏的文件选择框,用于视频上传 -->
<input style="opacity: 0; position: absolute; z-index: -1;" type="file" id="fileInput" multiple @change="change" />

代码说明

  • opacity: 0 + z-index: -1 :隐藏输入框,仅保留功能不显示视觉元素;
  • multiple :允许同时选择多个视频文件上传;
  • @change="change" :文件选择后触发 change 方法,处理文件上传逻辑(Vue 语法,非 Vue 项目可替换为 onchange="change()" )。

3. 视频上传与自定义显示块插入

文件选择后,通过异步接口上传视频,成功后生成自定义样式的视频显示块,并插入到编辑器中,同时设置 contenteditable="false" 禁止编辑。

// 视频上传并插入编辑器核心方法
async change(e) {
  this.loading('上传中'); // 显示上传加载提示(需自行实现 loading 方法)
  let inscon = ''; // 存储待插入的视频块 HTML
  // 遍历选中的所有视频文件
  for (let i = 0; i < e.target.files.length; i++) {
    // 调用上传接口,返回视频在线地址(需自行实现 upload 方法)
    let url = await this.upload(e.target.files[i]);
    // 验证文件类型为视频,避免插入非视频文件
    if (/video/.test(e.target.files[0].type)) {
      // 构建自定义视频显示块
      inscon += `<p class='video-box' contenteditable="false">
        <!-- 播放按钮:存储视频地址,禁止编辑 -->
        <img class="playBtn" contenteditable="false" data-video-src="${url}" src="img/play.png"/>
        <!-- 视频封面:通过 OSS 处理生成视频首帧截图,禁止编辑 -->
        <img class="videoCover" contenteditable="false" src="${url}?x-oss-process=video/snapshot,t_1000,m_fast,ar_auto"/>
        </p>`;
    }
  }
  // 将自定义视频块插入编辑器
  this.getEditor().execCommand('mceInsertContent', false, inscon);
},

代码说明

  • async/await :处理异步上传逻辑,确保视频上传完成后再插入编辑器;
  • contenteditable="false" :核心属性,禁止视频块及内部元素被编辑;
  • data-video-src="${url}" :自定义属性存储视频真实地址,用于后续播放;
  • OSS 截图参数video/snapshot,t_1000 表示截取视频第 1000 毫秒的帧作为封面,需结合自身 OSS 配置调整;
  • mceInsertContent :TinyMCE 内置方法,将自定义 HTML 插入编辑器光标位置。

4. 清除图片选中后的默认边框样式

TinyMCE 中图片被选中时会默认显示边框,影响视觉体验,需通过 CSS 清除该样式。

/* 清除播放按钮选中后的默认边框 */
.mce-content-body img.playBtn[data-mce-selected] {
  outline: none;
}
/* 可选:清除视频封面选中后的边框 */
.mce-content-body img.videoCover[data-mce-selected] {
  outline: none;
}

代码说明

  • [data-mce-selected] :TinyMCE 为选中元素添加的专属属性;
  • outline: none :清除选中后的默认外边框,保持视频块样式统一。

5. 点击播放按钮弹窗播放视频

实现点击视频块中的播放按钮,弹窗播放完整视频的交互逻辑。

// 监听编辑器元素选中事件
editor.on('ObjectSelected', e => {
  // 判断选中的是播放按钮
  if (e.target.className === 'playBtn') {
    // 获取存储的视频地址
    this.plSrc = e.target.dataset.videoSrc;
    // 显示视频播放弹窗(Vue 中通过变量控制弹窗显示)
    this.plShow = true;
  }
});

配合 Vue 的视频播放弹窗组件(非 Vue 项目可替换为原生 DOM 操作):

<!-- 视频播放弹窗 -->
<div class="popup" v-if="plShow" @click.stop>
  <!-- 视频播放组件:自动播放,显示控制栏,设置封面 -->
  <video
    class="plVideo"
    style="height: 100%; width: 100%"
    :src="plSrc"
    :poster="plSrc + '?x-oss-process=image/resize,w_750,h_1200/format,jpg/interlace,1'"
    controls
    autoplay
  ></video>
  <!-- 关闭按钮:停止事件冒泡,避免点击关闭时触发弹窗其他逻辑 -->
  <img src="img/delete.png" class="plClose" @click.stop="plShow = false" />
</div>

代码说明

  • ObjectSelected :TinyMCE 监听元素选中的事件,替代原生 click 事件更适配编辑器;
  • @click.stop :阻止事件冒泡,避免点击弹窗内部元素时关闭弹窗;
  • poster :视频封面,通过 OSS 调整封面尺寸和格式,优化加载速度;
  • controls :显示视频控制栏, autoplay :点击后自动播放视频。

6. 移除封面时自动删除整个视频块

解决仅删除视频封面但保留空视频块的问题,监听编辑器节点变化,自动清理无效视频块。

// 监听编辑器节点变化事件
editor.on('NodeChange', ()=>{
  // 选中所有自定义视频块
  const videoBoxes = editor.dom.select('p.video-box');
  videoBoxes.forEach(box => {
    // 判断视频块内是否还存在封面图
    if (!/videoCover/.test(box.innerHTML)) {
      // 移除无封面的空视频块,false 表示不保留子节点
      editor.dom.remove(box, false);
    }
  });
});

代码说明

  • NodeChange :编辑器节点新增/删除/修改时触发的事件;
  • editor.dom.select :TinyMCE 内置方法,选中指定类名的元素;
  • editor.dom.remove :删除无效视频块,确保编辑器内容整洁。

三、样式补充(可选)

为确保视频块和播放弹窗的视觉效果,补充基础样式:

/* 视频块样式 */
.video-box {
  position: relative;
  display: inline-block;
  margin: 10px 0;
  border: 1px solid #eee;
  padding: 5px;
}
/* 播放按钮:居中悬浮显示 */
.playBtn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 40px;
  height: 40px;
  cursor: pointer;
}
/* 视频封面 */
.videoCover {
  max-width: 100%;
  height: auto;
}
/* 播放弹窗 */
.popup {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0,0,0,0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}
/* 关闭按钮 */
.plClose {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 30px;
  height: 30px;
  cursor: pointer;
}

四、注意事项

  1. 上传接口 upload 、加载提示 loading 、编辑器实例获取 getEditor 需结合自身项目实现;
  2. 图标路径( video.pngplay.pngdelete.png )需替换为项目实际路径;
  3. OSS 截图/压缩参数需根据自身云存储配置调整,无 OSS 可替换为视频封面生成的其他方案;
  4. 非 Vue 项目需将 @changev-if@click.stop 等 Vue 语法替换为原生 DOM 操作。

总结

  1. 核心关键:通过 contenteditable="false" 禁止视频块编辑,结合 NodeChange 事件清理无效节点,保证编辑器内容整洁;
  2. 交互优化:利用 TinyMCE 的 ObjectSelected 事件监听元素选中,替代原生点击事件更适配编辑器环境;
  3. 样式统一:通过 CSS 清除选中元素的默认边框,自定义视频块和播放弹窗样式,提升用户体验。

本文实现的自定义视频上传方案,既满足了个性化的视觉和交互需求,又解决了 TinyMCE 默认功能的痛点,可直接适配各类基于 TinyMCE 的内容编辑场景。


发布评论

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

评论列表 0

暂无评论