React Quill 上传视频功能及 iframe 响应式缩放的解决方案

作者:frank 发表日期:2017-07-02 12:46:49 更新日期:2017-07-02 12:46:49 分类:猿文色

摘要

React Quill 上传视频功能及 iframe 响应式缩放的解决方案

正文

React Quill 是一个 封装了 Quill 富文本编辑器, 其默认的插入视频操作是点击图标弹出一个文本框, 输入视频的地址.

如果我们要实现点击视频图标直接上传文件的功能, 就必须自定义工具栏, 在文件上传成功后必须手动将文件地址插入到文本编辑器中. 自定义工具栏可以参考 这里, 手动将文本插入编辑器可以参考 这里. 其实 Quill 默认的图片插入操作时将图片转换为 base64, 但是当图片尺寸比较大的时候, 转换就比较浪费时间, 最后生成的 HTML 文档也会比较大, 这时候也可以自定义图片上传按钮, 在图片选择完成后判断图片的 size 大小, 大于 8KB 的选择上传模式, 小于 8KB 的选择 base64 模式.

Quill 在插入视频的时候, 会使用 iframe 包装起来, 这样如果后台直接返回视频地址, iframe 的 src 就是这个视频地址, iframe 内嵌文档会生成 video 标签, 默认带 controls, 默认 autoplay. 但是为什么 Quill 在插入视频的时候不直接选择 video 标签呢, 我尝试自定义一个 Quill formats, 在插入视频的时候直接使用 video 标签, 如下代码:

import Quill from 'quill';

const Embed = Quill.import('blots/embed');
const Link = Quill.import('formats/link');

const ATTRIBUTES = [
  'height',
  'width',
  'autoplay',
  'preload',
  'controls'
];

class H5Video extends Embed {
  static create(value) {
    let node = super.create(value);
    if(typeof value === 'string') {
      node.setAttribute('src', this.sanitize(value));
    } 
    return node;
  }

  static formats(domNode) {
    return ATTRIBUTES.reduce(function(formats, attribute) {
      if (domNode.hasAttribute(attribute)) {
        formats[attribute] = domNode.getAttribute(attribute);
      }
      return formats;
    }, {});
  }

  static sanitize(url) {
    return Link.sanitize(url);
  }

  static value(domNode) {
    return domNode.getAttribute('src');
  }

  format(name, value) {
    if (ATTRIBUTES.indexOf(name) > -1) {
      if (value) {
        this.domNode.setAttribute(name, value);
      } else {
        this.domNode.removeAttribute(name);
      }
    } else {
      super.format(name, value);
    }
  }
}
H5Video.blotName = 'h5-video';
H5Video.className = 'ql-video';
H5Video.tagName = 'VIDEO';

export default H5Video;

默认的 iframe 方式只定义了 width 和 height 属性, 我在自定义的属性中加入了 autoplay, preload 和 controls 来设置视频的初始化参数. 通过导入这个 format, 的确可以在编辑器中插入可控参数视频, 但是有一个很严重的问题, 插入视频后, 编辑器无法获得焦点, 无法输入文本内容, 有一个很挫的解决方法是在插入视频时, 同时在视频的上面和下面插入几个换行符, 这样换行符所在的地方就可以获得焦点, 但是我们不能阻止用户删除换行符, 所以这个方法行不通. 为什么会出现插入原生 video 标签编辑器无法获得焦点的问题? 这个就涉及到 contenteditable 属性, 具体来说, 富文本编辑器的实现原理是通过设置 wrapper 的 contenteditable 属性, 使用一些浏览器支持的 commands 插入一些特定元素, 具体的 commands 可以参考 这里, 这些命令规定了如何插入文本, 图片, html, 但是没有说明如果插入原生 video, 根本原因可能是 这些. 所以最终我还是选择使用 iframe 插入视频. 使用 iframe 插入视频就要解决在页面缩放的时候 iframe 可以根据内部文档中视频文件的比例进行缩放, 这个时候通过简单的 CSS 是无法解决问题的, 网上有一个很有意思的方案, 是在插入 iframe 的同时, 使用一个 div wrapper 将 iframe 包起来, 顺便插入一个和视频尺寸相同的 img, 将 iframe 的 position 设置为 absolute, width: 100%, height: 100%, 因为 img 只要设置了 width: 100%, 其高度就可以根据比例自适应, 但是不适用 Quill.

我的解决方案是:

  • 在 iframe 中监控 parent window 的 resize 事件;
  • 如果 parent window 的 size 发生变化, 通过 video 的比例设置 iframe 的大小;
  • 所有的逻辑都处于 iframe 中, parent window 不需要做任何特殊处理.

要实现上述方案, 首先要解决的就是插入视频的地址不能是 cdn 的地址, 否则会出现跨域. 所以在项目中新建了一个静态 HTML 文件 "video.html", 上传完成后将静态文件的地址插入到编辑器中, params 是视频的真实地址. 这样我们就可以在 video.html 中拿到 src 地址, 继而获得视频尺寸, 如何获得视频尺寸, 可以参考下面代码:

const video = document.createElement('video');
video.src = src;
video.preload = 'preload';
video.onloadedmetadata = () => {
    const videoOrgWidth = video.videoWidth;
    const videoOrgHeight = video.videoHeight;
};

接下来的操作就是在 video.html 中通过 window.parent 获取 parent window, 绑定 resize 事件; 通过 window.parent.document.querySelectorAll('iframe.ql-video') 获取 parent window 中的 iframe; 在 resize 发生时, 先获取 iframe parentElement 的 offsetWidth 属性, 通过真实视频比例设置 iframe 的 width 和 height.

全文完.