Проект: делаем интерактивный интерфейс камеры телефона

В браузере можно сделать что угодно, даже айфон

Проект: делаем интерактивный интерфейс камеры телефона

Настало время интерактивных проектов: сегодня сделаем интерфейс камеры телефона, который имитирует сьёмку фото с фронтальной камеры и селфи, если перевернуть камеру. Сделаем визуальную часть, а логикой займёмся во второй (чтобы можно было снимать себя по-настоящему).

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

Наша задача — подготовить весь интерфейс и интерактивную часть с анимациями, чтобы получить виртуальный мобильный телефон в браузере. Вот что мы для этого сделаем:

  1. Сделаем HTML- и CSS-файлы, которые будем наполнять в процессе.
  2. Создадим контейнер для камеры (imageContainer).
  3. Стилизуем камеру, чтобы интерфейс выглядел реалистично.
  4. Добавим кнопки управления.
  5. Сделаем анимацию вспышки и переворота камеры.

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

Готовим каркас

На старте сделаем самую базу проекта: создадим стандартную HTML-разметку, подключим CSS-файл и иконки Google Material Icons и добавим контейнер для будущего интерфейса камеры.

А ещё мы сразу добавим на страницу видео, которое будет имитировать то, что мы как бы снимаем фронтальной камерой. Это нам сильно пригодится в дальнейшем.

Начнём с HTML:

Готовим каркас

На старте сделаем самую базу проекта: создадим стандартную HTML-разметку, подключим CSS-файл и иконки Google Material Icons и добавим контейнер для будущего интерфейса камеры.

А ещё мы сразу добавим на страницу видео, которое будет имитировать то, что мы как бы снимаем фронтальной камерой. Это нам сильно пригодится в дальнейшем.

Начнём с HTML:

<!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>

</body>
</html>

Пока на странице крутится только видеоролик — это нормально, потому что мы ещё не настраивали стили. Займёмся теперь и этим.

Настраиваем CSS

Теперь очередь CSS. Создадим новый файл и сделаем там такое:

  • Сбросим стили.
  • Настроим центрирование и стартовые стили камеры.
  • Добавим скруглённые углы.
  • Поставим видео тоже по центру.

В общем, создадим простейший интерфейс телефона и сделаем так, чтобы в нём крутилось наше видео:

/* Сброс стандартных стилей браузера */
*,
*::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; /* Центрирование */
}
Проект: делаем интерактивный интерфейс камеры телефона

Добавляем кнопки зума

Теперь нам нужно создать интерактивную панель масштабирования с тремя уровнями зума (0,5×, 1×, 3×). Для этого мы используем data-zoom, который будет хранить текущее значение масштаба, и span внутри кнопок — в нём будет символ × для обозначения выбранного масштаба.

Сначала добавим блок с кнопками зума внутри .imageContainer (после .imageContainerInner):

  <!-- Панель кнопок зума -->
  <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>

Теперь поработаем со стилями: 

  • добавим три кнопки;
  • выровняем их по горизонтали;
  • положим их поверх видео;
  • сделаем их круглыми и настроим шрифт и цвета.

Чтобы было понятнее, как обычно мы прокомментировали каждую строку кода:

/* Контейнер кнопок зума */
.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; /* Уменьшаем размер */
}

Работать кнопки пока не будут, но выглядят уже хорошо:

Проект: делаем интерактивный интерфейс камеры телефона

Кнопки съёмки и переключения камеры

Теперь нам нужно создать нижнюю панель управления с тремя элементами:

  1. Миниатюра последнего снимка.
  2. Основная кнопка съёмки.
  3. Кнопка переключения камеры.

Для этого мы добавим в HTML .thumbnail (будущий снимок), .camerabutton (центральный элемент) и .switchCamera — кнопку с иконкой переключения камеры (справа).

Добавим это в HTML-файл:

<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>

В стилях мы настроим внешний вид новых элементов: разместим их внизу виртуального телефона, придадим нужную форму и стилизуем их под привычный интерфейс камеры. Пока всё визуально красиво, но не работает (это мы так, на всякий случай напоминаем):

/* Контейнер нижней панели управления */
.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; /* Размер иконки */
}
Проект: делаем интерактивный интерфейс камеры телефона

Добавляем вспышку

Первая часть проекта плавно подходит к концу, поэтому причешем мелочи — и начнём с анимации вспышки (чтобы всё как по-настоящему).

Чтобы вспышка потом работала, убедимся, что она у нас есть в HTML-файле — и это всё, что там потребуется:

<div class="imageContainerInner">
  <video id="videoElement" ...></video>
  <!-- Элемент вспышки (уже существует) -->
  <div class="flash"></div>
</div>

А вот со стилями уже интереснее, тут всё состоит из трёх частей.

Элемент .flash

  • Абсолютное позиционирование для покрытия всей области видео.
  • Изначально прозрачный (background: transparent).
  • z-index: 9 — выше видео, но ниже кнопок управления.

Анимация cameraFlash

  • Использует box-shadow вместо background для плавного эффекта.
  • inset делает тень внутренней (заполняет весь контейнер).
  • 100px — достаточно большое размытие для полного покрытия.

Класс .shutterClick

  • alternate-reverse — анимация проигрывается в обратном направлении.
  • Задержка 0.3s даёт время на обработку снимка перед вспышкой.

Кроме этого, сделаем сразу раскадровку анимации, чтобы всё выглядело естественно. Без скрипта работать, конечно, не будет, но раз готовим визуал, сделаем всё заранее:

/* Стили элемента вспышки */
.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: количество повторений
  */
}

Анимация переворота камеры

Наконец, сделаем эффект 3D-переворота при переключении между основной и фронтальной камерами.

Технически мы сейчас ничего не увидим, зато через неделю будет сильно проще заниматься логикой и скриптом, поэтому финальный рывок — добавляем этот код в CSS-файл:

/* Анимация исчезновения (переворот на 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;
}

Посмотреть интерфейс камеры на странице проекта

Что дальше

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

Вам слово

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

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

Обложка:

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

Корректор:

Елена Грицун

Вёрстка:

Кирилл Климентьев

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