В этом углубленном уроке мы хотим показать вам, как создать красивый эффект открытия видео. Анимация представляет собой небольшое вращение кадра и тонкое масштабирование видео, которое уже начинает проигрываться. Вся обертка расширяется до полного экрана без каких-либо элементов управления воспроизведением; будет показан только закрывающий крест. Как только видео закончится, или, если пользователь нажмет на закрывающий крестик, видео просто исчезнет.
Давайте перейдем к делу и начнем с планирования эффекта, напишем нашу разметку и JavaScript. Потом пропишем стили и настроим анимацию.
Планирование, разметка и JavaScript
Мы хотим анимировать оболочку, которая содержит видео, поэтому нам понадобится вложенная структура с двумя оболочками для видео. Единственный элемент управления, который мы хотим сделать, - это кнопка закрытия, она же будет входить в ту же оболочку. Видео должно быть в формате MP4 для его воспроизведения, но мы добавим несколько источников для поддержки в различных браузерах. Мы использовали удобный сервис CloudConvert для конвертации видео MP4 в необходимые форматы файлов (WebM и Ogg).
Значение атрибута poster позволяет задать изображение, которое будет показано перед загрузкой видео. При значении атрибута preload, установленном в auto, мы говорим браузеру, что желательно загружать все видео, т. е. с начать его выполнение. Источники для различных форматов видеофайлов перечислены в элементе video. При определении источника мы также устанавливаем кодек; кодек кодирует аудио и видео, превращая набор изображений в сжатый видеопоток или набор аудио-сэмплов в аудиопоток и наоборот. Контейнеры для комбинаций видео / аудио потока - это знакомые форматы файлов, такие как MP4 (сочетает H. 264 с AAC audio) или WebM (VP8 и Vorbis). Они также имеют идентифицирующий Тип MIME, как "video / webm".
Давайте напишем разметку:
<div class="video-wrap">
<div class="video-inner">
<video class="video-player" src="media/woods.mp4" poster="media/woods.jpg" preload="auto">
<source src="media/woods.webm" type='video/webm; codecs="vp8.0, vorbis"'>
<source src="media/woods.ogg" type='video/ogg; codecs="theora, vorbis"'>
<source src="media/woods.mp4" type='video/mp4; codecs="avc1.4D401E, mp4a.40.2"'>
<p>Sorry, but your browser does not support this video format.</p>
</video>
<button class="action action--close">
<i class="fa fa-close"></i>
<span class="action__label action__label--hidden">Close preview</span>
</button>
</div><!-- /video-inner-->
</div><!-- /video-wrap-->
<div class="content">
<div class="loader">
<i class="fa fa-spinner fa-pulse"></i>
</div>
<button class="action action--hidden action--play">
<i class="fa fa-play"></i><span class="action__label">Watch the video</span>
</button>
</div>
Мы также добавим кнопку воспроизведения, которая будет служить нашим триггером для анимации открытия и воспроизведения видео. Вы можете ее добавить путем использования шрифта FontAwesome (class fa).
Когда мы нажмем на эту кнопку воспроизведения, мы добавим класс video-wrap-show в раздел Video-wrap. Это позволит нам контролировать эффект на основе класса с помощью CSS-анимации и переходы. Как только мы нажмем на крестик, мы удалим этот класс и добавим video-wrap–hide вместо этого. Таким образом, мы можем определить альтернативное поведение закрытия с другой анимацией.
Наша цель состоит в том, чтобы контролировать весь эффект, используя в основном CSS; добавление классов и контроль воспроизведения видео будет работой нашего сценария.
Итак, давайте подытожим, что нам нужно сделать, чтобы достичь того, чего мы хотим:
Напишем JavaScript, реализуя все поведения элементов управления, которое мы определили ранее:
( function() {
'use strict';
var bodyEl = document.body,
videoWrap = document.querySelector('.video-wrap'),
videoEl = videoWrap.querySelector('video'),
playCtrl = document.querySelector('.action--play'),
closeCtrl = document.querySelector('.action--close');
function init() {
initEvents();
}
function initEvents() {
playCtrl.addEventListener('click', play);
closeCtrl.addEventListener('click', hide);
videoEl.addEventListener('canplaythrough', allowPlay);
videoEl.addEventListener('ended', hide);
}
function allowPlay() {
classie.add(bodyEl, 'video-loaded');
}
function play() {
videoEl.currentTime = 0;
classie.remove(videoWrap, 'video-wrap--hide');
classie.add(videoWrap, 'video-wrap--show');
setTimeout(function() {videoEl.play();}, 600);
}
function hide() {
classie.remove(videoWrap, 'video-wrap--show');
classie.add(videoWrap, 'video-wrap--hide');
videoEl.pause();
}
init();
})();
Довольно прямолинейно, верно? Красота наличия функциональности, контролируемой классом, заключается в том, что теперь мы можем определить поведение в нашей таблице стилей. Но это вопрос предпочтений и требований; если у вас много сложных взаимодействий, вам может быть лучше определить большинство или все поведение (анимации, переходы или видимость) в Вашем JavaScript. Но мы хотим погрузиться в CSS и исследовать некоторые интересные свойства, которые, надеемся, помогут вам с некоторыми другими проблемами во время вашей разработки.
Эффект сайта Moments App отлично подходит для изучения и использования некоторых захватывающих методов, поэтому мы погрузимся в CSS!
Стили
Давайте воплотим все в жизнь с нашими определениями стиля. Мы сосредоточимся на стилях, которые важны только для эффекта, но вы можете проверить свои стили в файле style.css, расположенный в папке css.
Начнем с того, что установим box-size в border-box для всех элементов. Эта модель делает выбор среди проявителей, потому что она имеет преимущество и позволяет определить размеры включая границы:
*,
*:after,
*:before {
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
Определим стили body и link:
body {
font-family: 'Avenir Next', 'Helvetica Neue', 'Lato', 'Segoe UI', Helvetica, Arial, sans-serif;
color: #fff;
background: #333 url(../media/woods.jpg) no-repeat center center;
background-size: cover;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
text-decoration: none;
color: #d9ecc9;
outline: none;
}
a:hover,
a:focus {
color: #fff;
}
Вам не нужно устанавливать свойства сглаживания шрифтов, но мы решили сделать, поскольку это делает шрифты четкими на Mac и предотвращает некоторые неисправности в Firefox.
Затем нам нужно установить стили для нашего контейнера главной страницы, который оборачивает все. Используя viewport-percentage, мы устанавливаем его размеры, чтобы охватить весь видовой экран и скрыть любое переполнение. Мы хотим, чтобы элементы внутри контейнера были выложены? Поскольку мы используем фиксированную высоту 100%, мы хотим, чтобы элементы были распределены в один столбец, который центрирован на экране. Раньше это было тяжело сделать бы без фантастического режима flexbox, так давайте широко использовать его и определим именно то, что мы хотим:
.container {
width: 100vw;
height: 100vh;
border: 20px solid #fff;
position: relative;
overflow: hidden;
display: -webkit-flex;
display: flex;
-webkit-flex-direction: column;
flex-direction: column;
-webkit-justify-content: space-around;
justify-content: space-around;
-webkit-align-items: center;
align-items: center;
}
Мы устанавливаем отображение на flex, а свойство flex-direction на column. Cодержание с space-around, тем самым выравниваем детали используя centеr. Попробуйте изменить размер окна браузера, чтобы увидеть, как элементы будут выложены в зависимости от того, сколько места доступно.
Теперь позаботимся о главной обертке видео. Прежде чем мы начнем укладку, давайте подумаем о возможных сценариях которые нам нужно рассмотреть, чтобы все не сломалось. Нам необходимо охватить все возможные случаи (или, по крайней мере, самые важные). Учитывая размер смартфона или небольшого устройства, мы хотим избежать слишком много причудливости, и должны рассмотреть собственное поведение (которое может просто бросить весь наш эффект за борт, как это происходит с iOS).
Мы хотим, чтобы наш эффект анимировался только для больших экранов, и не хотим делать ничего слишком сложного для крошечных экранов (мы просто хотим показать видео-обертку). Давайте используем медиа-запросы, которых у нас будет два: первый для определения всего эффекта для экранов больше, чем 25em, и второй для определения некоторых стилей по-разному в зависимости от соотношения сторон экрана. Нашим эталонным соотношением сторон является само видео: с размером 1280 × 720 у нас есть Соотношение сторон 1280/720 или 16/9. Поскольку мы хотим убедиться, что видео охватывает весь экран, нам нужно различать “вертикальный” и “горизонтальный” корпус. Но об этом чуть позже.
Сначала будут определены стили "по умолчанию" без основного эффекта анимации, а затем мы добавим все остальное, что нам нужно, в наши медиа-запросы.
Основная обертка будет фиксированным элементом, который будет занимать всю ширину и высоту. Мы будем использовать flexbox для центрирования внутреннего контейнера, поэтому определим необходимые свойства:
.video-wrap {
position: fixed;
z-index: 1000;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
display: -webkit-flex;
display: flex;
-webkit-align-items: center;
align-items: center;
}
Мы устанавливаем значение z-index на максимум, чтобы он был поверх всего остального, но мы не хотим, чтобы он был кликабельным, когда внутренние части скрыты. Использование событий указателя: ни один не сделает оболочку "неприкасаемой" и невидимой для взаимодействия, но будьте осторожны при использовании этого. Аналогичного поведения можно добиться с помощью visibility.
Когда мы покажем видео, мы установим указатель события на auto:
.video-wrap--show {
pointer-events: auto;
}
В то время как главная видео оболочка служит контейнером макета, video-inner разделение и само видео являются нашими ключевыми основными элементами. Давайте установим базовые стили для video-inner div:
.video-inner {
position: relative;
overflow: hidden;
width: 100%;
height: 100%;
margin: 0 auto;
opacity: 0;
background: black;
}
Здесь мы устанавливаем ширину и высоту до 100%, потому что никакое расширение или эффект не произойдет для базового случая небольших устройств.
.video-wrap--show .video-inner {
opacity: 1;
}
Добавление нашего класса "show" просто вызовет видимость внутреннего деления.
Сам элемент видео будет располагаться абсолютно и центрироваться, установив верхнюю часть на 50% и трансформацию, чтобы "вытащить" ее обратно на половину своей высоты:
.video-player {
position: absolute;
top: 50%;
width: 100%;
-webkit-transform: translate3d(0,-50%,0);
transform: translate3d(0,-50%,0);
}
Это базовые стили, которые позволят показать основную видео оболочку, а выглядеть будет вот так на мобильных устройствах:
Помните, что стили для основного эффекта будут определены в двух запросах медиа.
Давайте пропишем стили загрузчику и кнопке действия (играть и закрыть). Кнопка загрузки и воспроизведения будет помещена в раздел с содержанием класса, который нуждается в относительном позиционировании. Почему? Мы хотим, чтобы загрузчик был расположен абсолютно в том же месте, что и кнопка воспроизведения:
.content {
position: relative;
}
/* Loader */
.loader {
font-size: 2.5em;
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate3d(-50%,-50%,0);
transform: translate3d(-50%,-50%,0);
}
.video-loaded .loader {
opacity: 0;
pointer-events: none;
-webkit-transition: opacity 0.3s;
transition: opacity 0.3s;
}
На кнопки мы определим общие стили:
.action {
font-family: 'Avenir Next', 'Helvetica Neue', 'Lato', 'Segoe UI', Helvetica, Arial, sans-serif;
font-size: 1.15em;
font-weight: bold;
position: relative;
overflow: hidden;
margin: 0;
padding: 1em 2em;
color: #fff;
border: 2px solid;
border-radius: 40px;
background: none;
-webkit-flex: none;
flex: none;
}
.action:focus {
outline: none;
}
Установка значения свойства flex равным none гарантирует, что наша кнопка не будет сжиматься.
Убираем контур по фокусу.
Label или текст будут установлены в inline, и когда мы не хотим, чтобы показывался label, мы можем скрыть его, установив его положение в абсолютное и верхнее значение 200%. Обратите внимание, что нам нужно установить значение переполнения родителя в hidden, чтобы это работало.
.action__label {
display: inline-block;
margin: 0 0 0 0.75em;
}
.action__label--hidden {
position: absolute;
top: 200%;
}
Кнопка play изначально будет невидимой, а затем, когда видео будет загружено и мы добавим класс Video-loaded в тело, мы покажем его:
.action--play {
display: block;
margin: 1em auto;
opacity: 0;
pointer-events: none;
-webkit-transition: opacity 0.3s 0.1s;
transition: opacity 0.3s 0.1s;
}
.video-loaded .action--play {
opacity: 1;
pointer-events: auto;
}
Кнопка закрытия, которая находится в нашей внутренней обертке видео, будет показана с помощью перехода на ее непрозрачность и масштабное преобразование. Этот переход будет иметь задержку, когда мы открываем видео оболочку и когда закрываем:
.action--close {
line-height: 1;
position: absolute;
z-index: 1000;
top: 30px;
right: 30px;
width: 60px;
height: 60px;
padding: 0;
opacity: 0;
-webkit-transition: -webkit-transform 0.3s, opacity 0.3s;
transition: transform 0.3s, opacity 0.3s;
-webkit-transform: scale3d(0.7,0.7,1);
transform: scale3d(0.7,0.7,1);
}
.video-wrap--show .action--close {
opacity: 1;
-webkit-transition-delay: 1.2s;
transition-delay: 1.2s;
-webkit-transform: scale3d(1,1,1);
transform: scale3d(1,1,1);
}
Теперь мы добираемся до сердца наших стилей.
Для экранов, которые по крайней мере 25em в ширину, мы хотим, чтобы расширение анимации вступили в силу. Поэтому сначала нам нужно установить внутреннюю обертку видео нужного размера. Для "вертикального" случая мы будем использовать ширину viewport как измерение, чтобы определить размер нашего внутреннего контейнера. Мы делаем это так, что у нас есть аналогичный размер декоративных изображений polaroid, которые также имеют динамическую ширину и высоту в зависимости от соотношения сторон.
Как только мы установим класс основной обертки на video-wrap-show, мы будем воспроизводить анимационное ShowVideo-1. Когда мы закроем его, мы будем использовать анимацию hideVideo.
Внутренняя оболочка видео будет слегка повернута и уменьшена изначально. Можно также задать эти свойства в ключевом кадре 0%, но можно сохранить повторение и просто определить начальное состояние в объявлении класса.
Элемент video по умолчанию будет занимать всю высоту окна просмотра “вертикального регистра” (в следующем медиа-запросе мы переключим это) и пусть ширина будет определена автоматически. Он также будет преобразован; во-первых, будет центрирован с помощью метода "translate -50%", а затем масштабирован и повернут в противоположном направлении, чтобы немного компенсировать вращение родителя. Мы не хотим, чтобы он был повернут слишком сильно, так же не хотим, чтобы какие-либо края показывались здесь. Обязательно поиграйте с этими преобразованиями, чтобы увидеть, как вы можете достичь других интересных эффектов.
Когда покажем главную оболочку и изменим преобразование элемента video (все равно будем держать его в центре), мы должны убедится в его состоянии, когда его закроем. Помните, что пользователь хочет, чтобы у него все работало быстро (открывалось и закрывалось).
@media screen and (min-width: 25em) {
.video-inner {
width: 30vw;
height: 30vw;
border: 20px solid #fff;
-webkit-transform: scale3d(0.1,0.1,1) rotate3d(0,0,1,-5deg);
transform: scale3d(0.1,0.1,1) rotate3d(0,0,1,-5deg);
}
.video-wrap--show .video-inner {
opacity: 0;
-webkit-animation: showVideo-1 1.25s forwards;
animation: showVideo-1 1.25s forwards;
}
.video-wrap--hide .video-inner {
-webkit-animation: hideVideo 1.25s forwards;
animation: hideVideo 1.25s forwards;
}
.video-player {
left: 50%;
width: auto;
height: 100vh;
-webkit-transition: -webkit-transform 1s;
transition: transform 1s;
-webkit-transform: translate3d(-50%,-50%,0) scale3d(0.7,0.7,1) rotate3d(0,0,1,5deg);
transform: translate3d(-50%,-50%,0) scale3d(0.7,0.7,1) rotate3d(0,0,1,5deg);
}
.video-wrap--show .video-player,
.video-wrap--hide .video-player {
-webkit-transform: translate3d(-50%,-50%,0) scale3d(1,1,1);
transform: translate3d(-50%,-50%,0) scale3d(1,1,1);
}
}
Второй медиа-запрос предназначен для "горизонтального" случая, т. е. случай, который мы знаем, заставит видео раскрыться на весь экран, установив полную ширину окна просмотра. Мы берем значения ширины и высоты видео, чтобы определить минимальное соотношение сторон, которое совпадает с 16/9.
Мы изначально определяем размер нашей внутренней обертки до 30 vh, а само видео будет занимать всю ширину экрана, оставляя высоту авто.
@media screen and (min-width: 25em) and (min-aspect-ratio: 1280/720) {
.video-inner {
width: 30vh;
height: 30vh;
}
.video-wrap--show .video-inner {
-webkit-animation: showVideo-2 1.25s forwards;
animation: showVideo-2 1.25s forwards;
}
.video-player {
width: 100vw;
height: auto;
}
}
Просто заметка о форматировании CSS: вы, возможно, заметили, что мы не используем новую линию между правилами в медиа-запросах; это оказывается очень полезным, потому что вы знаете, что вы находитесь внутри медиа-запроса при редактировании ваших стилей. Не большая удивительная вещь, но иногда она может ускорить и облегчить поиск чего-то.
Давайте определимся с анимацией!
Вы можете задаться вопросом, почему мы используем префикс WebKit для переходов и анимации. Анимации по-прежнему нужен префикс для Safari (8), iOS Safari (8.4) и Android (40), чтобы работать.
Первая анимация предназначена для расширения внутренней оболочки видео для вертикального случая, когда мы берем ширину окна просмотра в качестве базовой меры. Вам не нужно различать эти два случая для внутренней оболочки, если у вас есть фиксированный начальный Размер. Но поскольку мы хотим динамический размер в зависимости от соотношения (и потому, что мы хотим показать вам, как злоупотреблять относительными единицами просмотра), мы определяем две разные анимации открытия.
Учитывая начальное состояние внутренней оболочки, мы теперь анимируем ее размеры до половины ширины окна просмотра и устанавливаем непрозрачность равным 1. Мы немного увеличиваем масштаб, но сохраняем значение поворота. Затем мы анимируем в полноэкранном режиме, используя 100vw для ширины и 100vh для высоты. Преобразования будут “отключены”:
@-webkit-keyframes showVideo-1 {
50% {
width: 50vw;
height: 50vw;
opacity: 1;
-webkit-transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,-5deg);
transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,-5deg);
}
100% {
width: 100vw;
height: 100vh;
opacity: 1;
-webkit-transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
}
}
@keyframes showVideo-1 {
50% {
width: 50vw;
height: 50vw;
opacity: 1;
-webkit-transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,-5deg);
transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,-5deg);
}
100% {
width: 100vw;
height: 100vh;
opacity: 1;
-webkit-transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
}
}
Другая анимация точно такая же, мы просто используем высоту экрана в качестве базовой меры, и поэтому нам нужно установить ширину и высоту ключевого кадра 50% до 50vh.
@-webkit-keyframes showVideo-2 {
50% {
width: 50vh;
height: 50vh;
opacity: 1;
-webkit-transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,-5deg);
transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,-5deg);
}
100% {
width: 100vw;
height: 100vh;
opacity: 1;
-webkit-transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
}
}
@keyframes showVideo-2 {
50% {
width: 50vh;
height: 50vh;
opacity: 1;
-webkit-transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,-5deg);
transform: scale3d(0.5,0.5,1) rotate3d(0,0,1,-5deg);
}
100% {
width: 100vw;
height: 100vh;
opacity: 1;
-webkit-transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
}
}
Обратите внимание, как мы добавляем фактическое преобразование сброса с масштабом 3d и rotate3d. Вы также можете просто написать один из типов преобразования, но это создаст проблемы для браузеров, таких как FireFox. Это хорошая идея, чтобы тщательно проверить каждый случай, если вы хотите упростить его.
И последнее, но не менее важное: мы определяем анимацию для скрытия всего. Помните, что мы уже позаботились о том, чтобы скрыть сам элемент video в первом медиа-запросе. Для анимации обертки видео мы устанавливаем первый и последний ключевой кадр одинаковыми, за исключением непрозрачности. Вот так мы просто обесцвечиваем видео-обертки.
@-webkit-keyframes hideVideo {
0% {
width: 100vw;
height: 100vh;
opacity: 1;
-webkit-transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
}
100% {
width: 100vw;
height: 100vh;
opacity: 0;
-webkit-transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
}
}
@keyframes hideVideo {
0% {
width: 100vw;
height: 100vh;
opacity: 1;
-webkit-transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
}
100% {
width: 100vw;
height: 100vh;
opacity: 0;
-webkit-transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
transform: scale3d(1,1,1) rotate3d(0,0,1,0deg);
}
}
Этот синтаксис выглядит так, как будто мы можем его упростить, верно? Должно быть возможно определить 0% и 100% ключевой кадр в одном определении, а затем установить различные свойства в отдельных ключевых кадрах, но это, кажется, не работает в IE, к сожалению. Кроме того, размещение анимации в определениях медиа-запросов приведет вас в ад с IE10 и IE11: они просто не будут работать.
Это все стили для эффекта. Обязательно поиграйте с преобразованиями и анимацией. Например, вы можете установить функцию синхронизации или повернуть по-другому, или даже повернуть.