Сегодня — продолжение проекта с интерфейсом камеры айфона в браузере. В прошлый раз мы сделали красивую картинку, сегодня будем её оживлять скриптами. А ещё при переключении на фронтальную камеру (если вы запустите код на ноуте, например) вы сможете сделать настоящее селфи!
Что мы уже сделали
К этому моменту у нас уже есть:
- HTML-страница со всеми элементами;
- дизайн, который примерно повторяет интерфейс камеры телефона;
- видео, которое крутится по кругу и имитирует работу основной камеры;
- иконки зума, переворота камеры и съёмки;
- область для «снятых» кадров.
Всё это работает на HTML и CSS, поэтому у нас есть пока только визуальная часть. Чтобы вдохнуть жизнь в проект, нам понадобится JavaScript — именно он будет отвечать за всю механику работы приложения.
Собственно, этим мы сегодня и займёмся :) Исходный код прошлого проекта — ниже.
<!DOCTYPE html> <!-- Объявление типа документа HTML5 -->
<html lang="кг"> <!-- Основной тег документа с указанием языка -->
<head>
<meta charset="UTF-8"> <!-- Кодировка UTF-8 для корректного отображения символов -->
<title>Интерактивная съёмка</title> <!-- Заголовок страницы -->
<!-- Подключение CSS-файла -->
<link rel="stylesheet" href="./style.css">
<!-- Подключение иконок Google Material Icons -->
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@48,400,0,0" />
</head>
<body>
<!-- Основной контейнер приложения -->
<div class="imageContainer">
<!-- Внутренний контейнер для медиаконтента -->
<div class="imageContainerInner">
<!-- Видеоэлемент с атрибутами:
- id: идентификатор для JS
- src: источник видео
- autoplay: автоматическое воспроизведение
- loop: зацикливание
- muted: отключение звука (обязательно для autoplay)
- poster: изображение-заглушка
- crossorigin: настройки CORS
- preload: предзагрузка метаданных
- type: MIME-тип видео
-->
<video id="videoElement" src="https://ismailvtl-images-project.vercel.app/video-codepen-dance.mp4" autoplay loop muted poster="https://images.pexels.com/videos/8688878/pexels-photo-8688878.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=2" crossorigin="anonymous" preload="metadata" type="video/mp4"></video>
<!-- Элемент для эффекта вспышки -->
<div class="flash"></div>
</div>
<!-- Панель кнопок зума -->
<div class="controls">
<!--
Кнопки с атрибутами:
- data-zoom: хранит множитель масштаба
- class="active": активное состояние (1× по умолчанию)
-->
<button data-zoom="1" class="active">1<span>×</span></button>
<button data-zoom="1.5">1.5<span>×</span></button>
<button data-zoom="2.5">3<span>×</span></button>
</div>
<div class="otherControls">
<!-- Кнопка миниатюры последнего снимка -->
<button class="thumbnail">
<img src="https://images.pexels.com/videos/8688878/pexels-photo-8688878.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1" alt="Thumbnail" />
</button>
<!-- Основная кнопка съёмки -->
<button class="camerabutton"></button>
<!-- Кнопка переключения камеры с иконкой -->
<button class="switchCamera">
<span class="material-symbols-outlined">
flip_camera_android
</span>
</button>
</div>
</div>
</body>
</html>
—-----
/* Сброс стандартных стилей браузера */
*,
*::before,
*::after {
margin: 0; /* Обнуление внешних отступов */
padding: 0; /* Обнуление внутренних отступов */
box-sizing: border-box; /* Размеры включают padding и border */
}
/* Основные стили страницы */
body {
display: flex; /* Flex-контейнер */
justify-content: center; /* Центрирование по горизонтали */
align-items: center; /* Центрирование по вертикали */
height: 100vh; /* Высота на весь экран */
background: radial-gradient(#9e9e9e, transparent); /* Градиентный фон */
}
/* Контейнер камеры */
.imageContainer {
width: 380px; /* Фиксированная ширина */
height: 95vh; /* 95% высоты экрана */
display: flex;
align-items: center;
justify-content: center;
background-size: 100%; /* Фоновое изображение на всю ширину */
background-position: center; /* Центрирование фона */
background-repeat: no-repeat; /* Запрет повторения фона */
border: 6px solid #000; /* Рамка */
border-radius: 60px; /* Закругление углов */
position: relative; /* Для позиционирования дочерних элементов */
overflow: hidden; /* Скрытие выходящего за границы контента */
background: #000; /* Чёрный фон */
}
/* Внутренний контейнер */
.imageContainerInner {
width: 100%; /* На всю ширину родителя */
height: 75vh; /* 75% высоты */
overflow: hidden; /* Скрытие выходящего контента */
margin-top: -64px; /* Смещение вверх */
position: relative; /* Для позиционирования */
}
/* Стили видео */
video {
height: 100%; /* На всю высоту */
width: 100%; /* На всю ширину */
object-fit: cover; /* Заполнение с сохранением пропорций */
transition: transform 0.4s; /* Анимация трансформации */
object-position: center; /* Центрирование */
}
/* Контейнер кнопок зума */
.controls {
position: absolute; /* Фиксируем поверх видео */
bottom: 128px; /* Отступ снизу */
left: 50%; /* Центрирование по горизонтали */
transform: translateX(-50%); /* Точное центрирование */
background: rgba(0, 0, 0, 0.54); /* Полупрозрачный чёрный фон */
border-radius: 50px; /* Закруглённая форма */
display: flex;
gap: 16px; /* Расстояние между кнопками */
padding: 8px 12px;
z-index: 20; /* Поверх других элементов */
}
/* Стили кнопок */
.controls button {
width: 32px;
height: 32px;
border: none;
border-radius: 50%; /* Круглая форма */
background: rgba(0, 0, 0, 0.56); /* Тёмный фон */
color: #fff; /* Белый текст */
font-size: 14px;
font-weight: 600;
font-family: system-ui, sans-serif; /* Стандартный шрифт */
transition: transform 0.2s; /* Плавное увеличение */
cursor: pointer; /* Указатель при наведении */
}
/* Стиль активной кнопки */
.controls button.active {
transform: scale(1.2); /* Увеличение на 20% */
color: yellow; /* Жёлтый текст */
}
/* Символ "×" (скрыт по умолчанию) */
.controls button span {
display: none;
}
/* Показываем "×" только для активной кнопки */
.controls button.active span {
display: inline; /* Отображаем символ */
font-size: 12px; /* Уменьшаем размер */
}
/* Контейнер нижней панели управления */
.otherControls {
position: absolute; /* Фиксированное позиционирование */
bottom: 16px; /* Отступ снизу */
left: 0;
right: 0;
display: flex; /* Гибкое расположение элементов */
justify-content: space-between; /* Равномерное распределение */
padding: 10px 16px; /* Внутренние отступы */
align-items: center; /* Вертикальное выравнивание */
z-index: 20; /* Поверх других элементов */
}
/* Стили кнопки съёмки */
.camerabutton {
height: 52px;
width: 52px;
background: #fff; /* Белый цвет */
border-radius: 100%; /* Круглая форма */
box-shadow:
0px 0px 0px 3px rgb(0 0 0 / 90%), /* Внутренняя чёрная обводка */
0px 0px 0px 6px #fff; /* Внешняя белая обводка */
border: 0; /* Убираем стандартную рамку */
cursor: pointer; /* Указатель при наведении */
}
/* Стили миниатюры */
.thumbnail {
height: 48px;
width: 48px;
background: #fff; /* Белый фон */
overflow: hidden; /* Обрезка содержимого */
border-radius: 6px; /* Закруглённые углы */
border: 0; /* Убираем рамку */
}
/* Стили изображения в миниатюре */
.thumbnail img {
object-fit: cover; /* Заполнение с сохранением пропорций */
height: 100%; /* На всю высоту */
width: 100%; /* На всю ширину */
}
/* Стили кнопки переключения камеры */
.switchCamera {
height: 48px;
width: 48px;
background: rgb(95 89 89 / 40%); /* Полупрозрачный серый */
border: 0; /* Убираем рамку */
border-radius: 100%; /* Круглая форма */
}
/* Стили иконки переключения камеры */
.switchCamera span {
color: #ffff; /* Белый цвет */
display: flex;
justify-content: center; /* Центрирование по горизонтали */
font-size: 30px; /* Размер иконки */
}
/* Стили элемента вспышки */
.flash {
position: absolute; /* Позиционирование поверх видео */
top: 0;
left: 0;
width: 100%;
height: 100%;
background: transparent; /* Изначально прозрачный */
z-index: 9; /* Поверх видео, но под кнопками */
}
/* Анимация вспышки */
@keyframes cameraFlash {
from {
box-shadow: none; /* Начальное состояние — нет свечения */
}
to {
box-shadow: inset 0 0 0 100px #fff; /* Белая вспышка на весь экран */
}
}
/* Класс для активации анимации */
.shutterClick {
animation: cameraFlash 0.3s 0.3s alternate-reverse 1;
/*
Параметры анимации:
- длительность: 0.3s
- задержка: 0.3s
- alternate-reverse: проигрывается в обратном направлении
- 1: количество повторений
*/
}
/* Анимация исчезновения (переворот на 90 градусов) */
@keyframes flipOutY {
from {
transform: perspective(400px) rotateY(0); /* Начальное положение */
opacity: 1; /* Полностью видимый */
}
to {
transform: perspective(400px) rotateY(90deg); /* Поворот на 90 градусов */
opacity: 0; /* Полностью прозрачный */
}
}
/* Анимация появления (переворот с 90 до 0 градусов) */
@keyframes flipInY {
from {
transform: perspective(400px) rotateY(90deg); /* Начальное положение */
opacity: 0; /* Полностью прозрачный */
}
to {
transform: perspective(400px) rotateY(0); /* Конечное положение */
opacity: 1; /* Полностью видимый */
}
}
/* Классы для управления анимациями */
.flipping-out {
animation: flipOutY 0.5s forwards;
/*
forwards — сохраняет конечное состояние анимации
0.5s — длительность анимации
*/
}
.flipping-in {
animation: flipInY 0.5s forwards;
}
Логика проекта
Что мы будем делать:
- Подключим скрипт к HTML-файлу.
- Добавим работу кнопок зума.
- Настроим переворот камеры, чтобы можно было снимать с настоящей фронталки на ноуте.
- Сымитируем съёмку фото, чтобы превьюшки появлялись в нижнем левом углу.
- Добавим анимацию-заглушку там, где пока не будет рабочего скрипта.
Первый пункт самый простой: открываем файл index.html и добавляем в самый конец такое:
<script src="script.js"></script>
После этого создаём в той же папке, где и другой исходный код, файл с именем script.js — и дальше работать будем уже в нём.
Анимация зума
На этом этапе мы сделаем функциональность масштабирования видео — она позволит увеличивать и уменьшать изображение с помощью кнопок управления. Этот механизм работает через взаимодействие JavaScript с CSS-свойствами видеоэлемента.В основе реализации лежит несколько ключевых компонентов. Прежде всего, мы работаем с тремя кнопками зума, которые уже присутствуют в нашей HTML-разметке. Каждая из этих кнопок содержит атрибут data-zoom с числовым значением, определяющим степень масштабирования (1 для исходного размера, 1,5 для полуторного увеличения и 3 для максимального приближения).
Всё начинается с получения ссылки на видеоэлемент через метод document.getElementById, так как нам необходимо напрямую взаимодействовать с видео, изменяя его свойства. Затем мы создаём функцию handleZoom(), которая будет обрабатывать все события клика по кнопкам зума.
При каждом клике мы сначала извлекаем значение масштаба из атрибута data-zoom нажатой кнопки. Для этого используем метод parseFloat для преобразования строкового значения в число — это надо для работы CSS-свойства transform. Полученное значение подставляем в строку шаблона для свойства transform: scale(), которое применяется к нашему видеоэлементу. Дальше сделаем подсветку активной кнопки через манипуляции с классами CSS. Сначала снимаем класс active со всех кнопок зума, используя метод querySelectorAll для выбора всех соответствующих элементов и forEach для их перебора. Затем добавляем класс active к нажатой кнопке. Этот класс уже содержит в CSS необходимые стили для визуального выделения — увеличение размера на 20% и изменение цвета на жёлтый.
Чтобы связать логику с интерфейсом, используем метод addEventListener, который подписывает каждую кнопку зума на событие click. При возникновении этого события вызывается функция handleZoom, в которой и происходит масштабирование.

// Получаем элемент видео из DOM
const videoElement = document.getElementById("videoElement");
// Функция обработки зума
function handleZoom(event) {
// Получаем значение масштаба из атрибута data-zoom
// parseFloat преобразует строку в число с плавающей точкой
const zoomLevel = parseFloat(event.target.getAttribute("data-zoom"));
// Применяем масштабирование к видео через CSS transform
// Используем шаблонную строку для подстановки значения
videoElement.style.transform = `scale(${zoomLevel})`;
// Удаляем класс active у всех кнопок зума
// querySelectorAll возвращает NodeList всех кнопок в блоке controls
document.querySelectorAll(".controls button").forEach((btn) => {
btn.classList.remove("active");
});
// Добавляем класс active к нажатой кнопке
// event.target — это элемент, на котором произошло событие
event.target.classList.add("active");
}
// Навешиваем обработчики событий на все кнопки зума
// Используем forEach для перебора всех найденных кнопок
document.querySelectorAll("button[data-zoom]").forEach((button) => {
// Добавляем обработчик click для каждой кнопки
button.addEventListener("click", handleZoom);
});
Снимаем фото
На этом этапе мы будем имитировать процесс создания снимков. При нажатии на центральную кнопку съёмки система должна захватить текущий кадр, сохранить его как изображение и отобразить в виде миниатюры. Чтобы было как в жизни, добавим немного эффектов для реалистичности.
Механика простая: получаем доступ к элементам DOM — видео, кнопке съёмки и элементу вспышки — и навешиваем обработчик события на кнопку съёмки. А вот что происходит после нажатия на кнопку:
- Активируем анимацию вспышки.
- Создаём временный canvas-элемент.
- Копируем текущий кадр видео на canvas.
- Преобразуем canvas в data URL.
- Обновляем источник изображения миниатюры.
- Убираем анимацию вспышки после завершения.

Основа — элемент canvas, который позволяет программно работать с изображениями. При нажатии на кнопку создаётся временный canvas-элемент, размеры которого соответствуют размерам видео. На этот canvas с помощью метода drawImage копируется текущий кадр из видеоэлемента.
После захвата кадра canvas преобразуется в data URL с использованием метода toDataURL, который кодирует изображение в формате JPEG. Полученная строка с данными изображения сохраняется в атрибуте src элемента миниатюры, что приводит к обновлению отображаемого снимка.
Для создания эффекта вспышки используется CSS-анимация: к элементу вспышки добавляется класс shutterClick, который запускает анимацию cameraFlash. Эта анимация создаёт эффект белой вспышки, постепенно заполняющей экран, а по завершении анимации класс автоматически удаляется.
Теперь запишем всё это в коде. Как обычно, прокомментировали каждую строку, чтобы было проще разобраться в том, что тут происходит:
// Получаем необходимые элементы из DOM
const flashElement = document.querySelector('.flash');
const cameraButton = document.querySelector('.camerabutton');
// Навешиваем обработчик на кнопку съёмки
cameraButton.addEventListener('click', function() {
// Активируем анимацию вспышки
flashElement.classList.add('shutterClick');
// Создаём временный canvas-элемент
let canvas = document.createElement('canvas');
// Устанавливаем размеры canvas равными размерам видео
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
// Получаем контекст рисования
let ctx = canvas.getContext('2d');
// Копируем текущий кадр видео на canvas
ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
// Получаем data URL изображения в формате JPEG
let thumbnailDataURL = canvas.toDataURL('image/jpeg');
// Находим элемент миниатюры и обновляем его источник
let thumbnailImage = document.querySelector('.thumbnail img');
thumbnailImage.src = thumbnailDataURL;
// Убираем класс анимации после её завершения
flashElement.addEventListener('animationend', function() {
flashElement.classList.remove('shutterClick');
});
});
Переключаемся между камерами
Для переключения между заранее загруженным видео и потоком с веб-камеры нам потребуется несколько ключевых компонентов. В HTML-разметке уже присутствует кнопка переключения камеры с иконкой разворота, а в CSS подготовлены анимации для плавного перехода. Основная логика будет реализована в JavaScript.Здесь можно выделить несколько этапов. Сначала мы получаем доступ к элементам интерфейса — видеоэлементу и кнопке переключения. Затем реализуем обработчик клика, который будет определять текущий режим (видео или камера) и выполнять соответствующие действия. При переключении на веб-камеру мы используем API MediaDevices для получения видеопотока, а при возврате к обычному видео останавливаем этот поток и восстанавливаем исходное видео.
А ещё надо обратить внимание на CSS-анимации flipOutY и flipInY (мы их сделали в прошлый раз), которые создают эффект переворота камеры. Эти анимации применяются последовательно: сначала видео «переворачивается» и исчезает, затем после смены источника появляется снова с обратной анимацией.
Когда высветится такое (или похожее) окно, надо нажать «Allow» («Разрешить»), чтобы страница могла получить доступ к камере:

// переворота ещё не было
let firstInteractionDone = false;
// Находим кнопку переключения камеры и добавляем обработчик клика
document.querySelector(".switchCamera").addEventListener("click", function () {
// Проверяем, было ли первое взаимодействие (для автовоспроизведения)
if (!firstInteractionDone) {
videoElement.play();
firstInteractionDone = true;
}
// Проверяем текущий режим (веб-камера или видеофайл)
var isWebcam = videoElement.getAttribute("data-iswebcam") === "true";
// Запускаем анимацию «исчезновения»
videoElement.classList.add("flipping-out");
// Обработчик завершения анимации исчезновения
videoElement.onanimationend = () => {
if (isWebcam) {
// Если текущий режим — веб-камера, переключаемся на видеофайл
// Останавливаем все треки потока веб-камеры
if (videoElement.srcObject) {
const tracks = videoElement.srcObject.getTracks();
tracks.forEach((track) => track.stop());
}
// Очищаем srcObject и устанавливаем атрибуты для видеофайла
videoElement.srcObject = null;
videoElement.setAttribute("crossorigin", "anonymous");
videoElement.src = "video-codepen-dance.mp4";
videoElement.setAttribute("data-iswebcam", "false");
// Запускаем анимацию появления
videoElement.classList.remove("flipping-out");
videoElement.classList.add("flipping-in");
// Воспроизводим видео
videoElement.play();
} else {
// Если текущий режим — видеофайл, переключаемся на веб-камеру
// Проверяем поддержку API getUserMedia
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices
.getUserMedia({ video: true })
.then(function (stream) {
// Устанавливаем атрибуты для потока веб-камеры
videoElement.setAttribute("crossorigin", "anonymous");
videoElement.srcObject = stream;
videoElement.setAttribute("data-iswebcam", "true");
// Запускаем анимацию появления
videoElement.classList.remove("flipping-out");
videoElement.classList.add("flipping-in");
// Воспроизводим поток
videoElement.play();
})
.catch(function (err) {
console.error("Error accessing the camera", err);
});
} else {
console.error("Your browser does not support getUserMedia API");
}
}
// Обработчик завершения анимации появления
videoElement.onanimationend = () => {
videoElement.classList.remove("flipping-in");
};
};
});
Дополнительные эффекты
Сейчас наш проект умеет всё, кроме того, чтобы показывать, что мы отсняли. Так как это отдельный большой пласт работы, вместо него мы сделаем заглушку на будущее — при нажатии на превью будет появляться смайлик. Это нормально, в ИТ часто так делают.
Вот как это работает.При клике на элемент с классом .thumbnail создаётся новый элемент span, которому добавляется класс heart и текстовое содержимое в виде эмодзи «😉». Этот элемент динамически добавляется в DOM как дочерний элемент миниатюры. Через небольшой таймаут (10 мс) применяются CSS-стили, которые запускают анимацию: смайлик плавно поднимается вверх (изменение свойства bottom) и одновременно становится полностью видимым (изменение opacity). Размер шрифта, кстати, увеличивается до 30px для более выразительного отображения. Через 2 секунды после создания элемент удаляется из DOM, завершая анимационный цикл.
// Находим кнопку миниатюры в DOM
let thumbnailButton = document.querySelector(".thumbnail");
// Добавляем обработчик события клика
thumbnailButton.addEventListener("click", function () {
// Создаём новый span-элемент для смайлика
let heart = document.createElement("span");
// Добавляем класс для стилизации
heart.classList.add("heart");
// Устанавливаем текстовое содержимое (эмодзи)
heart.textContent = "😉";
// Добавляем созданный элемент в DOM как дочерний для миниатюры
thumbnailButton.appendChild(heart);
// Запускаем анимацию через небольшой таймаут (10 мс)
setTimeout(() => {
// Задаём конечное положение (поднимается вверх)
heart.style.bottom = "100%";
// Делаем полностью видимым
heart.style.opacity = "1";
// Увеличиваем размер
heart.style.fontSize = "30px";
}, 10);
// Удаляем элемент через 2 секунды
setTimeout(() => {
thumbnailButton.removeChild(heart);
}, 2000);
});
И добавим нужный класс в CSS:
/* Стили для анимированного смайлика */
.heart {
position: absolute; /* Абсолютное позиционирование */
bottom: 0; /* Начальное положение — внизу */
left: 50%; /* Центрирование по горизонтали */
font-size: 24px; /* Начальный размер */
opacity: 0; /* Начальная прозрачность */
transform: translateX(-50%); /* Точное центрирование */
transition: bottom 2s ease, opacity 2s ease; /* Плавные переходы */
z-index: 10; /* Отображение поверх других элементов */
pointer-events: none; /* Чтобы не перехватывал клики */
}
Посмотреть на работу проекта в отдельной странице.
// Получаем элемент видео из DOM
const videoElement = document.getElementById("videoElement");
// Функция обработки зума
function handleZoom(event) {
// Получаем значение масштаба из атрибута data-zoom
// parseFloat преобразует строку в число с плавающей точкой
const zoomLevel = parseFloat(event.target.getAttribute("data-zoom"));
// Применяем масштабирование к видео через CSS transform
// Используем шаблонную строку для подстановки значения
videoElement.style.transform = `scale(${zoomLevel})`;
// Удаляем класс active у всех кнопок зума
// querySelectorAll возвращает NodeList всех кнопок в блоке controls
document.querySelectorAll(".controls button").forEach((btn) => {
btn.classList.remove("active");
});
// Добавляем класс active к нажатой кнопке
// event.target — это элемент, на котором произошло событие
event.target.classList.add("active");
}
// Навешиваем обработчики событий на все кнопки зума
// Используем forEach для перебора всех найденных кнопок
document.querySelectorAll("button[data-zoom]").forEach((button) => {
// Добавляем обработчик click для каждой кнопки
button.addEventListener("click", handleZoom);
});
// Получаем необходимые элементы из DOM
const flashElement = document.querySelector('.flash');
const cameraButton = document.querySelector('.camerabutton');
// Навешиваем обработчик на кнопку съёмки
cameraButton.addEventListener('click', function() {
// Активируем анимацию вспышки
flashElement.classList.add('shutterClick');
// Создаём временный canvas-элемент
let canvas = document.createElement('canvas');
// Устанавливаем размеры canvas равными размерам видео
canvas.width = videoElement.videoWidth;
canvas.height = videoElement.videoHeight;
// Получаем контекст рисования
let ctx = canvas.getContext('2d');
// Копируем текущий кадр видео на canvas
ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height);
// Получаем data URL изображения в формате JPEG
let thumbnailDataURL = canvas.toDataURL('image/jpeg');
// Находим элемент миниатюры и обновляем его источник
let thumbnailImage = document.querySelector('.thumbnail img');
thumbnailImage.src = thumbnailDataURL;
// Убираем класс анимации после её завершения
flashElement.addEventListener('animationend', function() {
flashElement.classList.remove('shutterClick');
});
});
// переворота ещё не было
let firstInteractionDone = false;
// Находим кнопку переключения камеры и добавляем обработчик клика
document.querySelector(".switchCamera").addEventListener("click", function () {
// Проверяем, было ли первое взаимодействие (для автовоспроизведения)
if (!firstInteractionDone) {
videoElement.play();
firstInteractionDone = true;
}
// Проверяем текущий режим (веб-камера или видеофайл)
var isWebcam = videoElement.getAttribute("data-iswebcam") === "true";
// Запускаем анимацию «исчезновения»
videoElement.classList.add("flipping-out");
// Обработчик завершения анимации исчезновения
videoElement.onanimationend = () => {
if (isWebcam) {
// Если текущий режим — веб-камера, переключаемся на видеофайл
// Останавливаем все треки потока веб-камеры
if (videoElement.srcObject) {
const tracks = videoElement.srcObject.getTracks();
tracks.forEach((track) => track.stop());
}
// Очищаем srcObject и устанавливаем атрибуты для видеофайла
videoElement.srcObject = null;
videoElement.setAttribute("crossorigin", "anonymous");
videoElement.src = "video-codepen-dance.mp4";
videoElement.setAttribute("data-iswebcam", "false");
// Запускаем анимацию появления
videoElement.classList.remove("flipping-out");
videoElement.classList.add("flipping-in");
// Воспроизводим видео
videoElement.play();
} else {
// Если текущий режим — видеофайл, переключаемся на веб-камеру
// Проверяем поддержку API getUserMedia
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices
.getUserMedia({ video: true })
.then(function (stream) {
// Устанавливаем атрибуты для потока веб-камеры
videoElement.setAttribute("crossorigin", "anonymous");
videoElement.srcObject = stream;
videoElement.setAttribute("data-iswebcam", "true");
// Запускаем анимацию появления
videoElement.classList.remove("flipping-out");
videoElement.classList.add("flipping-in");
// Воспроизводим поток
videoElement.play();
})
.catch(function (err) {
console.error("Error accessing the camera", err);
});
} else {
console.error("Your browser does not support getUserMedia API");
}
}
// Обработчик завершения анимации появления
videoElement.onanimationend = () => {
videoElement.classList.remove("flipping-in");
};
};
});
// Находим кнопку миниатюры в DOM
let thumbnailButton = document.querySelector(".thumbnail");
// Добавляем обработчик события клика
thumbnailButton.addEventListener("click", function () {
// Создаём новый span-элемент для смайлика
let heart = document.createElement("span");
// Добавляем класс для стилизации
heart.classList.add("heart");
// Устанавливаем текстовое содержимое (эмодзи)
heart.textContent = "😉";
// Добавляем созданный элемент в DOM как дочерний для миниатюры
thumbnailButton.appendChild(heart);
// Запускаем анимацию через небольшой таймаут (10 мс)
setTimeout(() => {
// Задаём конечное положение (поднимается вверх)
heart.style.bottom = "100%";
// Делаем полностью видимым
heart.style.opacity = "1";
// Увеличиваем размер
heart.style.fontSize = "30px";
}, 10);
// Удаляем элемент через 2 секунды
setTimeout(() => {
thumbnailButton.removeChild(heart);
}, 2000);
});
Вам слово
Приходите к нам в соцсети поделиться своим мнением о проекте и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте: шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!