• Vue quill-editor富文本编辑器如何插入本地视频或者网络视频
  • 发布于 2个月前
  • 1125 热度
    0 评论
  • Pigeon
  • 1 粉丝 30 篇博客
  •   
背景:
公司现在需要富文本编辑器插入视频的功能,之前富文本编辑使用的是 Vue quill-editor 插件。
目标:
使用人员自由插入本地视频或者网络视频链接。如下图:

实现步骤:
提醒:本文章适合已经会基本使用 quill-editor 功能的读者。
一. 先实现基本插入视频的功能
在 toolbar 内配置 video 功能:
toolbar: {
    container: [
        ["bold", "italic", "underline", "strike"], // 加粗 斜体 下划线 删除线
        [{ header: 1 }, { header: 2 }], // 1、2 级标题
        [{ indent: "-1" }, { indent: "+1" }], // 缩进
        [{ direction: "rtl" }], // 文本方向
        [{ size: ["small", false, "large", "huge"] }], // 字体大小
        [{ header: [1, 2, 3, 4, 5, 6, false] }], // 标题
        [{ color: [] }, { background: [] }], // 字体颜色、字体背景颜色
        [{ font: [] }], // 字体种类
        [{ align: [] }], // 对齐方式
        ["clean"], // 清除文本格式
        ["image", "video"], // 图片, 视频
        ["emoji"], //表情包
        [{ lineHeight: lineHeightStyle.whitelist }], //行高
    ], // 工具菜单栏配置
    handlers: {
        lineHeight: (value) => {
            if (value) {
                let quill = this.$refs.myQuillEditor.quill;
                quill.format("lineHeight", value);
            }
        },
        video: (value) => {
            this.videoDialog.show = true;
        },
    },
},
点击 toolbar 内的 video 按钮时,弹出上传视频的弹框:
<!--视频上传 堆代码 duidaima.com-->
<div>
    <el-dialog :close-on-click-modal="false" width="50%" style="margin-top: 1px" title="视频上传" :visible.sync="videoDialog.show" append-to-body>
        <el-tabs v-model="videoDialog.activeName">
            <el-tab-pane label="添加视频链接" name="first">
                <el-input v-model="videoDialog.videoLink" placeholder="请输入视频链接" clearable></el-input>
                <el-button type="primary" size="small" style="margin: 20px 0px 0px 0px" @click="addVideoLink(videoDialog.videoLink)">添加 </el-button>
            </el-tab-pane>
            <el-tab-pane label="本地视频上传" name="second">
                <el-upload
                        v-loading="uploading"
                        style="text-align: center"
                        drag
                        :action="uploadUrl"
                        accept="video/*"
                        :data="videoFileData"
                        :before-upload="onBeforeUploadVideo"
                        :on-success="onSuccessVideo"
                        :on-error="onErrorVideo"
                        :multiple="false"
                >
                    <i class="el-icon-upload"></i>
                    <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
                </el-upload>
            </el-tab-pane>
        </el-tabs>
    </el-dialog>
</div>
视频上传的回调函数:
// --------- 视频相关 ---------
addVideoLink(videoLink) {
    if (!videoLink) return this.$message.error("请输入视频地址");
    this.videoDialog.show = false;
    let range = this.editor.selection.savedRange;
    let index = 0;
    if (range != null) {
            index = range.index;
    }
    // 插入
    this.editor.insertEmbed(index, "video", videoLink);
    // 调整光标到最后
    this.editor.setSelection(index + 1);
},

// el-文件上传组件
onBeforeUploadVideo(file) {
    this.loading = true;
    let acceptArr = ["video/mp4"];
    const isVideo = acceptArr.includes(file.type);
    if (isVideo) {
        console.log(file.name);
        //笔者公司的视频托管在七牛云,读者按照自己公司的业务配置
        return api.getQiniuToken().then((res) => {
            // 配置上传的七牛token
            this.videoFileData = {
                token: res.retInfo,
                name: file.name,
                key: Math.floor(Math.random() * 100 + 1) + file.name,
            };
        });
    } else {
        return false;
    }
},
onSuccessVideo(res) {
    this.uploading = false;
    //笔者公司的视频托管在七牛云,读者按照自己公司的业务配置
    const videoUrl = "xxx" + res.retInfo;
    this.addVideoLink(videoUrl);
},
onErrorVideo(file, fileList) {
    this.uploading = false;
    this.$message.error("上传失败");
},

// --------- 视频相关 ---------
重点:addVideoLink的功能是将视频链接插入到quill-editor编辑框内,let range = this.editor.selection.savedRange; 是计算编辑框光标的位置(其中this.editor = this.$refs.myQuillEditor.quill)。刚开始用的是this.editor.getSelection(),第一次插入视频时,获取的range总是为null,换成this.editor.selection.savedRange后,获取range就正常了。
二.适配插入视频的布局
插入视频默认用的是iframe标签,插入竖屏视频后,样式如下:

生成的富文本代码如下:
<iframe class="ql-video" frameborder="0" allowfullscreen="true" src="https://xxx.com/65IMG_3870.mp4"></iframe><p><br></p>
将iframe标签换成 html5 内的video标签:
先创建 video.js 文件:
import { Quill } from "vue-quill-editor";
// 堆代码 duidaima.com
// 源码中是import直接导入,这里要用Quill.import引入
const BlockEmbed = Quill.import("blots/block/embed");
const Link = Quill.import("formats/link");

const ATTRIBUTES = ["height", "width"];

class Video extends BlockEmbed {
    static create(value) {
        const node = super.create(value);
        // 添加video标签所需的属性
        node.setAttribute("controls", "controls");
        node.setAttribute("type", "video/mp4");
        node.setAttribute("src", this.sanitize(value));
        //为了兼容 iOS 设备上,显示海报图(视频封面)
        node.setAttribute("preload", "metadata");
        return node;
    }

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

    static sanitize(url) {
        return Link.sanitize(url); // eslint-disable-line import/no-named-as-default-member
    }

    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);
        }
    }

    html() {
        const { video } = this.value();
        return `<a href="${video}">${video}</a>`;
    }
}
Video.blotName = "video";
Video.className = "ql-video";
Video.tagName = "video"; // 用video标签替换iframe
export default Video;
将 video.js 引入到 quill-editor 所在文件:
import Video from "../utils/video";
Quill.register(Video, true);
将入视频后,样式如下:

生成的富文本代码如下:
<video class="ql-video" controls="controls" type="video/mp4" src="https://xxx.com/65IMG_3870.mp4"></video><p><br></p>
生成的iframe标签页面和video标签页面效果对比:
iframe标签页面

video标签页面

三.video标签替换iframe标签后,出现新的问题
在iOS设备上,html内嵌入的视频默认只显示一个大大的播放按钮,不显示海报图(封面图)。在其他系统(windows、mac、android等)没问题。
排查问题:
首先:在渲染加载富文本内容时,我们可以为视频的video标签设置poster属性,指定视频第一帧为海报图:
//富文本视频第一帧作为封面
videoCover(item, url) {
    const video = item; // 获取视频对象
    let canvas = document.createElement("canvas"); //创建canvas
    video.src = url; // video的url地址
    const ctx = canvas.getContext("2d"); // 绘制2d
    video.crossOrigin = "anonymous"; // 解决跨域问题
    video.currentTime = 1; // 视频第一帧
    video.addEventListener('loadeddata', function () {
        canvas.width = video.clientWidth; // 视频宽度
        canvas.height = video.clientHeight; //视频高度
        // 利用canvas对象方法绘图
        ctx.drawImage(video, 0, 0, video.clientWidth, video.clientHeight);
        // 转换成base64形式并设置封面
        video.poster = canvas.toDataURL("image/jpeg", 1); // 截取后的视频封面
    });
},
//在vue的updated函数中调用videoCover函数:
updated() {
     //在vue中使用document.querySelectorAll时,必须在mounted、updated等函数中调用,才能获取到document内容
    const videos = document.querySelectorAll("video");
    videos.forEach((item) => {
            this.videoCover(item, item.src);
    });
},
实现上述的想法,打开调试时,发现video标签内并没有设置poster属性。打断点调试,发现在iOS设备上,根本没有调用loadeddata方法。

在stackoverflow上,发现有开发者回答笔者的疑惑。大概意思是:在iOS设备上,video标签不会默认加载视频数据。

然后此回答下面有其他开发者回答,为video标签添加preload="metadata"属性,就可以在iOS设备上显示海报图了。


笔者在video.js文件,默认为video标签添加preload="metadata"属性,解决此问题。

问题完美解决。
总结:
1:quill-editor配置video功能。
2:插入视频默认使用的是iframe标签,竖屏视频显示效果比较差。使用video标签替换iframe标签。
3:解决video标签在iOS设备上不显示海报图(封面图)的问题。

用户评论