vue实现一个鼠标滑动预览视频封面组件

简介: vue实现一个鼠标滑动预览视频封面组件

说在前面

🎈不知道大家平时在逛B站的时候有没有发现这么一个功能?在视频封面移入鼠标时我们可以对视频进行预览,预览过后再决定时候要点进去观看视频,那么这个实现具体是怎么实现的呢?让我们一起动手来试一下吧。

效果预览

B站效果

组件效果

体验地址

组件实现

组件设计

我们首先应该要对组件进行一个简单的设计。

主要的逻辑如上图☝️☝️☝️,可以拆分成这么几个步骤:

1、视频截取关键帧

我们可以先将视频各个时间的关键帧截图保存,具体截取帧数可以使用传入参数控制。

2、鼠标移入封面时显示对应关键帧

在鼠标移入的时候我们应该要计算当前鼠标位置和视频宽度的比例关系,然后从视频帧列表中获取到对应的图片作为当前的视频封面图片。

3、视频和封面的状态切换

这里我们是将用两个元素分别作为视频和封面,所以我们状态切换的时候需要控制两个元素的显示和隐藏。

  • 点击封面

显示并播放视频,隐藏封面。

  • 暂停播放

显示封面,隐藏视频。

功能实现

分析完组件的关键步骤之后我们便可以开始动手来实现相应的功能了。

1、视频截取关键帧图片列表
1.1 截取指定帧

视频关键帧的截取我们可以使用canvas来实现,具体实现方法如下:

/** 
 * @param {element} video 
 * @param {number} currentTime
 * @return {void}
 */
cutCover(video, currentTime) {
    video.currentTime = currentTime;
    const canvas = document.createElement("canvas");
    let ctx = canvas.getContext("2d");
    canvas.width = parseInt(this.width);
    canvas.height = parseInt(this.height);
    ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
    const img = canvas.toDataURL("image/png");
    return img;
},

通过该函数我们可以获取指定时间的视频图片帧。

  • 传入参数
/** 
 * @param {element} video 
 * @param {number} currentTime
 */

video为需要截取视频的dom元素,currentTime为要截取图片帧的时间点。

  • 返回参数
/** 
 * @return {Base64} img
 */

返回参数为截取的指定帧的Base64格式的图片。

1.2 截取stepNums张关键帧图片

stepNums为我们传入的组件参数,及需要截取的封面关键帧图片数量,数量越多,预览的效果越连贯,可以根据视频长度来调整截取的张数。

init(){
    const videoContentShow = document.getElementById(
        this.uid + "-video"
    );
    videoContentShow.style.height = this.height;
    videoContentShow.style.width = this.width;
    const videoContent = videoContentShow.cloneNode();
    videoContent.addEventListener("canplay", () => {
        if (this.currentTime < this.duration) this.cut(videoContent);
        else this.progressValue = 0;
    });
}
cut(video) {
    const duration = video.duration;
    this.duration = duration;
    this.currentTime += duration / this.stepNums;
    const img = this.cutCover(video, this.currentTime);
    this.imgList.push(img);
    if (this.imgList.length == 2) {
        this.coverSrc = img;
        const coverImg = document.getElementById(
            this.uid + "-coverImg"
        );
        coverImg.setAttribute("src", img);
    }
}

具体代码如上,首先我们应该先要获取到视频的dom元素,但是要注意:我们不在原始视频元素上进行截取操作,我们这里使用了cloneNode()来克隆一个dom元素进行操作。因为在进行截取的时候我们需要对视频的currentTime属性进行一个修改,也就是改变视频的播放进度,如果在原视频上截取的话,在未截取完成前播放视频会导致视频播放进度混乱,所以这里我们在克隆元素对象上进行操作。

我们总共需要截取stepNums张图片,所以每次截取的时间间隔应该为:duration / this.stepNums,即视频总时间长度/截取图片张数,循环截取即可。

2、鼠标移入封面时显示对应关键帧

鼠标移入封面的时候我们需要对封面图片进行切换。

2.1 鼠标移动事件监听
<img
    :id="uid + '-coverImg'"
    :src="coverSrc"
    class="j-coverImg"
    @mousemove="imgHover"
    @mouseleave="hoverOut"
    @click="coverClick"
/>

这里我们使用vue中的mousemovemouseleave对鼠标事件进行监听。

imgHover(e) {
    const coverImg = document.getElementById(this.uid + "-coverImg");
    const w = coverImg.offsetWidth / this.stepNums;
    const x = e.offsetX - coverImg.offsetLeft;
    const index = Math.min(
        Math.max(Math.ceil(x / w), 1),
        this.stepNums
    );
    if (this.imgList.length < index) return;
    this.progressValue = index;
    coverImg.setAttribute(
        "src",
        this.imgList[Math.min(this.imgList.length - 1, index)]
    );
},

鼠标移入的时候我们需要根据鼠标的坐标位置来计算展示的帧数下标,具体计算如下:

  • 每张图片展示的区间大小
const w = coverImg.offsetWidth / this.stepNums;

每个区间的大小我们只需要将封面的宽度除于图片帧列表的数量即可得到每张图片展示的区间大小。

  • 当前鼠标所在区间
const x = e.offsetX - coverImg.offsetLeft;
const index = Math.min(
    Math.max(Math.ceil(x / w), 1),
    this.stepNums
);

首先我们应该要计算当前鼠标在封面里的相对位置,这里我们只需要其横坐标x即可,然后将坐标除于区间大小,我们即可得到当前坐标所对应的区间下标。这里的最大值应该进行限制为1stepNums

2.2 鼠标移出事件监听

鼠标移出的时候我们需要将封面恢复成当前视频的封面。

hoverOut(e) {
    const coverImg = document.getElementById(this.uid + "-coverImg");
    const step = this.duration / this.stepNums;
    const index = Math.ceil(this.pauseTime / step);
    this.progressValue = index;
    coverImg.setAttribute("src", this.pauseCover || this.coverSrc);
},
3、视频和封面的状态切换

封面和视频的显示隐藏需要根据播放状态来进行对应的切换。

3.1 播放视频

点击封面的时候播放视频,需要隐藏封面及相关的进度条并显示视频

doHide(hide = false) {
    const videoContent = document.getElementById(this.uid + "-video");
    videoContent.style.display = hide ? "block" : "none";
    videoContent.currentTime = this.pauseTime;
    hide ? videoContent.play() : videoContent.pause();
    const img = document.getElementById(this.uid + "-coverImg");
    img.style.display = hide ? "none" : "block";
    const progress = document.getElementById(this.uid + "-progress");
    progress.style.display = hide ? "none" : "block";
    const progress1 = document.getElementById(this.uid + "-progress1");
    progress1.style.display = hide ? "none" : "block";
},
coverClick() {
    this.doHide(true);
},
3.2 视频暂停

视频暂停时我们需要隐藏视频,截取当前帧作为封面并显示封面及相关的进度条。

videoContentShow.addEventListener("pause", e => {
    this.pauseTime = videoContentShow.currentTime;
    this.pauseCover = this.cutCover(
        videoContentShow,
        videoContentShow.currentTime
    );
    coverImg.setAttribute("src", this.pauseCover);
    const step = this.duration / this.stepNums;
    const index = Math.ceil(this.pauseTime / step);
    this.progressValue = index;
    setTimeout(() => {
        if (videoContentShow.paused) this.doHide();
    }, 200);
});

这里我使用了一个setTimeout来进行一个延时控制,大家知道为什么吗?因为视频有两种操作会触发视频的pause事件:

  • 点击暂停按钮
  • 拉动进度条

这里拉动进度条的时候会触发视频的pause事件并且马上继续播放,所以我们应该要过滤掉这一情况。

组件使用

<template>
    <div class="content">
        <div class="video-list">
            <j-video-cover
                class="video"
                :videoUrl="videoUrl"
                stepNums="40"
            ></j-video-cover>
        </div>
    </div>
</template>
<script>
    export default {
        data() {
            return {
                videoUrl: require("../../assets/video/202112250058.mp4"),
            }
        }
    }
</script>

组件库引用

这里我将这个组件打包进了自己的一个组件库,并将其发布到了npm上,有需要的同学也可以直接引入该组件进行使用。

引入教程可以看这里:http://jyeontu.xyz/jvuewheel/#/installView

引入后即可直接使用。

源码地址

组件库已开源,想要查看完整源码的可以到 gitee 查看,自己也整理了相关的文档对其进行了简单介绍,具体如下:

组件文档

jvuewheel: jyeontu.xyz/jvuewheel/#…

Gitee源码

Gitee源码:gitee.com/zheng_yongt…

觉得有帮助的同学可以帮忙给我点个star,感激不尽~~~

有什么想法或者改良可以给我提个pr,十分欢迎~~~

有什么问题都可以在评论告诉我~~~

往期精彩

密码太多不知道怎么记录?不如自己写个密码箱小程序

微信小程序实现一个手势图案锁组件

vue封装一个图案手势锁组件

vue封装一个弹幕组件

为了学(mo)习(yu),我竟开发了这样一个插件

程序员的浪漫之——情侣日常小程序

vue简单实现词云图组件

vue + echarts实现中国地图省份下钻联动

使用学过的算法做个游戏很酷的好吗

说在后面

🎉这里是JYeontu,喜欢算法,GDCPC打过卡;热爱羽毛球,大运会打过酱油。毕业一年,两年前端开发经验,目前担任H5前端开发,算法业余爱好者,有空会刷刷算法题,平时喜欢打打羽毛球🏸 ,也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。

目录
相关文章
|
6天前
|
JavaScript 前端开发
如何在 Vue 项目中配置 Tree Shaking?
通过以上针对 Webpack 或 Rollup 的配置方法,就可以在 Vue 项目中有效地启用 Tree Shaking,从而优化项目的打包体积,提高项目的性能和加载速度。在实际配置过程中,需要根据项目的具体情况和需求,对配置进行适当的调整和优化。
|
6天前
|
存储 缓存 JavaScript
在 Vue 中使用 computed 和 watch 时,性能问题探讨
本文探讨了在 Vue.js 中使用 computed 计算属性和 watch 监听器时可能遇到的性能问题,并提供了优化建议,帮助开发者提高应用性能。
|
6天前
|
存储 缓存 JavaScript
如何在大型 Vue 应用中有效地管理计算属性和侦听器
在大型 Vue 应用中,合理管理计算属性和侦听器是优化性能和维护性的关键。本文介绍了如何通过模块化、状态管理和避免冗余计算等方法,有效提升应用的响应性和可维护性。
|
5天前
|
JavaScript 前端开发 UED
vue学习第二章
欢迎来到我的博客!我是一名自学了2年半前端的大一学生,熟悉JavaScript与Vue,目前正在向全栈方向发展。如果你从我的博客中有所收获,欢迎关注我,我将持续更新更多优质文章。你的支持是我最大的动力!🎉🎉🎉
|
5天前
|
JavaScript 前端开发 开发者
vue学习第一章
欢迎来到我的博客!我是瑞雨溪,一名热爱JavaScript和Vue的大一学生。自学前端2年半,熟悉JavaScript与Vue,正向全栈方向发展。博客内容涵盖Vue基础、列表展示及计数器案例等,希望能对你有所帮助。关注我,持续更新中!🎉🎉🎉
|
20天前
|
数据采集 监控 JavaScript
在 Vue 项目中使用预渲染技术
【10月更文挑战第23天】在 Vue 项目中使用预渲染技术是提升 SEO 效果的有效途径之一。通过选择合适的预渲染工具,正确配置和运行预渲染操作,结合其他 SEO 策略,可以实现更好的搜索引擎优化效果。同时,需要不断地监控和优化预渲染效果,以适应不断变化的搜索引擎环境和用户需求。
|
6天前
|
存储 缓存 JavaScript
Vue 中 computed 和 watch 的差异
Vue 中的 `computed` 和 `watch` 都用于处理数据变化,但使用场景不同。`computed` 用于计算属性,依赖于其他数据自动更新;`watch` 用于监听数据变化,执行异步或复杂操作。
|
7天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
7天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
12天前
|
JavaScript
Vue基础知识总结 4:vue组件化开发
Vue基础知识总结 4:vue组件化开发