Во всех наших прошлых проектах с пинг-понгом мы использовали JavaScript, чтобы управлять платформами и шариком. Но можно обойтись и без него — и написать всё на чистом CSS. Играть, правда, пока не получится, зато выглядит классно.
Так что сегодня у нас проект с завораживающей анимацией на чистом CSS. Ничего сложного, просто красиво.
Что делаем
Страничку, на которой бесконечно движутся платформы и отбивают шарики — заодно попрактикуемся в CSS. Можно просто расслабиться и посмотреть на идеальные синхронные отбивания. Глупость, но приятно.
Готовим страницу
Всё, что нам нужно сделать, — подготовить область и нарисовать в ней две платформы и два круга:
<!DOCTYPE html>
<html lang="ru" >
<head>
<meta charset="UTF-8">
<title>Заставка с пинг-понгом</title>
<!-- подключаем файл со стилями -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- указываем внутренние размеры графического контейнера, чтобы привязать к ним все элементы -->
<svg viewBox="0 0 100 75">
<!-- рисуем левую и правую платформы -->
<path d="m2,0 v15" />
<path d="m98,0 v15" />
<!-- рисуем оба круга на одном и том же месте -->
<circle cx="50" cy="68" r="5" />
<circle cx="50" cy="68" r="5" />
</svg>
</body>
</html>
Первый элемент на странице, который требует подробного пояснения — это блок <svg> с параметром viewBox. Работает так:
- Пишем параметр <svg viewBox="0 0 100 75">
- В этом параметре указаны 4 числа.
- Первые два числа — координаты левого верхнего угла, а вторые два — координаты правого нижнего угла.
- Эта система координат будет использоваться для всех элементов внутри блока <svg>.
- Это значит, что круг с координатами (50, 37) будет находиться ровно по центру этого блока, потому что координаты привязаны к значениям из п. 2.
Внутри этого блока нарисуем две платформы, например, так:
<path d="m2,0 v15">
А вот как это работает:
- <path> рисует какое-то векторное изображение по правилам, которые описаны внутри тега.
- d — отвечает за параметры рисования, которые в нём указаны.
- m2,0 — перемещает виртуальный карандаш к точке с координатами (2,0).
- v15 — рисует вертикальную линию вниз на указанное число.
С кругами всё проще: мы указываем только центр и радиус. То, что мы рисуем два круга в одном и том же месте — не опечатка. В стилях мы их разведём.
Настраиваем внешний вид элементов
Чтобы всё выглядело эффектнее, сделаем тёмный фон и расположим область рисования по центру страницы:
/* общие настройки для всей страницы */
body {
background: #16161c;
height: 100vh;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
Теперь настроим параметры самой области рисования:
/* задаём размеры области рисования SVG-элементов */
svg {
/* ширина и высота привязана к размерам экрана */
width: 100vmin;
height: 75vmin;
/* убираем обводку у области рисования */
border: 0px dotted #f3f9f4;
position: absolute;
/* ставим тёмный фон */
background: #ffffff02;
}
Здесь же укажем толщину линии для рисования фигур и её цвет. У нас виртуальные размеры области рисования — 100 на 75 пикселей, поэтому линия из четырёх пикселей получается толстой:
/* общие параметры рисования платформы и кругов */
path,
circle {
stroke: #f3f9f4;
stroke-width: 4px;
fill: none;
}
Добавляем анимацию
Обычно мы добавляем к каждому элементу на странице параметр id, чтобы можно было отличать его от остальных элементов. Но есть и другой способ: использовать псевдокласс :nth-of-type
, например:
path:nth-of-type(2) {
animation-delay: -1875ms;
}
Работает так:
- Число в скобках — порядковый номер элемента такого типа среди других таких же элементов.
- Нумерация начинается с единицы.
- В каком порядке мы описывали элементы на странице — в таком порядке они и создаются.
- Получается, path:nth-of-type(2) позволит нам обратиться ко второму элементу path на странице, то есть ко второй платформе.
Чтобы не делать одинаковое время запуска анимации для правой платформы и её круга, установим задержку анимации для этих элементов:
/* ставим задержку начала анимации для второго круга и платформы */
circle:nth-of-type(2) {
animation-delay: -2500ms;
}
path:nth-of-type(2) {
animation-delay: -1875ms;
}
Теперь можем указать общие параметры для кругов и платформ: из-за разного начала момента анимации всё начнёт двигаться в нужный момент и круги не будут слипаться друг с другом.
/* общая настройка анимации кругов */
circle {
/* устанавливаем бесконечную анимацию длительностью 5 секунд с постоянной скоростью, а раскадровку анимации берём из параметра circleAnimation */
animation: circleAnimation 5000ms infinite linear;
}
/* общая настройка анимации платформ */
path {
/* отдельно указываем вторым параметром время сдвига начала второй платформы */
/* также указываем, что анимация бесконечная, на каждом следующем цикле анимации фигура должна двигаться в обратном направлении, а начало и конец анимации должны быть плавными */
animation: paddleAnimation 1250ms -625ms infinite alternate ease-in-out;
}
Прописываем раскадровку
У нас заданы параметры анимации, но ничего пока не движется. Всё дело в том, что у нас не написано, как именно будет происходить анимация.
Смысл в том, чтобы задать, что делать в ключевых кадрах. Например, мы можем сказать, что за первую четверть анимации круг должен сместиться вниз и влево, к половине анимации он должен достигнуть правого края, а затем сместиться вверх и влево. Если это записать на языке CSS, то анимация будет выглядеть так:
/* раскадровка анимации кругов */
@keyframes circleAnimation {
25% {
transform: translate(-39px, -30.5px);
}
50% {
transform: translate(0px, -61px);
}
75% {
transform: translate(39px, -30.5px);
}
}
/* раскадровка анимации платформы */
@keyframes paddleAnimation {
100% {
transform: translateY(60px);
}
}
Видно, что у платформы анимация простая — сместиться вдоль оси Y на 60 пикселей, а у круга сложная — надо прописать каждое положение, в котором происходит отскок, и куда круг дальше движется.
Вот и всё, проект готов: платформы двигаются и отбивают круги с идеальной точностью. На странице проекта можно посмотреть, как это работает.
<!DOCTYPE html>
<html lang="ru" >
<head>
<meta charset="UTF-8">
<title>Заставка с пинг-понгом</title>
<!-- подключаем файл со стилями -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- указываем внутренние размеры графического контейнера, чтобы привязать к ним все элементы -->
<svg viewBox="0 0 100 75">
<!-- рисуем левую и правую платформы -->
<path d="m2,0 v15" />
<path d="m98,0 v15" />
<!-- рисуем оба круга на одном и том же месте -->
<circle cx="50" cy="68" r="5" />
<circle cx="50" cy="68" r="5" />
</svg>
</body>
</html>
/* общие настройки для всей страницы */
body {
background: #16161c;
height: 100vh;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
/* задаём размеры области рисования SVG-элементов */
svg {
/* ширина и высота привязана к размерам экрана */
width: 100vmin;
height: 75vmin;
/* убираем обводку у области рисования */
border: 0px dotted #f3f9f4;
position: absolute;
/* ставим тёмный фон */
background: #ffffff02;
}
/* общие параметры рисования платформы и кругов */
path,
circle {
stroke: #f3f9f4;
stroke-width: 4px;
fill: none;
}
/* ставим задержку начала анимации для второго круга и платформы */
circle:nth-of-type(2) {
animation-delay: -2500ms;
}
path:nth-of-type(2) {
animation-delay: -1875ms;
}
/* общая настройка анимации кругов */
circle {
/* устанавливаем бесконечную анимацию длительностью 5 секунд с постоянной скоростью, а раскадровку анимации берём из параметра circleAnimation */
animation: circleAnimation 5000ms infinite linear;
}
/* общая настройка анимации платформ */
path {
/* отдельно указываем вторым параметром время сдвига начала второй платформы */
/* также указываем, что анимация бесконечная, на каждом следующем цикле анимации фигура должна двигаться в обратном направлении, а начало и конец анимации должны быть плавными */
animation: paddleAnimation 1250ms -625ms infinite alternate ease-in-out;
}
/* раскадровка анимации кругов */
@keyframes circleAnimation {
25% {
transform: translate(-39px, -30.5px);
}
50% {
transform: translate(0px, -61px);
}
75% {
transform: translate(39px, -30.5px);
}
}
/* раскадровка анимации платформы */
@keyframes paddleAnimation {
100% {
transform: translateY(60px);
}
}