从零制作视频播放器——别人能搞得出来视频控件,我们能搞得更好!(第四章)

1. 前言

这篇是下面这篇文章的续章。因为上一篇文章篇幅过长,所以我强行让大家伙们休息一下,贴心吧?上一篇文章的链接在下面了哈~

从零制作视频播放器------别人能搞得出来视频控件,难道我们不能搞?(第三章)

上一篇文章我们讲到了 js 实现渐现、渐隐对吧?前面我们介绍的几种方法都实现不了,所以只能靠 js 来实现了。另外各位看客的点赞与收藏就是我源源不断的动力,有兴趣的也可以关注一下该专栏,如果有合适的功能需求,专栏也会进行相应的更新。为了大家,奥利给~ヾ(≧▽≦*)o

2. 实现渐现、渐隐

废话不多说,我们下面将用 js 来实现元素的渐现和渐隐,具体的效果如下图所示:

下面我将为大家讲解具体的思路,因为需求是不断变化的,我们得提高自身的编程水平,以变化适应变化,这个世界唯一不变的就是万物都是变化的。

仔细观察上面图片的效果,我也画了一张简图来理解,我们大概能得出初步的结论:

  • 鼠标移入触发元素,面板元素显示,移出则隐藏。
  • 鼠标移入、移出触发元素或者是面板元素的效果,与只移入、移出触发元素的效果相同。
  • 鼠标移入后移出,然后再快速移入时,透明度不会发生跳跃变化。

好了,初步的剖析就是上面这样,下面我们就开始发车了,老司机上路!

通常我们将渐现函数命名为 fadeIn,渐隐函数命名为 fadeOut。先写渐现,再写渐隐。

注意,下面的实现为了让大家好理解一些,删减了一些内容。所以如果想看完整的代码可以点击下面的链接。下面是一个效果预览地址。

渐现、渐隐函数 - 码上掘金 (juejin.cn)

2.1. 渐现函数实现

初步分析如果要实现 fadeIn 我们至少需要两个参数:要渐现的元素,过渡时间。

js 复制代码
function fadeIn(element, duration) {
    let computedStyle = window.getComputedStyle(element);
    // 首先得让元素在渲染树中,确保 display 不为 none
    if(computedStyle.display === "none") {
        // display 可能有其他的取值, 如 flex 
        element.style.display = "";
        element.style.opacity = "0";
        element.style.transition = `opacity ${duration}ms ease-in 0s`;
        // 回流、重绘,我们需要最新的样式
        element.clientHeight;
        computedStyle = window.getComputedStyle(element);
        // 如果还为 none,只能上大招了,加上 block
        if(computedStyle.display === "none") {
            element.style.display = "block";
            element.clientHeight;
        }
    } else {
        // 让 opacity 不发生跳跃变化, 不一定从 0 开始变为 1
        element.style.opacity = computedStyle.opacity;
    }
    
    // 变化至 1
    element.style.opacity = "1";
    // 过度结束后的操作,悄悄地我走了,正如我悄悄地来,我挥一挥衣袖,不带走一片云彩
    let transitionEnd = () => {
        element.style.transition = "";
        element.style.opacity = "";
        removeEvent();
    };
    let removeEvent = () => {
        element.removeEventListener("transitionend", transitionEnd);
    };
    element.addEventListener("transitionend", transitionEnd);
}

代码应该也不算很难,就是有些细节不容易考虑到。是不是以为到这就结束了?如果点击了上面的链接的朋友应该知道这段代码不是最终的代码。上面代码有一个 bug,之后会给补上,具体的原因还得先把 fadeOut 的函数给写完才能说得清楚。

2.2. 渐隐函数的实现

相比渐现,渐隐不需要考虑太多, 因为元素必定显示, 这里不考虑元素的 display 含有 !important 的情况。

js 复制代码
function fadeOut(element, duration) {
        let computedStyle = window.getComputedStyle(element);
        element.style.transition = `opacity ${duration}ms ease-in 0s`;
        // 防止透明度突然的变化
        element.style.opacity = computedStyle.opacity;
        element.clientHeight;
        element.style.opacity = "0";
        let transitionEnd = () => {
            element.style.display = "none";
            element.style.transition = "";
            element.style.opacity = "";
            removeEvent();
        };
        let removeEvent = () => {
            element.removeEventListener("transitionend", transitionEnd);
        };
        element.addEventListener("transitionend", transitionEnd);
    });
}

2.3. 分析 bug

最苦恼的莫过于辛苦写了好久,出来一个未知的 bug,能解决还好,不能解决的话那不得 茶不思,饭不想,有女也肌无力😂

有没有拿着上面的代码试过的?算了,还是我来吧!

js 复制代码
// board 是 container 的子元素
const container = document.querySelector(".container");
const board = document.querySelector(".board");

container.addEventListener("mouseenter", () => {
    window.fadeIn(board, 1000);
});

container.addEventListener("mouseleave", () => {
    window.fadeOut(board, 1000);
});

运行上面的代码我们能发现,当鼠标移入 container 时,在 board 还在过渡阶段快速将鼠标移入 board 时,board 会消失,即 display: none; 。

原因也很简单,就是鼠标移出时,由于 board 还在过渡,而 fadeOut 函数也在 board 上加了一个 transitionend 事件,使得 display 为 none。

解决方法也很简单,由于鼠标移出了 container 元素,然后又移入了 container 元素,所以我们可以在鼠标移入时,移除鼠标移出时添加的事件。

最终的代码就是大概下面这个样子,当然有些地方还是有点不同的,不过不影响理解。这里为了防止文章篇幅过长,就弄了个掘金的代码空间给大家查看。

渐现、渐隐函数 - 码上掘金 (juejin.cn)

3. 整合

不知大家阅读文章的感觉如何,有没有忘记我们最初的目的是为了什么?没有被我给搞晕吧,哈哈~ 要是给大家弄晕了,那真是罪过。

我们要实现的是下面的这个效果哈,就是控件面板的渐现和渐隐,而已,对吧?

相信和我一起走到这的小伙伴已经能将这个简单的效果实现了吧?

不知小伙伴们怎么看我给大家弄得这些分析,提供的这些思路,可能会有小伙伴觉得这会很啰嗦。其实我也不想写这么多,我只是不想随便敷衍大家,大家也不喜欢看没有深度的文章对吧?随便写的文章既敷衍了你们,又浪费了我的时间,所以我才尽可能地将好的内容呈现给大家。另外这也是正常的思路,为了实现一个功能,我们既要实现,也要思考有没有更好的解决办法,尝试各种奇怪的实现方式,力求完美,无可挑剔。

好了,前面我们说了实现该功能需要考虑三个方面:限流执行回调渐现、渐隐。目前都已经解决了。

下面开始整合!(≧∇≦)ノ

注意哈,这里的代码只是成功的一个雏形,因为还有其他的要考虑,比如视频暂停时还要不要隐藏控件。

html 复制代码
<!-- css 样式已省略 -->

<div class="video-container">
    <div class="video-instance">
        <video src="./spy.mp4" controls></video>
    </div>
    <div class="video-controls"></div>
</div>

<script>
    "use strict";
    
    // 节流函数、fadeIn、fadeOut 函数已省略
    
    const VIDEO_CONTROL_DURATION = 500;
    
    const videoContainer = document.querySelector(".video-container");
    const videoControls = document.querySelector(".video-controls");
    
    /**
     * 显示控件后执行的回调
     * @type {[function]}
     */
    const showControlsCallbackArr = [];
    /**
     * 隐藏控件后执行的回调
     * @type {[function]}
     */
    const hideControlsCallbackArr = [];
    
    /**
     * 控件是否显示
     * @type {boolean}
     */
    let controlsShow;
    
    /**
     * 显示节流, 节流就是在指定的时间内执行一次
     * @type {function(...[*]): *}
     */
    let showControlsThrottle = window.getThrottle(window.showControls, 100);
    
    let timer;

    /**
     * 直接显示控件, 并定时隐藏
     */
    function showControls() {
        window.showControlsNotScheduleHide();
        if (!pause) {
            window.scheduleHiddenControls();
        }
    }
    
    /**
     * 直接隐藏控件
     */
    function hiddenControls() {
        if (!enterControls) {
            window.fadeOut(videoControls, VIDEO_CONTROL_DURATION, videoContainer);
            controlsShow = false;
            window.execCallback(hideControlsCallbackArr);
        }
    }

    /**
     * 定时隐藏控件
     */
    function scheduleHiddenControls() {
        if (enterControls) {
            return;
        }
        // 这里我们后面可以用防抖来处理
        timer = window.setTimeout(window.hiddenControls, 2000);
    }

    /**
     * 鼠标在 videoContainer 元素中持续移动
     */
    function mousemoveInVideoContainer() {
        // 节流
        showControlsThrottle();
    }

    /**
     * 鼠标移入视频控件时触发, 清除隐藏控件的定时器
     */
    function mouseenterVideoControls() {
        enterControls = true;
        window.clearTimeout(timer);
    }

    /**
     * 鼠标移出视频控件时触发, 设置隐藏控件的定时器
     */
    function mouseleaveVideoControls() {
        enterControls = false;
        window.scheduleHiddenControls();
    }
    
    videoContainer.addEventListener("mousemove", window.mousemoveInVideoContainer);
    videoControls.addEventListener("mouseenter", window.mouseenterVideoControls);
    videoControls.addEventListener("mouseleave", window.mouseleaveVideoControls);
    
</script>

下面我做了一个小 demo,大家可以点击下面这个链接去查看最终的效果。

控件面板整合渐现,限流等 - 码上掘金 (juejin.cn)

好了,这个简单的效果到这就暂时告一段落了,可以看到这个简单的效果也是涉及多方面的。下面就是我的自我推销时间了~

大家的点赞和收藏就是我源源不断的动力,有兴趣的可以关注该专栏,如果有合适的需求该专栏也会进行更新,对我感兴趣的也可以关注我鸭~ 😋

希望这系列的文章能对大家有所帮助,当然如果看的人如果过不了两千我就不更了,热度不够我也不更了,毕竟写文章还是比较累的,平均每篇文章都要花三四个小时以上,因为我要写各种 demo,还要检查 demo 结果是否准确,毕竟不能误人子弟呀。

相关推荐
李昊哲小课2 分钟前
deepin 安装 chrome 浏览器
java·大数据·前端·chrome·python
ew452188 分钟前
【VUE】el-table表格内输入框或者其他控件规则校验实现
前端·javascript·vue.js
Lsx-codeShare8 分钟前
Uniapp 安装安卓、IOS模拟器并调试
android·前端·javascript·ios·微信小程序·小程序·uni-app
longlongago~~13 分钟前
DRM(数字权限管理技术)防截屏录屏----视频转hls流加密、web解密播放
前端·windows·node.js·音视频
爱上语文17 分钟前
Ajax入门程序
前端·javascript·css·ajax·html
北国13722 分钟前
python端拉流检测后再推流到流媒体服务器实现前端拉流查看(多摄像头切换)
服务器·前端·python·opencv·流媒体服务器
木子七24 分钟前
vue3-组件通信
前端·vue
乐闻x27 分钟前
Vue 是如何实现数据双向绑定的?
前端·vue.js
乐闻x28 分钟前
Vue 3 实现高性能拖拽指令的最佳实践
前端·javascript·vue.js
陈逸轩*^_^*40 分钟前
硅谷甄选前端项目环境配置笔记
前端·笔记