Статьи

Создание интерактивной видео-витрины с помощью API видео

Эта статья была рецензирована Томом Греко и Марком Таулером . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!

Поскольку поддержка родного элемента видео и его API довольно обширна , сейчас самое время взглянуть на способы использования видео для создания чего-то интересного и интерактивного для ваших пользователей.

В этой статье мы рассмотрим основы API, и после того, как мы получим ориентиры, мы будем работать на практическом примере. Это будет мульти-видео слайдер, в котором видео воспроизводятся одно за другим, плавно загружаются и анимируются по мере воспроизведения.

Видео API — краткое введение

По сути, когда мы говорим о Video API, мы действительно говорим о Media API — API JavaScript, который позволяет вам взаимодействовать с аудио и видео элементами на веб-странице.

Этот API реализует интерфейс под названием HTMLMediaElement, который добавляет свойства , методы и события, необходимые для поддержки основных операций, общих для аудио и видео (таких как загрузка мультимедиа, изменение позиции поиска, завершение воспроизведения и т. Д.). Он расширяется как HTMLVideoElement, так и HTMLAudioElement, которые предоставляют свои собственные специальные свойства и методы.

В основном нас интересует элемент video поэтому мы сосредоточимся на взаимодействии с этой частью API. Чтобы ознакомиться с API, вы можете посетить страницу интерактивного примера HTML5-видео, на которой представлены наиболее часто используемые элементы.

Снимок экрана с примером видео HTML5

Поддержка браузера

Хотя Chrome, Firefox, Safari, Opera и Internet Explorer поддерживают видеоэлемент, они различаются по форматам, которые они могут воспроизводить, у каждого браузера есть список поддерживаемых видеоформатов, который также может быть основан на версии самого браузера.

На момент написания статьи все современные настольные и мобильные браузеры будут воспроизводить формат mp4 . Кроме того, большинство браузеров будут иметь длительную поддержку любого (или обоих) форматов ogg или webm . Вот полный обзор текущего состояния поддержки.

Хотя вы можете обойтись только предоставлением версии mp4 вам, вероятно, следует включить форматы ogg и webm в качестве комплексного решения.

Интерактивная видео-витрина

Мы будем создавать функцию демонстрации, используя элемент video . Наша витрина будет воспроизводить серию небольших видеоклипов подряд и запускать анимацию в определенное время. Используя такой видеоэлемент, мы исследуем некоторые его свойства, методы и события и показываем уровень контроля, которого вы можете достичь с помощью этого API.

Скриншот готовой витрины

Как всегда, вы можете найти код для этого руководства в нашем репозитории GitHub , а также демонстрацию готовой демонстрации в конце статьи.

Структурирование HTML-макета

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

Создайте контурную обертку для своего слайдера и добавьте в нее каждый раздел видео, который вы хотите определить

 <!--Main video wrapper--> <div class="video-wrapper" id="video-container"> <!--first video--> <div class="video-container"></div> <!--second video--> <div class="video-container"></div> <!--Nth video--> ... </div> 

Разметка для каждого раздела видео

Для каждого видео, которое мы хотим воспроизвести, нам нужно будет настроить несколько элементов, чтобы мы могли отображать видео, затемнять элементы заголовка и отслеживать общий ход видео.

 <!--Progress bar--> <div class="progress-bar"> <div class="progress"> <div class="progress-inner"> <span class="progress-time"></span> <span class="progress-value"></span> </div> </div> </div> <!--Progress bar overlay--> <div class="progress-overlay"></div> <!--Video Elements--> <video preload="none"> <source src="videos/video1/video1.mp4" type="video/mp4"> <source src="videos/video1/video1.webm" type="video/webm"> <source src="videos/video1/video1.ogg" type="video/ogg"> </video> <!--Video overlay--> <div class="overlay"></div> <!--Caption Elements--> <div class="caption"> <h1 data-animation-percent="10">Amazing New Adventures</h1> <h3 data-animation-percent="20">Come visit new parts of the world</h3> <p data-animation-percent="40"> Don't wait, there is a wide world out there that you can explore! Contact us to see what we can do </p> <div class="readmore" data-animation-percent="60">Find out more</div> </div> 

Элементы видео будут содержать тег video со всеми его дочерними source настроенными на разные типы данных, для максимальной совместимости. Узнайте больше о добавлении поддержки нескольких форматов видео .

Элементы заголовка будут содержать всю разметку, которую вы хотите добавить в процессе воспроизведения видео. Вы можете добавить что угодно, но он должен иметь атрибут data-animation-percent со значением от 0 до 100 . Это скажет ползунку, на каком проценте завершения видео исчезать элемент.

Индикатор выполнения отображает текущий прогресс видео (как в секундах, так и в процентах). Это будет периодически обновляться. Бар будет интерактивным; Когда вы наводите курсор на него, видео приостанавливается, и вы сможете просматривать его, обновляя позицию видео.

Предоставление запасного варианта для мобильных браузеров

Чтобы помочь охватить все наши базы, мы также настроим запасной элемент, который будет использоваться в случае, если текущий браузер является мобильным устройством.

Добавьте следующую разметку непосредственно перед концом основного видеооболочки

 <!--Fallback--> <div class="fallback-container"> <div class="image"></div> <div class="overlay"></div> <div class="caption"> <h1 data-animation-percent="15">This is a title</h1> <h3 data-animation-percent="25">Fallback when you dont support autoplay!</h3> <p data-animation-percent="50">Come and see a wide range of tasks and activities</p> <div class="readmore" data-animation-percent="70">Act now!</div> </div> </div> 

Это будет работать аналогично нашим видео. Когда пользователь загрузит страницу, мы запустим все ее элементы заголовка, чтобы исчезнуть в зависимости от значения, установленного в атрибуте data-animation-percent . Вместо видео мы используем фоновое изображение.

Обнаружение мобильных браузеров

Наш слайдер будет использовать autoplay элемента video для автоматического воспроизведения первого видео. Однако большинство мобильных браузеров блокируют эту функциональность (чтобы ограничить использование данных), что означает, что наилучшей политикой является обнаружение, если мы находимся на мобильном устройстве, и отображение запасного контента соответствующим образом. Обычно мы делаем это с использованием функции обнаружения, однако обнаружение поддержки атрибута autoplay довольно сложно .

Каким бы уродливым ни был нюхающий браузер / UA, он даст нам возможность проверить текущий браузер на соответствие известному списку мобильных устройств и определить, как нам действовать дальше. Мы можем сделать это используя следующее регулярное выражение:

 var mobile = navigator.userAgent.match(/Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile|IEMobile/i); 

Который мы можем затем использовать так:

 if(!mobile){ //main video slider functionality } else { //fallback functionality } 

Текущая и следующая настройка видео

Примечание : для функциональности JavaScript я использую jQuery, поэтому возьмите себе копию и добавьте ее внизу страницы.

Когда мы довольны тем, что у нас нет мобильных устройств, мы ищем все наши элементы с помощью класса video-container и присваиваем коллекцию нашей переменной videos . Эти элементы будут основными обертками для каждого видео и содержания его подписи. Затем мы находим элемент video каждого контейнера и присваиваем его локальной переменной video .

Теперь, когда у нас есть текущее видео, нам нужно найти следующий элемент видео, чтобы мы могли запустить его, чтобы начать загрузку и воспроизвести его, когда закончится текущий.

Если у нас есть другой элемент video-container мы назначаем его переменной nextVideo . Если у нас нет другого элемента video-container мы возвращаемся к первому.

 var videos = $('.video-container'); videos.each(function(index){ var video = $(this).find('video'), nextVideo; if(index !== (videos.length - 1)){ nextVideo = $(this).next('.video-container').find('video'); } else { nextVideo = videos.first().find('video'); } }); 

Вы можете добавить столько элементов video-container сколько захотите, и определить дополнительные видео и подписи. Слайдер найдет следующее видео и создаст цикл, который будет продолжаться бесконечно.

Начало воспроизведения видео для первого видео

Для нашего первого видео нам нужно настроить несколько вещей, чтобы мы могли все двигаться.

  • Мы добавляем active класс в .video-container первого видео (это .video-container видео с относительным позиционированием, чтобы оно отображалось)
  • Установите для атрибута preload видео значение auto чтобы браузер начал загружать видео.
  • Вызовите метод play() чтобы воспроизведение началось немедленно

 //first video, preload and play if(index === 0){ video.parents('.video-container').addClass('active'); video[0].preload = 'auto'; video[0].play(); } 

Вспомогательные переменные для видео

Для каждого видео мы должны установить несколько переменных, которые мы будем использовать позже в наших мероприятиях.

 var caption = video.siblings('.caption'); var captionItems = caption.find('[data-animation-percent]'); var videoBar = video.siblings('.progress-bar'); var dragging = false; var nextLoaded = false; 
  • caption является контейнером для всех анимированных элементов заголовка
  • captionItems содержит каждый элемент, который будет анимирован во время воспроизведения
  • videoBar относится к индикатору выполнения каждого видео (используется позже во время наших событий для визуального обновления происходящего).
  • dragging будет использоваться, когда мы ищем / пропускаем видео, чтобы изменить его позицию воспроизведения
  • nextLoaded будет использоваться для того, чтобы мы знали, когда уже начали загружать следующее видео в конце текущего воспроизведения видео (не позволяет нам вызывать load() несколько раз)

Событие timeUpdate

Поскольку наше видео воспроизводится, оно периодически вызывает событие timeupdate . Мы будем использовать это событие для некоторой обработки:

 $(video).on('timeupdate', function(){ //processing to go in here }); 

В обратном вызове (анонимная функция, которую мы передаем методу on jQuery) мы хотим получить время текущего видео. Мы устанавливаем переменную videoTime , находя currentTime видео и деля его на продолжительность, умноженную на 100. Это позволит нам узнать в процентном отношении, насколько полно видео.

 var videoTime = ((this.currentTime / this.duration) * 100); 

Запуск анимации для элементов заголовка

Если текущее видео имеет элементы заголовка, нам нужно проверить текущее время видео, чтобы увидеть, должны ли мы сделать элементы активными или неактивными.

Внутри нашей функции мы добавляем следующее

 $(video).on('timeupdate', function(){ var videoTime = ((this.currentTime / this.duration) * 100); if(captionItems.length > 0){ captionItems.each(function(){ var item = $(this); var animTime = parseInt(item.attr('data-animation-percent')); if(videoTime >= animTime){ item.addClass('active'); } else { item.removeClass('active'); } }); if(captionItems.filter('.active').length !== 0){ caption.addClass('active'); } else { caption.removeClass('active'); } if(videoTime >= 90){ caption.removeClass('active'); captionItems.each(function(){ $(this).removeClass('active'); }); } } }); 

Вот разбивка того, что мы делаем

  • Если длина наших captionItems больше 0 это означает, что у нас есть элементы, которые мы должны отслеживать.
    • Для каждого из captionItems нам нужно собрать значение, хранящееся в его атрибуте data-animation-percent и присвоить его animTime (значение должно быть от 0 до 100 ).
    • Если текущий videoTime больше, чем наши элементы animTime тогда нам нужно сделать элемент активным, добавив active класс.
  • Мы также получаем коллекцию captionItems и используем метод .filter jQuery, чтобы проверить, есть ли у нас какие-либо active элементы класса. Если мы это сделаем, мы хотим добавить активный класс в оболочку заголовка (переменная caption ).
  • Мы также проверяем, является ли текущий videoTime больше 90 (что означает, что мы закончили на 90%). Если мы близки к завершению, мы просматриваем все подписи и удаляем их active класс, а также удаляем его из caption

Загрузка следующего видео

В том же событии timeupdate мы также хотим проверить продолжительность нашего текущего видео и посмотреть, следует ли нам начинать загрузку следующего видео.

 if(videoTime >= 70 && nextLoaded === false){ nextVideo.preload = 'auto'; nextVideo.load(); nextLoaded = true; } 

Вы можете увидеть это в действии в готовой демоверсии — следующее видео должно появиться / загрузиться на вкладке сети вашей консоли примерно на 70% завершения текущего видео, давая ему несколько секунд до того, как конечное событие вызовет вызов его для воспроизведения ,

Снимок экрана вкладки сети браузера

Обновление индикатора выполнения во время воспроизведения

Так как мы хотим, чтобы индикатор выполнения часто обновлялся, мы не можем поместить его в функцию timeupdate (поскольку браузер определяет, как вызывается offen). Вместо этого мы вызываем setInterval и присваиваем его нашей переменной videoInterval . Каждые 100 мс мы будем вызывать функцию updateProgressAuto и передавать текущее воспроизводимое видео.

 setInterval(function(){ updateProgressAuto(video); }, 100); 

Автоматическое обновление индикатора выполнения с помощью UpdateProgressAuto

Каждые 100 мсек индикатор прогресса должен будет обновляться. Мы используем эту функцию для этой цели.

 function updateProgressAuto(video){ var videoBar = $(video).siblings('.progress-bar'); var videoPercent = ((video[0].currentTime / video[0].duration ) * 100); videoBar.find('.progress').css('width', videoPercent + '%'); videoBar.find('.progress-value').html(parseFloat(video[0].currentTime).toFixed(2) + ' : ' + parseFloat(video[0].duration).toFixed(2)); videoBar.find('.progress-time').html(parseInt(videoPercent) + '%'); } 

Вот что происходит.

  • Мы получаем элемент индикатора выполнения и videoPercent время видео по его duration и умножая на 100.
  • Мы обновляем ширину индикатора выполнения, чтобы отразить currentTime процентах
  • Мы обновляем текущее время / продолжительность видео, чтобы точно знать, когда оно закончится.
  • Мы обновляем процент, показанный на видео панели, с текущим процентом.

Событие OnEnded

Когда текущее видео заканчивается, оно вызывает событие onEnded . Именно здесь мы переключим активный класс на контейнер nextVideo а затем вызовем его функцию воспроизведения, чтобы начать воспроизведение.

 video[0].onended = function() { nextVideo.parents('.video-container').addClass('active'); video.parents('.video-container').removeClass('active'); nextVideo[0].play(); }; 

Обновление воспроизведения видео вручную

В качестве дополнительной функции, слайдер также позволяет вам вручную просматривать видео, изменяя его текущее время воспроизведения при нажатии или перетаскивании (позволяя перематывать или перематывать видео).

Мы связываем несколько обработчиков событий с событиями click , mousedown , mouseup и mousemove для переменной videoBar . Цель этого состоит в том, чтобы перевести позицию щелчка / позицию перетаскивания мыши, чтобы обновить позицию видео.

Индикатор выполнения в действии

 //seeking with the video bar videoBar.on('click', function(e){ updateProgressManual((e.pageX - $(this).offset().left) , video); }); //mouse moving videoBar.on('mousedown',function(e) { dragging = true; updateProgressManual(e.pageX - $(this).offset().left, video); }); //mouse up (choose time to seek to) videoBar.on('mouseup',function(e) { dragging = false; updateProgressManual(e.pageX - $(this).offset().left, video); }); //mouse dragging (actively seeking) videoBar.on('mousemove',function(e) { if(dragging === true){ updateProgressManual(e.pageX - $(this).offset().left, video); } }); 

Все эти события вызывают функцию updateProgressManual , которая обновляет время воспроизведения видео. Значение, которое мы соберем, будет текущей позицией pageX минус текущее левое offset видеобара (что даст нам правильное положение на индикаторе выполнения)

Обновление воспроизведения вручную с помощью функции UpdateProgressManual

Эта функция вызывается, когда мы вручную ищем текущее видео. Его цель — получить позицию, на которую щелкнули, и преобразовать ее во время, к которому видео должно стремиться.

 //Manually updates the video when we seek using the progress bar function updateProgressManual(progressBarPosition, video){ var videoBar = $(video).siblings('.progress-bar'); var videoPercentage = ((progressBarPosition / videoBar.outerWidth()) * 100); videoBar.find('.progress').css('width', videoPercentage + '%'); videoBar.find('.progress-value').html(parseFloat(video[0].currentTime).toFixed(2) + " : " + parseFloat(video[0].duration).toFixed(2)); videoBar.find('.progress-time').html(parseInt(videoPercentage) + '%'); video[0].currentTime = ((video[0].duration * videoPercentage) / 100); } 

Вот разбивка того, что происходит

  • Мы рассчитываем videoPercentage , получая ток, переданный в позиции, и деля его на outerWidth времени видеобара на 100. Это скажет нам, какой процент мы хотим искать.
  • Мы устанавливаем ширину элемента progress внутри videoBar в процентах, к которым мы стремимся.
  • Мы также хотим обновить время визуального воспроизведения в баре. Это делается путем получения currentTime и duration видео
  • Наконец, мы устанавливаем значение currentTime для видео, чтобы оно вызывало указанный период времени.

Дополнительная функциональность

У слайдера также есть несколько других функций, которые могут оказаться полезными.

Когда videoBar пересекается с нашей мышью, мы расширяем индикатор выполнения, чтобы было легче нажимать / перетаскивать (чтобы искать видео). Кроме того, мы также приостанавливаем видео и активируем наше наложение. Когда мышь удаляется, все возвращается назад, начиная воспроизведение и удаляя классы.

Мы также проверяем, перемещаете ли вы мышь на область caption и ее элементы. Если заголовок в настоящее время активен, он будет замедлять видео через свойство playbackRate (делая воспроизведение в два раза быстрее). Когда мы убираем мышь, видео возвращается к нормальной скорости.

 //When hovering over progress bar, pause video and restyle videoBar.on('mouseover', function(){ $(this).siblings('.progress-overlay').addClass('active'); $(this).addClass('expanded'); video[0].pause(); }); //When not hovering, unpause video and restyle back to normal videoBar.on('mouseout', function(){ $(this).siblings('.progress-overlay').removeClass('active'); $(this).removeClass('expanded'); video[0].play(); }); //if we have caption elements, slow playback on hover if(captionItems.length !==0){ video.parents('.video-container').on('mouseover','.caption.active', function(){ video[0].playbackRate = 0.5; }); video.parents('.video-container').on('mouseout','.caption.active', function(){ video[0].playbackRate = 1; }); } 

Наш мобильный только резервный контент

Если мы на мобильном устройстве, мы не будем показывать наши видео, вместо этого мы будем показывать запасной контент. Вы можете проверить это в демонстрационной версии, установив mobile переменную в значение true.

Большая часть функциональности похожа на то, как мы обрабатывали каждое видео. Мы ищем все его элементы и затем определяем, когда их нужно добавить.

Снимок экрана резервного контента

 var fallbackInterval = setInterval(function(){ currentTime = (parseInt(currentTime) + timeInterval); fallbackElements.each(function(){ var animationPercent = parseInt($(this).attr('data-animation-percent')); if((currentTime / animationDuration * 100) >= animationPercent){ $(this).addClass('active'); } else { $(this).removeClass('active'); } }); //if we have any caption elements faded in if(fallbackElements.filter('.active').length !== 0){ fallbackElements.parents('.caption').addClass('active'); } //if we have ended, finish if(currentTime >= animationDuration){ clearInterval(fallbackInterval); } }, timeInterval); 

Давайте пройдемся по тому, что происходит

  • Мы находим fallback и fallbackElements и устанавливаем fallback контейнер для активации.
  • Мы устанавливаем currentTime в 0 что дает нам время начала. Затем мы устанавливаем animationDuration на 5000 чтобы указать, что полная анимация займет 5 секунд. Наконец, мы устанавливаем timeInterval равным 50 что означает, что каждые 50 мс мы будем иметь обновление.
  • Мы создаем наш основной цикл с помощью функции setInterval (присваивая его нашей переменной fallbackInterval ). Мы устанавливаем интервал для запуска каждые 50 мс.
  • Внутри основного интервала мы получаем новое текущее время (добавляя timeInterval к переменной currentTime ).
  • Мы проверяем каждый из элементов заголовка по отношению к проценту этого нового времени, чтобы увидеть, должны ли мы активировать этот элемент.
  • Если наш currentTime больше нашего animationDuration мы fallbackInterval используя clearInterval, чтобы завершить все.

демонстрация

И, наконец, вот демонстрация готовой витрины. Вы можете нажать на видео, чтобы переключить воспроизведение.

Где отсюда?

Теперь, когда вы видите, как Video API можно использовать для создания интересных элементов, вы можете взять пример кода из этого и расширить его, чтобы сделать его еще более удивительным.

Есть много вещей, которые вы могли бы сделать, чтобы улучшить это, вот некоторые из них:

  • Создайте более привлекательную анимацию, которая применяется к элементам заголовка по мере их появления. Вы также можете создать несколько различных анимаций на случай, если вы захотите их различить.
  • Подайте совершенно другой опыт, если вы находитесь на мобильном телефоне. Может быть, у вас может быть слайдер изображений или другой интерактивный контент.
  • Вы можете попытаться предоставить несколько резервных контейнеров с их собственными изображениями и контентом, чтобы имитировать то, что делает видео слайдер.
  • Используйте дополнительные события, методы и свойства из API, чтобы сделать слайдер еще лучше.

Если в итоге вы создадите что-то замечательное, не стесняйтесь поделиться им с нами, я хотел бы услышать ваше мнение в комментариях ниже.