Трансформация элементов. Часть 2: трёхмерные преобразования

Чтение — около 7 минут· без учёта практики

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

Оси X, Y и Z

У элемента три оси: X идёт слева направо, Y — сверху вниз, а Z — перпендикулярно экрану, к зрителю. Поворот «вокруг оси» представляйте как вращение вокруг стержня: вокруг X элемент кувыркается вперёд или назад, как страница перекидного календаря, вокруг Y — поворачивается, как дверь на петлях, а вокруг Z — вращается в плоскости экрана, как стрелка часов. Поэтому rotate() из первой части — это поворот вокруг оси Z.

Без перспективы трёхмерный поворот выглядит плоским: элемент, повёрнутый вокруг Y, просто сужается. Чтобы появилась глубина, в примерах вместе с поворотом задана функция perspective() — о ней ниже.

Оси CSS: положительная X направлена вправо, Y вниз, Z к наблюдателю. rotateX, rotateY и rotateZ поворачивают элемент вокруг соответствующей оси.
Ось Z выходит из плоскости экрана; на схеме её направление показано условно.

Трёхмерный поворот

Функции rotateX(), rotateY() и rotateZ() поворачивают элемент вокруг одной оси. Угол задают в тех же единицах, что и у rotate().

Поворот элементов по осям: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Поворот элементов по осям</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test">rotateX(60deg)</div></div>
    <div class="static"><div class="test2">rotateX(-160deg)</div></div>
    <div class="static"><div class="test3">rotateY(20deg)</div></div>
    <br>
    <div class="static"><div class="test4">rotateY(60deg)</div></div>
    <div class="static"><div class="test5">rotateZ(20deg)</div></div>
    <div class="static"><div class="test6">rotateZ(145deg)</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.3s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test:hover {
    transform: perspective(500px) rotateX(60deg);
}
.test2:hover {
    transform: perspective(500px) rotateX(-160deg);
}

.test3:hover {
    transform: perspective(500px) rotateY(20deg);
}
.test4:hover {
    transform: perspective(500px) rotateY(60deg);
}

.test5:hover {
    transform: rotateZ(20deg);
}

.test6:hover {
    transform: rotateZ(145deg);
}

Функция rotate3d() поворачивает элемент вокруг произвольной оси:

CSS
rotate3d(x, y, z, angle)

Первые три числа задают направление оси вращения — вектор от центра элемента, — а четвёртый параметр — угол поворота вокруг этой оси. rotate3d(1, 0, 0, 45deg) равносилен rotateX(45deg), а rotate3d(0, 0, 1, 45deg) — rotateZ(45deg). rotate3d(2, 1, 0, 50deg) поворачивает элемент на 50 градусов вокруг одной наклонной оси: она лежит в плоскости экрана и направлена ближе к оси X, чем к Y. Это не два отдельных поворота по осям: важно только направление вектора, поэтому rotate3d(4, 2, 0, 50deg) даёт тот же результат.

Трехмерный поворот элемента: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Трехмерный поворот элемента</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test">rotate3d(2,1,0, 50deg)</div></div>
    <div class="static"><div class="test2">rotate3d(2,1,0, -50deg)</div></div>
    <div class="static"><div class="test3">rotate3d(1,1,1, 50deg)</div></div>
    <br>
    <div class="static"><div class="test4">rotate3d(4,2,2, 50deg)</div></div>
    <div class="static"><div class="test5">rotate3d(4,2,2, -50deg)</div></div>
    <div class="static"><div class="test6">rotate3d(4,2,2, 1turn)</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.3s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test:hover {
    transform: rotate3d(2, 1, 0, 50deg);
}
.test2:hover {
    transform: rotate3d(2, 1, 0, -50deg);
}

.test3:hover {
    transform: rotate3d(1, 1, 1, 50deg);
}
.test4:hover {
    transform: rotate3d(4, 2, 2, 50deg);
}

.test5:hover {
    transform: rotate3d(4, 2, 2, -50deg);
}

.test6:hover {
    transform: rotate3d(4, 2, 2, 1turn);
}

Масштаб и сдвиг по оси Z

Функция scaleZ() масштабирует элемент по оси Z, а scale3d(x, y, z) — сразу по трём осям. У плоского элемента все точки изначально лежат при Z = 0, поэтому один scaleZ() не меняет его вид. Чтобы увидеть эффект, в примерах используется perspective(500px) scaleZ(2) rotateX(45deg): поворот создаёт разную глубину у точек, масштаб по Z меняет её, а перспектива делает разницу заметной. Порядок здесь важен: в perspective(500px) rotateX(45deg) scaleZ(2) масштаб по Z применяется к исходной плоскости и ничего не меняет. Так можно рассматривать действие на точки справа налево; в первой части мы описывали тот же результат через последовательное изменение системы координат слева направо.

Масштабирование по оси Z: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Масштабирование по оси Z</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test5">scaleZ(-2)</div></div>
    <div class="static"><div class="test6">scaleZ(6)</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.3s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test5:hover {
    transform: perspective(500px) scaleZ(-2) rotateX(45deg);
}

.test6:hover {
    transform: perspective(500px) scaleZ(6) rotateX(45deg);
}

Трехмерное масштабирование элементов по осям: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Трехмерное масштабирование элементов по осям</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test">scale3d(1,2,1)</div></div>
    <div class="static"><div class="test2">scale3d(2,1,1)</div></div>
    <div class="static"><div class="test3">scale3d(1,1,2)</div></div>
    <br>
    <div class="static"><div class="test4">scale3d(-1,-2,-1)</div></div>
    <div class="static"><div class="test5">scale3d(0.5,1,1)</div></div>
    <div class="static"><div class="test6">scale3d(0.5,0.5,2)</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.3s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test:hover {
    transform: perspective(500px) scale3d(1, 2, 1) rotateX(45deg);
}
.test2:hover {
    transform: perspective(500px) scale3d(2, 1, 1) rotateX(45deg);
}

.test3:hover {
    transform: perspective(500px) scale3d(1, 1, 2) rotateX(45deg);
}
.test4:hover {
    transform: perspective(500px) scale3d(-1, -2, -1) rotateX(45deg);
}

.test5:hover {
    transform: perspective(500px) scale3d(0.5, 1, 1) rotateX(45deg);
}

.test6:hover {
    transform: perspective(500px) scale3d(0.5, 0.5, 2) rotateX(45deg);
}

Функция translateZ() приближает элемент к зрителю (положительное значение) или отдаляет его (отрицательное), а translate3d(x, y, z) сдвигает элемент сразу по трём осям. С перспективой приближенный элемент выглядит крупнее, отдалённый — мельче.

Сдвиг по оси Z: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Сдвиг по оси Z</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test5">translateZ(30px)</div></div>
    <div class="static"><div class="test6">translateZ(-30px)</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.3s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test5:hover {
    transform: perspective(500px) rotateX(45deg) translateZ(30px);
}

.test6:hover {
    transform: perspective(500px) rotateX(45deg) translateZ(-30px);
}

Трехмерный сдвиг элементов: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Трехмерный сдвиг элементов</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test">translate3d(20px,20px,20px)*</div></div>
    <div class="static"><div class="test2">translate3d(-20px,-20px,-20px)*</div></div>
    <div class="static"><div class="test3">translate3d(-20px,-20px,-80px)*</div></div>
    <br>
    <div class="static"><div class="test4">translate3d(0,0,-80px)*</div></div>
    <div class="static"><div class="test5">translate3d(20px,0, 40px)*</div></div>
    <div class="static"><div class="test6">translate3d(0,20px, 40px)*</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.3s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test:hover {
    transform: perspective(500px) rotateX(45deg) translate3d(20px, 20px, 20px);
}
.test2:hover {
    transform: perspective(500px) rotateX(45deg) translate3d(-20px, -20px, -20px);
}

.test3:hover {
    transform: perspective(500px) rotateX(45deg) translate3d(-20px, -20px, -80px);
}
.test4:hover {
    transform: perspective(500px) rotateX(45deg) translate3d(0, 0, -80px);
}

.test5:hover {
    transform: perspective(500px) rotateX(45deg) translate3d(20px, 0, 40px);
}

.test6:hover {
    transform: perspective(500px) rotateX(45deg) translate3d(0, 20px, 40px);
}

Перспектива

Функция perspective() задаёт расстояние от зрителя до плоскости элемента и создаёт иллюзию глубины. Чем больше значение, тем дальше зритель и тем слабее искажения; при маленьком значении эффект резкий, будто смотришь вплотную. Отрицательная длина недопустима, а значения меньше 1px, включая ноль, при отрисовке считаются равными 1px. Для обычной сцены удобнее сотни пикселей. В примерах функцию ставят первой в списке transform, чтобы перспектива проецировала результат остальных преобразований. Это рекомендуемый порядок для такой сцены, а не требование синтаксиса: другое положение функции тоже допустимо, но меняет результат.

Указание перспективы обзора элемента: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Указание перспективы обзора элемента</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test">perspective(50px)*</div></div>
    <div class="static"><div class="test2">perspective(100px)*</div></div>
    <div class="static"><div class="test3">perspective(200px)*</div></div>
    <br>
    <div class="static"><div class="test4">perspective(300px)*</div></div>
    <div class="static"><div class="test5">perspective(400px)*</div></div>
    <div class="static"><div class="test6">perspective(500px)*</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.3s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test:hover {
    transform: perspective(50px) rotateX(45deg);
}
.test2:hover {
    transform: perspective(100px) rotateX(45deg);
}

.test3:hover {
    transform: perspective(200px) rotateX(45deg);
}
.test4:hover {
    transform: perspective(300px) rotateX(45deg);
}

.test5:hover {
    transform: perspective(400px) rotateX(45deg);
}

.test6:hover {
    transform: perspective(500px) rotateX(45deg);
}

Свойство perspective задают не самому элементу, а его родителю: тогда у всех трёхмерных потомков одна общая точка зрения, как у реальной сцены. Свойство perspective-origin задаёт, откуда смотрит зритель: по умолчанию — из центра родителя.

Свойство perspective и функция perspective(): попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Свойство perspective и функция perspective()</title>
    <style>
      body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
      .row { display: flex; gap: 1rem; margin-bottom: 1.5rem; padding: 1rem; border: 1px dashed tan; }
      .card {
        width: 6rem;
        height: 6rem;
        display: grid;
        place-items: center;
        background-color: saddlebrown;
        color: white;
      }
      /* Функция: у каждой карточки своя точка зрения — все повёрнуты одинаково */
      .own .card {
        transform: perspective(300px) rotateY(45deg);
      }
      /* Свойство родителя: одна точка зрения на всех — карточки повёрнуты по-разному относительно взгляда */
      .shared {
        perspective: 300px;
        perspective-origin: 50% 50%; /* откуда смотрим: центр ряда */
      }
      .shared .card {
        transform: rotateY(45deg);
      }
    </style>
  </head>
  <body>
    <p>perspective() у каждой карточки</p>
    <div class="row own"><div class="card">1</div><div class="card">2</div><div class="card">3</div></div>
    <p>perspective у ряда</p>
    <div class="row shared"><div class="card">1</div><div class="card">2</div><div class="card">3</div></div>
  </body>
</html>

В верхнем ряду у каждой карточки своя перспектива, и все три выглядят одинаково. В нижнем перспектива общая: зритель смотрит на ряд из центра, поэтому левая и правая карточки повёрнуты к нему по-разному. Измените perspective-origin на 0% 50% — точка зрения сместится к левому краю.

Точка преобразования по оси Z

Третье значение transform-origin задаёт положение точки преобразования по оси Z — только длиной, без процентов и ключевых слов. Отрицательное значение уводит точку за экран, положительное — к зрителю, и при поворотах вокруг X или Y элемент описывает дугу вокруг неё. При повороте вокруг Z третья координата точки опоры ничего не меняет.

Точка преобразования по оси Z: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Точка преобразования по оси Z</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test5">50% 50% -250px</div></div>
    <div class="static"><div class="test6">50% top 250px</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.2s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test5:hover {
    transform: perspective(500px) rotateY(35deg);
    transform-origin: 50% 50% -250px;
}

.test6:hover {
    transform: perspective(500px) rotateY(35deg);
    transform-origin: 50% top 250px;
}

Трёхмерная сцена: transform-style и backface-visibility

По умолчанию потомки трансформированного элемента отображаются плоско — в его плоскости. Свойство transform-style со значением preserve-3d сохраняет их в трёхмерном пространстве: каждый потомок можно повернуть относительно родителя, и все они повернутся вместе с ним.

Свойство backface-visibility определяет, видна ли обратная сторона элемента, повёрнутого к зрителю спиной. Значение hidden её прячет. Вместе эти свойства дают карточку-перевёртыш: две стороны в одной карточке, оборот заранее повёрнут на 180 градусов, а при повороте карточки видна то одна, то другая сторона. Именно её мы сделаем в задании.

Матрицы преобразований

Все трансформации в CSS браузер сводит к двум функциям:

  • matrix() — двухмерное преобразование матрицей из шести значений;
  • matrix3d() — трёхмерное преобразование матрицей 4 × 4 из шестнадцати значений.

Матрица двухмерного преобразования выглядит так:

CSS
matrix(a, b, c, d, e, f)

┌ a  c  e ┐
│ b  d  f │
└ 0  0  1 ┘
  • a, b, c и d вместе задают линейную часть преобразования: масштаб, поворот и наклон. При чистом масштабе a и d — масштаб по осям X и Y, а b = c = 0. При чистом наклоне a = d = 1, c — тангенс угла наклона по X, а b — по Y.
  • e и f — сдвиг по осям X и Y.

Применяя двухмерную трансформацию, браузер умножает матрицу на вектор [x, y, 1], где x и y — координаты точки элемента:

CSS
┌ a  c  e ┐   ┌ x ┐   ┌ a·x + c·y + e ┐
│ b  d  f │ × │ y │ = │ b·x + d·y + f │
└ 0  0  1 ┘   └ 1 ┘   └       1       ┘

x′ = a·x + c·y + e
y′ = b·x + d·y + f

Матрица элемента без преобразования — единичная:

CSS
transform: matrix(1, 0, 0, 1, 0, 0);

Вручную матрицы пишут редко: проще комбинировать понятные функции. Но их стоит узнавать: на вкладке Computed инструментов разработчика браузер показывает итоговое значение transform именно в виде matrix() или matrix3d(). Отдельные свойства translate, rotate и scale показываются отдельно и не включаются в значение transform.

Матрицы преобразований в CSS: попробуйте сами

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Матрицы преобразований в CSS</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="static"><div class="test">matrix(0.5,0,0,1,0,0)</div></div>
    <div class="static"><div class="test2">matrix(1,0,0,1.5,0,0)</div></div>
    <div class="static"><div class="test3">matrix(0,-1, 1,0,0,0)</div></div>
    <br>
    <div class="static"><div class="test4">matrix(1,0, -1,1,0,0)</div></div>
    <div class="static"><div class="test5">matrix(1,0, 0,1,50,0)</div></div>
    <div class="static"><div class="test6">matrix3d()</div></div>
  </body>
</html>
style.css
CSS
.static {
    display: inline-block;
    margin: 10px;
    background: gray;
}
div {
    width: 180px;
    height: 100px;
    line-height: 100px;
    text-align: center;
    transition: 0.3s;
}
.test2,
.test4,
.test6 {
    background: orange;
}
.test,
.test3,
.test5 {
    background: plum;
}

.test:hover {
    transform: matrix(0.5, 0, 0, 1, 0, 0);
}
.test2:hover {
    transform: matrix(1, 0, 0, 1.5, 0, 0);
}

.test3:hover {
    transform: matrix(0, -1, 1, 0, 0, 0);
}
.test4:hover {
    transform: matrix(1, 0, -1, 1, 0, 0);
}

.test5:hover {
    transform: matrix(1, 0, 0, 1, 50, 0);
}

.test6:hover {
    transform: matrix3d(0.3, -0.4, 0.8, 0, 0.2, 0.9, 0.4, 0, -0.9, 0, 0.4, 0, 0, 0, 0, 1);
}

Подробнее о матрицах — в статье «Разбираемся с матрицами CSS-трансформаций» (на русском) и в стандарте CSS Transforms.

Измените угол и точку опоры. Пунктир показывает исходное положение фото.

Тони

Включите JavaScript, чтобы менять настройки. Поворот фото: transform: rotateY(-6deg); transform-origin: center.

CSS для примера

.scene {
  perspective: 800px;
}

.box {
  transform: rotateY(-6deg);
}

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

Проверьте себя

Выберите ответ — объяснение появится сразу. Ответ можно менять.

1. Вокруг какой оси вращается элемент при rotate() из первой части?
Показать ответ

Вокруг оси Z — перпендикулярной экрану — Поворот в плоскости экрана — это вращение вокруг оси Z, как у стрелки часов.

2. Что означает rotate3d(1, 0, 0, 45deg)?
Показать ответ

То же, что rotateX(45deg) — Первые три числа задают направление оси: здесь — ось X.

3. Почему scaleZ(2) без других функций у плоского элемента с обычной точкой опоры ничего не меняет?
Показать ответ

Все точки исходного элемента лежат при Z = 0, и масштаб по Z не сдвигает их — Для заметного эффекта: perspective(500px) scaleZ(2) rotateX(45deg). Если поменять scaleZ и rotateX местами, масштаб снова действует на исходную плоскость.

4. Где удобно поставить perspective(), чтобы перспектива проецировала результат остальных функций transform?
Показать ответ

Первой, перед поворотом и другими преобразованиями — transform: perspective(500px) rotateX(45deg).

5. Чем свойство perspective у родителя отличается от функции perspective() у каждого потомка?
Показать ответ

У свойства одна точка зрения на всех потомков, у функции — своя у каждого — Поэтому с общей перспективой карточки ряда повёрнуты к зрителю по-разному.

6. Карточка-перевёртыш при повороте показывает зеркальное фото вместо подписи. Чего не хватает?
Показать ответ

backface-visibility: hidden у сторон — Без него сторона, повёрнутая спиной, остаётся видна и закрывает оборот.

7. Зачем карточке transform-style: preserve-3d?
Показать ответ

Чтобы стороны остались в трёхмерном пространстве и поворачивались вместе с карточкой — По умолчанию потомки сплющиваются в плоскость родителя.

8. Что показывает вкладка Computed у элемента с transform: rotate(45deg)?
Показать ответ

Значение в виде matrix(…) — Браузер сводит любое преобразование к матрице.

Задание

Ожидаемый результат: сначала видно фото Тони с белкой. При наведении карточка плавно переворачивается, как настоящая, и на коричневом обороте появляется подпись. Убираете указатель — карточка поворачивается обратно. Если вместо подписи видно зеркальное фото, проверьте backface-visibility; если карточка сплющивается без объёма — перспективу и preserve-3d.

Выполните задание

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8" />
    <title>Карточка-перевёртыш</title>
    <style>
      body {
        font-family: system-ui, sans-serif;
        background-color: linen;
        color: #2b211b;
      }
      .scene {
        width: 180px;
        height: 240px;
      }
      .scene:focus-visible {
        outline: 3px solid saddlebrown;
        outline-offset: 4px;
      }
      .card {
        position: relative;
        width: 180px;
        height: 240px;
        transition: transform 0.6s;
      }
      .side {
        position: absolute;
        inset: 0;
        border-radius: 12px;
        overflow: hidden;
      }
      .front img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        background-color: #fffaf3;
      }
      .back {
        display: grid;
        place-items: center;
        padding: 1rem;
        background-color: saddlebrown;
        color: white;
        text-align: center;
      }
    </style>
  </head>
  <body>
    <div class="scene" tabindex="0">
      <div class="card">
        <div class="side front">
          <img src="assets/album-squirrel.png" alt="Тони с плюшевой белкой" width="300" height="400" />
        </div>
        <div class="side back">С плюшевой белкой: с ней я отдыхаю после прогулки.</div>
      </div>
    </div>
  </body>
</html>

Дополнительная задача

Соберите раскрывающуюся фигуру из четырёх зелёных квадратов вокруг красного центра. На первом этапе разведите квадраты сдвигом и поворотом; на втором поверните всю фигуру на полный оборот. Позиционирование и плавность уже подготовлены: замените ??? только у transform и transform-origin.

Подсказки:

  • Родителю уже задано относительное позиционирование, детям — абсолютное, как в уроке «Позиционирование элементов. Часть 1».
  • Двум вложенным элементам задайте rotate(), двум другим — translateY(); двум элементам измените точку преобразования.
  • Переходы и задержка родителя уже заданы: сначала за 1 секунду расходятся дети, затем за 1 секунду поворачивается родитель. Эти настройки пока не меняйте; переходы разберём в следующем уроке.

Дополнительное задание: вращающиеся квадраты

Разведите четыре зелёных квадрата вокруг красного: два сдвигом, два поворотом. Затем поверните всю фигуру на полный оборот с задержкой, как в образце.

Подсказка

Используйте translateY() для вертикального сдвига и rotate() для поворота. Точка transform-origin задаёт угол вращения боковых квадратов. Готовая задержка родителя даёт дочерним элементам время разойтись.

index.html
HTML
<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <title>Дополнительное задание</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body>
    <div class="main-div">
      <div class="sub-div first"></div>
      <div class="sub-div second"></div>
      <div class="sub-div third"></div>
      <div class="sub-div fourth"></div>
    </div>
  </body>
</html>
style.css
CSS
div {
    width: 75px;
    height: 75px;
}
.main-div {
    position: relative;
    background: red;
    margin: 100px auto;
}
.sub-div {
    position: absolute;
    background: green;
    transition: transform 1s; /* готовая настройка следующего урока */
}
.second {
    transform-origin: ???;
}
.fourth {
    transform-origin: ???;
}
.main-div:hover {
    transition: transform 1s 1s; /* сначала расходятся дети, затем вращается родитель */
    transform: ???;
}
.main-div:hover .first {
    transform: ???;
}
.main-div:hover .second {
    transform: ???;
}
.main-div:hover .third {
    transform: ???;
}
.main-div:hover .fourth {
    transform: ???;
}