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

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 结果是否准确,毕竟不能误人子弟呀。

相关推荐
GISer_Jing2 小时前
前端面试通关:Cesium+Three+React优化+TypeScript实战+ECharts性能方案
前端·react.js·面试
落霞的思绪3 小时前
CSS复习
前端·css
咖啡の猫5 小时前
Shell脚本-for循环应用案例
前端·chrome
百万蹄蹄向前冲7 小时前
Trae分析Phaser.js游戏《洋葱头捡星星》
前端·游戏开发·trae
朝阳5818 小时前
在浏览器端使用 xml2js 遇到的报错及解决方法
前端
GIS之路8 小时前
GeoTools 读取影像元数据
前端
ssshooter9 小时前
VSCode 自带的 TS 版本可能跟项目TS 版本不一样
前端·面试·typescript
你的人类朋友9 小时前
【Node.js】什么是Node.js
javascript·后端·node.js
Jerry9 小时前
Jetpack Compose 中的状态
前端
dae bal10 小时前
关于RSA和AES加密
前端·vue.js