Проект с камерой айфона в браузере: пишем рабочий скрипт

Как вдохнуть жизнь в красивый прототип

Проект с камерой айфона в браузере: пишем рабочий скрипт

Сегодня — продолжение проекта с интерфейсом камеры айфона в браузере. В прошлый раз мы сделали красивую картинку, сегодня будем её оживлять скриптами. А ещё при переключении на фронтальную камеру (если вы запустите код на ноуте, например) вы сможете сделать настоящее селфи!

Что мы уже сделали

К этому моменту у нас уже есть:

  • 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;
}

Логика проекта

Что мы будем делать:

  1. Подключим скрипт к HTML-файлу.
  2. Добавим работу кнопок зума.
  3. Настроим переворот камеры, чтобы можно было снимать с настоящей фронталки на ноуте.
  4. Сымитируем съёмку фото, чтобы превьюшки появлялись в нижнем левом углу.
  5. Добавим анимацию-заглушку там, где пока не будет рабочего скрипта.

Первый пункт самый простой: открываем файл 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 — видео, кнопке съёмки и элементу вспышки — и навешиваем обработчик события на кнопку съёмки. А вот что происходит после нажатия на кнопку:

  1. Активируем анимацию вспышки.
  2. Создаём временный canvas-элемент.
  3. Копируем текущий кадр видео на canvas.
  4. Преобразуем canvas в data URL.
  5. Обновляем источник изображения миниатюры.
  6. Убираем анимацию вспышки после завершения.

Проект с камерой айфона в браузере: пишем рабочий скрипт

Основа — элемент 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);
});

Вам слово

Приходите к нам в соцсети поделиться своим мнением о проекте и почитать, что пишут другие. А ещё там выходит дополнительный контент, которого нет на сайте: шпаргалки, опросы и разная дурка. В общем, вот тележка, вот ВК — велком!

Обложка:

Алексей Сухов

Корректор:

Елена Грицун

Соцсети:

Юлия Зубарева

Вёрстка:

Егор Степанов

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
А вы читали это?
hard