В этом уроке мы с вами вспомним детство и немного поиграем в кубики. А чтобы нас никто не заподозрил в старческом маразме, будем всем говорить, что делаем кубичекую 3D галерею.
Разворачиваем куб
Совершенно очевидно, что каждой из 6 сторон куба будет соответствовать свой HTML элемент. Кроме того, они должны быть повернуты так, чтобы своими гранями образовывать куб, а значит они должны быть в каком-то контейнере. Еще нам нужно будет ссылаться на каждую сторону чтобы спозиционировать их, поэтому придется задать каждому блоку соответсвующий класс. Итак, вот что мы получим:
<div class="cube">
<div class="cube-face cube-face-front"></div>
<div class="cube-face cube-face-back"></div>
<div class="cube-face cube-face-left"></div>
<div class="cube-face cube-face-right"></div>
<div class="cube-face cube-face-top"></div>
<div class="cube-face cube-face-bottom"></div>
</div>
Стилизация сторон куба
Мы написали теги, но куба я еще не вижу, так что давайте возьмем и напишем стили. Пользоваться мы будем SASS, иначе CSS код нас похоронит. Итак, пока ничего сложного:
$size: 150px; // длина куба
.cube {
width: $size;
height: $size;
position: relative;
}
.cube-face {
width: inherit;
height: inherit;
position: absolute;
background: red;
opacity: 0.5;
}
Обратите внимание, что каждая сторона куба имеет свойство position: absolute
, в результате чего они все наложились друг на друга. Хороший кубик для галереи у нас получился)). Но ничего: поскольку каждая сторона имеет свой собственный, отличный от других сторон куба, стиль (cube-face-front, например), то нам не составит труда стилизовать грани. Также я сделал полупрозрачной каждую сторону чтобы можно было сквозь них смотреть и видеть что происходит.
CSS3 3D концепции
Давайте изучим некоторые концепции CSS3 3D. Чтобы немного приблизить лицевую сторону, наложим ее на ось Z:
.cube-face-front {
color: blue;
transform: translate3d(0, 0, 20px);
}
Пока разницы не видно. Давайте разберемся почему.
Перспектива
Как упомянуто на сайте MDN:
Свойство перспектива определяет расстояние между плоскостью z=0 и пользователем для придания перспективы трехмерному элементу.
Проще говоря, его значение определяет степень трехмерности в пространстве. Чем меньше это значение, тем глубже эффект 3D. Без этого свойства элементы располагаются на экране в параллельной проекции, при которой все линии проекции параллельны друг другу. Поэтому независимо от того, насколько элемент приближается к вам или отдаляется от вас, его размер остается неизменным – в отличие от оптических законов реального мира (Более подробная информация о перспективе).
Мы будем установим это свойство в родительском контейнере нашего куба так, что все его дети (грани) изменятся от общей перспективы:
.cube {
width: $size;
height: $size;
position: relative;
perspective: 600px;
}
Как и ожидалось, теперь передняя поверхность действительно кажется больше в размере. Но все равно чего-то не хватает.
Transform-style
Даже после того как мы дали перспективу нашей сцене, у нас все еще есть проблемы. Передняя поверхность в идеале должна появиться над всеми остальными, скрывая их за собой. Но это не так.
Причина в том, что контейнер нашего куба не имеет контекста 3D рендеринга, который определяется следующим образом на CSSWG:
Иерархия контейнеров состоит из одного или нескольких уровней, образованных элементами со значением preserve-3d
для свойства transform-style, которые находятся в общей трехмерной системе координат.
Если не установить свойство transform-style
на preserve-3d
, его детские элементы будут плоскими, без стекового контекста. Поэтому даже если мы приблизим переднюю грань по оси Z, ее z-index останется неизменным, без учета ее положения в трехмерном пространстве.
Попробуйте назначить элементу .cube
это свойство и посмотреть, что происходит.
.cube {
width: $size;
height: $size;
position: relative;
perspective: 600px;
transform-style: preserve-3d;
}
Работает. Теперь, когда мы разобрались с 3D настройками, давайте сделаем наш куб!
Позиционирование граней
Мы будем брать по одной грани и настраивать для них соответствующее положение. Для начала разберемся с системой координат в CSS. Возьмем одну из граней нашего куба:
Front face lying flat with Z-axis coming towards us
Как видите, ось Z выходит прямо из элемента в нашем направлении. Поэтому когда мы размещаем элемент в области положительных значений оси Z, он становится ближе к нам. Заметьте, что эта система координат локальна для данного элемента. Давайте рассмотрим ее более подробно.
Мы снова используем переднюю грань и немного повернем ее по оси Y.
.cube-face-front {
transform: rotateY(40deg);
}
После этого наша лицевая сторона будет выглядеть так:
Axes rotate with the face.
Заметьте, что оси вращались вместе с элементом. Это значит, что ось Z больше не направлена прямо на нас. Теперь она повернута в направлении элемента. Поэтому если бы мы захотели переместить его вдоль оси Z, он бы двигался в том направлении, куда обращена его лицевая сторона.
Этот принцип мы будем использовать при позиционировании каждой грани нашего куба. Предположим, что центр куба находится в двухмерной плоскости экрана. Мы должны спозиционировать грани куба вокруг него в трехмерной плоскости. Помните, Мы вращаем грань так, чтобы она была повернута в нужном направлении, а потом переносим ее на ось Z.
Передняя грань
Здесь нечего вращать. Просто переместите лицевую сторону вперед на половину длины куба.
.cube-face-front {
transform: translate3d(0, 0, $size/2);
}
Задняя грань
Задняя грань будет повернута в направлении, противоположном тому, в котором повернута передняя. То есть, ее нужно будет повернуть на 180 градусов
по оси Y перед трансформацией. Это будет выглядеть так:
.cube-face-back {
transform: rotateY(180deg) translate3d(0, 0, $size/2);
}
С двумя гранями мы закончили. Осталось еще четыре.
Левая грань
Если вы все еще не до конца вникли в механизм трансформации, рассмотрим случай с данной конкретной гранью на наглядных примерах. В данный момент левая грань лежит в двухмерной плоскости (z=0):
Left face: in the 2d plane
Так как левая грань должна быть повернута влево, мы устанавливаем вращение на 90 градусов
:

Left face: after rotation
Как и все остальные грани, мы перемещаем ее по оси Z:

Left face: after translation
Так выглядит окончательный код для левой грани:
.cube-face-left {
transform: rotateY(-90deg) translate3d(0, 0, $size/2);
}
Правая грань
Здесь все так же, как и с левой гранью, только вращение происходит в противоположном направлении:
.cube-face-right {
transform: rotateY(90deg) translate3d(0, 0, $size/2);
}
Верхняя грань
Эта грань должна быть повернута вокруг оси Х на 90 градусов так, чтобы она была обращена вверх:
.cube-face-top {
transform: rotateX(90deg) translate3d(0, 0, $size/2);
}
Нижняя грань
Точно так же, указывая отрицательный поворот, мы позиционируем нижнюю грань:
.cube-face-bottom {
transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}
Завершим позиционирование граней и теперь у нас есть настоящий куб с помощью следующего CSS кода (я также добавил случайные цвета для каждой грани чтобы дифференцировать их):
$size: 150px; // длина куба
.cube {
margin: 100px;
width: $size;
height: $size;
position: relative;
perspective: 600px;
transform-style: preserve-3d;
}
.cube-face {
width: inherit;
height: inherit;
position: absolute;
background: red;
opacity: 0.8;
}
.cube-face-front {
background: yellow;
transform: translate3d(0, 0, $size/2);
}
.cube-face-back {
background: orange;
transform: rotateY(180deg) translate3d(0, 0, $size/2);
}
.cube-face-left {
background: green;
transform: rotateY(-90deg) translate3d(0, 0, $size/2);
}
.cube-face-right {
background: magenta;
transform: rotateY(90deg) translate3d(0, 0, $size/2);
}
.cube-face-top {
background: blue;
transform: rotateX(90deg) translate3d(0, 0, $size/2);
}
.cube-face-bottom {
background: red;
transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}
Теперь, чтобы повернуть куб мы можем просто применить повороты контейнера .cube
. Попробуйте повернуть его на 180 градусов вокруг оси Y (по вертикали):
.cube {
margin: 100px;
width: $size;
height: $size;
position: relative;
perspective: 600px;
transform-style: preserve-3d;
transform: rotateY(180deg);
}
Вы заметили что-то не так? Мы повернули куб на 180 градусов вокруг его вертикальной оси, а значит, мы должны увидеть заднюю сторону вместо передней. Мы видим ее, но по какой-то причине она выглядит меньше. Что мы сделали не так?
Исправляем перспективу
Как вы помните, мы придали трехмерность контейнеру куба. (.cube
). И когда мы вращали этот элемент, перспектива, обозначенная точкой схождения, вращалась вместе с ним. То есть, точка схождения, которая изначально находилась где-то за двухмерной плоскостью, после вращения оказалась перед двухмерной плоскостью, тем самым приведя к возникновению нашей проблемы.
В идеале перспектива не должна изменяться, оставаясь постоянной независимо от того, какой элемент мы трансформируем.
Как нам это исправить? Мы помещаем все наши элементы в еще один DIV
, для которого устанавливаем значение perspective
:
<div class="scene">
<div class="cube">
<div class="cube-face cube-face-front"></div>
<div class="cube-face cube-face-back"></div>
<div class="cube-face cube-face-left"></div>
<div class="cube-face cube-face-right"></div>
<div class="cube-face cube-face-top"></div>
<div class="cube-face cube-face-bottom"></div>
</div>
</div>
.scene {
margin: 100px;
width: $size;
height: $size;
perspective: 600px;
}
.cube {
position: relative;
width: inherit;
height: inherit;
transform-style: preserve-3d;
transform: rotateY(180deg);
}
Проверьте результат сейчас, все должно работать, как ожидалось. Попробуйте повращать его (например, так: transform: rotateX(30deg) rotateY(30deg)
), чтобы поиграть с ним немного (вы ведь не забыли, что мы играем с кубиками?)) ). После этого удалите свойство transform.
Добавляем интерактивность
Теперь мы добавим элементы управления для навигации по галерее. Для этого мы собираемся использовать хороший трюк под названием Чекбокс хак. Хотя мы будем использовать радио-кнопки, концепция остается той же.
Добавим переключатели, отвечающие за вращение галереи в наш HTML:
<!-- CONTROLS -->
<input type="radio" checked id="radio-front" name="select-face"/>
<input type="radio" id="radio-back" name="select-face"/>
<input type="radio" id="radio-left" name="select-face"/>
<input type="radio" id="radio-right" name="select-face"/>
<input type="radio" id="radio-top" name="select-face"/>
<input type="radio" id="radio-bottom" name="select-face"/>
<div class="scene">
<div class="cube">
<div class="cube-face cube-face-front"></div>
<div class="cube-face cube-face-back"></div>
<div class="cube-face cube-face-left"></div>
<div class="cube-face cube-face-right"></div>
<div class="cube-face cube-face-top"></div>
<div class="cube-face cube-face-bottom"></div>
</div>
</div>
Свяжем поворот нашей 3D галереи с переключателями при помощи CSS:
#radio-back:checked ~ .scene .cube {
transform: rotateY(180deg);
}
#radio-left:checked ~ .scene .cube {
transform: rotateY(90deg);
}
#radio-right:checked ~ .scene .cube {
transform: rotateY(-90deg);
}
#radio-top:checked ~ .scene .cube {
transform: rotateX(-90deg);
}
#radio-bottom:checked ~ .scene .cube {
transform: rotateX(90deg);
}
В приведенном выше коде мы просто указываем, когда включается каждая радиокнопка, что должно активировать вращение куба в этот момент.
Итоговый результат
Чтобы сгладить анимацию, мы добавим небольшой эффект перехода для куба и соответствующее выравнивание, получив следующий код:
<!-- Элементы управления -->
<input type="radio" checked id="radio-front" name="select-face"/>
<input type="radio" id="radio-left" name="select-face"/>
<input type="radio" id="radio-right" name="select-face"/>
<input type="radio" id="radio-top" name="select-face"/>
<input type="radio" id="radio-bottom" name="select-face"/>
<input type="radio" id="radio-back" name="select-face"/>
<!-- 3D галерея в кубе -->
<div class="scene">
<div class="cube">
<div class="cube-face cube-face-front"></div>
<div class="cube-face cube-face-back"></div>
<div class="cube-face cube-face-left"></div>
<div class="cube-face cube-face-right"></div>
<div class="cube-face cube-face-top"></div>
<div class="cube-face cube-face-bottom"></div>
</div>
</div>
$size: 150px; // длина куба
body {
text-align: center;
padding: 50px;
}
.scene {
display: inline-block;
margin-top: 50px;
width: $size;
height: $size;
perspective: 600px;
}
.cube {
position: relative;
width: inherit;
height: inherit;
transform-style: preserve-3d;
transition: transform 0.6s;
}
.cube-face {
width: inherit;
height: inherit;
position: absolute;
background: red;
opacity: 0.8;
}
// стороны куба
.cube-face-front {
background: yellow;
transform: translate3d(0, 0, $size/2);
}
.cube-face-back {
background: black;
transform: rotateY(180deg) translate3d(0, 0, $size/2);
}
.cube-face-left {
background: green;
transform: rotateY(-90deg) translate3d(0, 0, $size/2);
}
.cube-face-right {
background: magenta;
transform: rotateY(90deg) translate3d(0, 0, $size/2);
}
.cube-face-top {
background: blue;
transform: rotateX(90deg) translate3d(0, 0, $size/2);
}
.cube-face-bottom {
background: red;
transform: rotateX(-90deg) translate3d(0, 0, $size/2);
}
// элементы управления
#radio-back:checked ~ .scene .cube {
transform: rotateY(180deg);
}
#radio-left:checked ~ .scene .cube {
transform: rotateY(90deg);
}
#radio-right:checked ~ .scene .cube {
transform: rotateY(-90deg);
}
#radio-top:checked ~ .scene .cube {
transform: rotateX(-90deg);
}
#radio-bottom:checked ~ .scene .cube {
transform: rotateX(90deg);
}
Добавте с помощью CSS свойства background-image картинки на все стороны кубика и наслаждайтесь нашей 3D галереей.