[译] 网速敏感的视频延迟加载方案

简介: 一个大视频的背景,如果做的好,会是一个绝佳的体验!但是,在首页添加一个视频并不仅仅是随便找个人,然后加个 25mb 的视频,那会让你的所有的性能优化都付之一炬。Lazy pandas love lazy loading. (Photo by Elena Loshina)我参加过一些团队,他们希望给首页加上类似的全屏视频背景。我通常不愿意那么做,因为这种做法通常会导致性能上的噩梦。老实说,我曾给一个页面加上一个 40mb 大的视频。 😬上次有人让我这么做的时候,我很好奇应如何将背景视频的加载作为渐进增强(Progressive Enhancement),来提升网络连接状况比较好的用户的

微信截图_20220426225439.png


一个大视频的背景,如果做的好,会是一个绝佳的体验!但是,在首页添加一个视频并不仅仅是随便找个人,然后加个 25mb 的视频,那会让你的所有的性能优化都付之一炬。


微信截图_20220426225446.png


Lazy pandas love lazy loading. (Photo by Elena Loshina)


我参加过一些团队,他们希望给首页加上类似的全屏视频背景。我通常不愿意那么做,因为这种做法通常会导致性能上的噩梦。老实说,我曾给一个页面加上一个 40mb 大的视频。 😬


上次有人让我这么做的时候,我很好奇应如何将背景视频的加载作为渐进增强(Progressive Enhancement),来提升网络连接状况比较好的用户的体验。除了和我的同事们强调视频体积小和压缩视频的重要性以外,也希望在代码上有一些奇迹发生。


下面是最终的解决方案:


  1. 尝试使用 JavaScript 加载 <source>
  2. 监听 canplaythrough 事件
  3. 如果 canplaythrough 事件没有在 2 秒内触发,那么使用 Promise.race() 将视频加载超时
  4. 如果没有监听到 canplaythrough 事件,那么移除 <source>,并且取消视频加载
  5. 如果监测到 canplaythrough 事件,那么使用淡入效果显示这个视频


标记


这里要注意的问题是,即使我正在 <video> 标签中使用 <source>,但我还没为这些 <source> 设置 src 属性。如果设置了 src 属性,那么浏览器会自动地找到它可以播放的第一个 <source>,并立即开始下载它。


因为在这个例子中,视频是作为渐进增强的对象,默认情况下我们不用真的加载视频。事实上唯一需要加载的,是我们为这个页面设置的预览图片。


<video class="js-video-loader" poster="<?= $poster; ?>" muted="true" loop="true">
    <source data-src="path/to/video.webm" type="video/webm">
    <source data-src="path/to/video.mp4" type="video/mp4">
  </video>
复制代码


JavaScript


我编写了一个简单的 JavaScript 类,用于查找带有 .js-video-loader 这个 class 的 video 元素,让我们以后可以在其他视频中复用这个逻辑。完整的源码可以从 Github 上看到


构造函数是这样的:


constructor () {
    this.videos = Array.from(document.querySelectorAll('video.js-video-loader'));
    // 将在下面情况下返回
    // - 浏览器不支持 Promise
    // - 没有 video 元素
    // - 如果用户设置了减少动态偏好(prefers reduced motion) 
    // - 在移动设备上
    if (typeof Promise === 'undefined'
      || !this.videos
      || window.matchMedia('(prefers-reduced-motion)').matches
      || window.innerWidth < 992
    ) {
      return;
    }
    this.videos.forEach(this.loadVideo.bind(this));
  }
复制代码


这里我们所做的就是找到这个页面上所有我们希望延迟加载的视频。如果没有,我们可以返回。当用户开启了减少动态偏好(preference for reduced motion)设置时,我们同样不会加载这样的视频。为了不让某些低网速或低图形处理能力的手机用户担心,在小屏幕手机上也会直接返回。(我在考虑是否可以通过 <source> 元素的媒体查询来做这些,但也不确定。)


然后给每个视频运行这个视频加载逻辑。


loadVideo


loadVideo() 是一个调用其他函数的简单的函数:


loadVideo(video) {
    this.setSource(video);
    // 加上了视频链接后重新加载视频
    video.load();
    this.checkLoadTime(video);
  }
复制代码


setSource


setSource() 中,我们找到那些作为数据属性(Data Attributes)插入的视频链接,并且将它们设置为真正的 src 属性。


/**
    * 找 video 子元素中是 <source> 的,
    * 基于 data-src 属性,
    * 给每个 <source> 设置 src 属性
    *
    * @param {DOM Object} video
    */
    setSource (video) {
      let children = Array.from(video.children);
      children.forEach(child => {
        if (child.tagName === 'SOURCE' && typeof child.dataset.src !== 'undefined') {
          child.setAttribute('src', child.dataset.src);
        }
      });
    }
复制代码


基本上,我所做的就是遍历每一个 <video> 元素的子元素,找一个定义了 data-src 属性(child.dataset.src)的 <source> 子元素。如果找到了,那就用 setAttribute 将它的 src 属性设置为视频链接。


现在视频链接已经被设置给 <video> 元素了,下面需要让浏览器再次加载视频。我们通过在 loadVideo() 中的 video.load() 来完成这个工作。load() 方法是 HTMLMediaElement API 的一部分,它可以重置媒体元素并且重启加载过程。


checkLoadTime


接下来是见证奇迹的时刻。在 checkLoadTime() 方法中我们创建了两个 Promise。第一个 Promise 将在 <video> 元素的 canplaythrough  事件触发时被 resolve。这个 canplaythrough 事件是浏览器认为这个视频可以在不停下来缓冲的情况下持续播放的时候被触发。我们在这个 Promise 中添加一个这个事件的监听回调,当这个事件触发的时候执行 resolve()


// 创建一个 Promise,将在
  // video.canplaythrough 事件发生时被 resolve
  let videoLoad = new Promise((resolve) => {
    video.addEventListener('canplaythrough', () => {
      resolve('can play');
    });
  });
复制代码


我们同时创建另一个 Promise 作为计时器。在这个 Promise 中,当经过一个设定好的时间后,我们使用 setTimeout 来将这个 Promise 给 resolve 掉,我这设置了一个 2 秒的时延(2000毫秒)。


// 创建一个 Promise 将在
  // 特定时间(2s)后被 resolve
  let videoTimeout = new Promise((resolve) => {
    setTimeout(() => {
      resolve('The video timed out.');
    }, 2000);
  });
复制代码


现在我们有了两个 Promise,我们可以通过 Promise.race() 看他们谁先完成。


// 将 promises 进行 Race 看看哪个先被 resolves
  Promise.race([videoLoad, videoTimeout]).  then(data => {
    if (data === 'can play') {
      video.play();
      setTimeout(() => {
        video.classList.add('video-loaded');
      }, 3000);
    } else {
      this.cancelLoad(video);
    }
  });
复制代码


在这个 .then() 的回调中我们等着拿到最先被 resolve 的那个 Promise 传回来的信息。如果这个视频可以播放,那么我就会拿到之前传的 can play,然后试一下是否可以播放这个视频。video.play() 是使用 HTMLMediaElement 提供的 play() 方法来触发视频播放。


3 秒后,setTimeout() 将会给这个标签加上 .video-loaded 类,这将有助于视频文件更巧妙的淡入自动循环播放。


如果我们没接收到 can play 字符串,那么我们将取消这个视频的加载。


cancelLoad


cancelLoad() 方法做的基本上跟 loadVideo() 方法相反。它从每个 source 标签移除 src 属性,并且触发 video.load() 来重置视频元素。


如果我们不这么做,这个视频元素将会在后台保持加载状态,即使我们都没将它显示出来。


/**
    * 通过移除所有的 <source> 来取消视频加载
    * 然后触发 video.load().
    *
    * @param {DOM object} video
    */
    cancelLoad (video) {
      let children = Array.from(video.children);
      children.forEach(child => {
        if (child.tagName === 'SOURCE' && typeof child.dataset.src !== 'undefined') {
          child.parentNode.removeChild(child);
        }
      });
      // 重新加载没有 <source> 标签的 video
      // 这样它会停止下载
      video.load();
    }
复制代码


总结


这个方法的缺点是,我们仍然试图通过一个不一定靠谱的链接来下载一个可能比较大的文件,但是通过提供一个超时时间,我们希望能够给某些网速慢的用户节约一些流量并且获得更好的性能。根据我在 Chrome Dev Tools 里将网速节流到慢 3G 条件下的测试,这个方法将在超时之前加载了 512kb 的视频。即使是一个 3-5mb 的视频,对于一些网速慢的用户来说,这也带来了显著的流量节省。


你觉得怎么样?如果有改进的建议,欢迎在评论里分享!



Originally published atbenrobertson.io.

如果发现译文存在错误或其他需要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可获得相应奖励积分。文章开头的 本文永久链接 即为本文在 GitHub 上的 MarkDown 链接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏



相关文章
|
18天前
|
缓存 NoSQL Java
揭秘性能提升的超级武器:掌握Hibernate二级缓存策略!
【9月更文挑战第3天】在软件开发中,性能优化至关重要。使用Hibernate进行数据持久化的应用可通过二级缓存提升数据访问速度。一级缓存随Session生命周期变化,而二级缓存是SessionFactory级别的全局缓存,能显著减少数据库访问次数,提高性能。要启用二级缓存,需在映射文件或实体类上添加相应配置。然而,并非所有场景都适合使用二级缓存,需根据业务需求和数据变更频率决定。此外,还可与EhCache、Redis等第三方缓存集成,进一步增强缓存效果。合理运用二级缓存策略,有助于大幅提升应用性能。
35 5
|
4月前
|
缓存 前端开发 JavaScript
|
2月前
|
存储 JSON JavaScript
小程序优化:第三方SDK过大解决方案
小程序开发中,项目目录中存放过大的js包,会被警告影响手机端性能,同时让开发编译启动变得很慢。慢是其次,单是影响性能这一点,就需要解决一下。
|
4月前
显示广告的几种方案及缺点
显示广告的几种方案及缺点
32 0
|
4月前
|
前端开发 算法 JavaScript
如何优化前端性能:探索图片压缩与延迟加载技术
本文深入探讨了前端性能优化中的关键问题:图片压缩与延迟加载技术。通过介绍图片压缩的原理和方法,并结合实例说明了如何有效减少图片大小、提升加载速度;同时,详细解析了延迟加载技术的实现原理及其在提高页面加载性能中的作用,为前端开发者提供了实用的优化方案。
|
存储 缓存 前端开发
意外之惊喜!浏览器缓存优化方案,让页面加载速度飙升48.5%!
经过对浏览器缓存优化方案的调研和实现过程,我发现了一个令人意外的发现:**页面加载速度提升了整整48.5%!** 这个令人震撼的结果在微前端架构项目中具有重要意义,同时虽然本文是针对微前端架构的,但这个浏览器缓存优化方案同样适用于其他前端项目。本文将深入探讨这个优化方案,并分享调试和改进的经验。
408 1
意外之惊喜!浏览器缓存优化方案,让页面加载速度飙升48.5%!
|
存储 开发框架 负载均衡
限流的非常规用途 - 缓解抢购压力
限流的非常规用途 - 缓解抢购压力
97 0
|
搜索推荐 PHP
typecho引入五秒盾,缓解服务器压力
typecho引入五秒盾,缓解服务器压力
93 0
|
数据采集 监控 前端开发
网络抖动对重复提交的影响与解决方案
网络抖动对重复提交的影响与解决方案
344 0
|
机器学习/深度学习 分布式计算 安全
基于动态数据体积的网络入侵问题
基于动态数据体积的网络入侵问题
92 0