[Перевод] Решение для ленивой загрузки видео, чувствительного к скорости

JavaScript Программа перевода самородков
[Перевод] Решение для ленивой загрузки видео, чувствительного к скорости

Фон для большого видео, если он сделан хорошо, может стать отличным опытом! Но добавление видео на домашнюю страницу — это не просто случайный поиск кого-то и добавление 25-мегабайтного видео, это сведет на нет все ваши оптимизации производительности.

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Элемент 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));
  }

Здесь мы находим все видео на этой странице, которые мы хотим отложенно загрузить. Если нет, мы можем вернуться. Когда пользователь включаетпредпочтение малоподвижностиМы также не загружаем такие видео при настройке. Чтобы не беспокоить некоторых пользователей мобильных телефонов с низкой скоростью сети или низкой производительностью графической обработки, он также вернется непосредственно на телефоны с маленьким экраном. (думаю можно ли пройти<source>Медиа-запросы Element для этого, но тоже не уверен. )

Затем запустите эту логику загрузки видео для каждого видео.

loadVideo

loadVideo()это простая функция, которая вызывает другие функции:

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

setSource

существуетsetSource(), мы находим эти видео ссылки, вставленные в виде атрибутов данных и устанавливать их на truesrcАтрибуты.

  /**
    * 找 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()В методе мы создаем два промиса. Первое Обещание будет в<video>элементальcanplaythroughкогда событие срабатываетresolve. этоcanplaythroughСобытие запускается, когда браузер считает, что видео может продолжать воспроизводиться без буферизации. Мы добавляем обратный вызов прослушивателя для этого события в этот промис, который выполняется, когда это событие запускается.resolve().

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

Мы также создаем другое обещание в качестве таймера. В этом обещании, когда после хорошего времени мы используемsetTimeoutЧтобы выполнить это обещание, я установил задержку в 2 секунды (2000 миллисекунд).

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

Теперь, когда у нас есть два промиса, мы можем пройти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 of . Если это видео можно воспроизвести, то я получу предыдущую загрузкуcan playА потом постарайтесь посмотреть, можете ли вы играть в это видео.video.play()предоставляется с помощью HTMLMediaElementplay()метод запуска воспроизведения видео.

Через 3 секундыsetTimeout()добавит этот тег.video-loadedclass, который поможет видеофайлам исчезать и автоматически зацикливаться более тонко.

если мы не получимcan playstring, то мы отменим загрузку этого видео.

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, этот метод загрузит видео размером 512 КБ до истечения времени ожидания. Даже для видео размером 3-5 Мб это дает значительную экономию трафика для некоторых пользователей с медленным соединением.

что ты думаешь? Если у вас есть предложения по улучшению, поделитесь ими в комментариях!


Originally published at benrobertson.io.

Если вы обнаружите ошибки в переводе или в других областях, требующих доработки, добро пожаловать наПрограмма перевода самородковВы также можете получить соответствующие бонусные баллы за доработку перевода и PR. начало статьиПостоянная ссылка на эту статьюЭто ссылка MarkDown этой статьи на GitHub.


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.

PS: Всех приглашаю обратить внимание на мой публичный аккаунт [Front End Afternoon Tea], давайте работать вместе~

Кроме того, вы можете присоединиться к группе WeChat «Front-end Afternoon Tea Exchange Group», нажмите и удерживайте, чтобы определить QR-код ниже, чтобы добавить меня в друзья, обратите вниманиеДобавить группу, я заберу тебя в группу~