Увидев на просторах сети пару впечатляющих примеров 3D-трансформаций средствами CSS — заинтересовался, решил разобраться в теме, прочитал несколько статей, вроде бы что-то понял. Но, как известно, теория без практики – как зомби — мертва, хоть и может съесть
Для усвоения материала необходимо самому сделать что-нибудь любопытное с использованием прочитанного. Какой трехмерный объект сделать легче всего? Пожалуй, кубик. А чтобы результат получился интереснее и красивее, пусть это будет игральный кубик с точками на гранях. Поехали.
Для нетерпеливых и тех, кто смотрит Хабр ради забавных картинок – конечный результат. Работает в Chrome, последних версиях Firefox, Safari. Opera 12.01 — пока никак, ну а про IE вы и сами все знаете.
Стройматериалы
Для начала сделаем заготовку. Блок-контейнер, чтобы отцентрировать все на странице, блок-куб, в который будем вкладывать все составляющие нашей скульптуры, и «развертка» из пяти (шестую добавим потом) будущих граней куба.
«Грани» представляют собой банальные квадратные div’ы, абсолютно спозиционированные в блоке-кубе: «единичку» оставим в центре, она послужит лицевой стороной, а все остальные состыкуем с ней, как на рисунке 1. Покрасим их для веселья и наглядности в разные цвета, добавим внутреннюю тень – и хватит.
Точки на гранях – тоже блоки с position: absolute, border-radius: 50% и внутренней тенью (box-shadow: inset …) для придания иллюзии объема.
Напоследок — два не таких банальных момента.
Для самого куба нужно указать
transform-style: preserve-3d;
иначе будет использоваться значение по умолчанию – flat, трехмерный мир снова станет плоским и от нашего куба останется только одна грань, которая расположена «лицом» к пользователю.
Маленькая ремарка: здесь и далее css-свойства для трансформаций указаны без префиксов, но в реальном коде, пока что, необходимо их использовать.
Также добавим кубику свойство
transform: perspective(900px);
которое определит, как сильно он будет искажаться из-за перспективы. Чем больше значение, тем дальше находится точка «схождения линий», тем меньше выражен эффект искажения. Значения ниже 200 приводят к диким результатам (например, рисунок 2), около 300 – к заметному искажению (рисунок 3), мы же ограничимся скромными 900, дающими очень умеренный эффект.
С рутиной закончили, начинается интересное.
Сгибаем и клеим
Чтобы превратить нашу развертку в подобие кубика, нужно «согнуть» ее по линиям примыкания крайних граней к грани-1. Для этого нам надо сделать две вещи.
Во-первых, установить для каждой грани ось, вокруг которой она будет вращаться. Дело в том, что по умолчанию блоки поворачиваются вокруг своего центра, а нам нужно несколько иное поведение – на рисунке 4 видно, что грань-5 должна вращаться вокруг отрезка AB, грань-2 вокруг BC, и так далее.
Воспользуемся свойством
transform-origin: y x;
которое сдвигает оси вращения для объекта. Например, для грани-5 нужно ось X (горизонтальная) сдвинуть к нижнему краю, соответственно второе значение transform-origin должно быть 100% (transform-origin: 0 100%;). Значение Y в данном случае неважно, т.к. вращать эту грань мы будем исключительно вокруг горизонтальной оси. Для грани-2 все наоборот – значение X неважно, а Y должно быть установлено в 0, т.е. подойдет значение transform-origin: 0 0.
Во-вторых, и, для данной статьи, «в-главных», для непосредственного вращения элементов используем
transform: rotate3d(x, y, z, deg);
Первые три параметра определяют, вокруг какой из осей координат будет вращаться объект, а последний – на сколько градусов. X, Y и Z задаются не как абсолютные величины, а как соотношение углов. Например, код transform: rotate3d(2, 1, 0, 90deg); заставит объект повернуться на 90 градусов вокруг оси X и на 45 (90 * 1 / 2) градусов вокруг Y. То же самое сделает и строчка transform: rotate3d(90, 45, 0, 90deg).
На рисунке 5 я постарался проиллюстрировать вращение блоков вокруг осей: серый прямоугольник – исходное положение блока, красным выделяется ось, вокруг которой происходит вращение, красный и зеленый прямоугольники – положение блока при повороте на -60 и 60 градусов соответственно.
Повернем грани со второй по пятую на 90 градусов вокруг соответствующих осей, после чего, для наглядности, повернем и сам куб, добавив к .cube свойство transform: rotate3d(1,2,0, -150deg), в результате чего получится нечто, изображенное на рисунке 6.
Куб почти готов, осталось только «закрыть» его.
Закрываем крышкой и подаем к столу
Последнюю грань нужно расположить там же, где «единичку», но «глубже» в экран на 200px (размер граней куба). Сделаем это с помощью свойства
transform: translate3d(x, y, z);
которое позволяет сдвигать блок по любой из трех осей. В нашем случае – движение по оси Z на минус 200 пикселей (отрицательные значения «удаляют» блок, положительные – «приближают»). Одновременно повернем грань на 180 градусов по оси X – хотя, при значениях по умолчанию, «точки» будут отображаться с обеих ее сторон, правильнее будет поставить грань лицевой стороной к зрителю, а не внутрь куба. В итоге свойство transform для нашей «крышки» будет выглядеть следующим образом: transform: translate3d(0,0,-200px) rotate3d(1,0,0,180deg).
Напоследок, с помощью банального opacity: 0.9, добавим немного прозрачности (в Firefox, по невыясненной мной причине, работает только добавление этого свойства для отдельных граней, но не для всего блока-куба сразу) – так наш кубик становится немного похож на стеклянный, выглядит более презентабельно и, если честно, вызывает у автора приступ теплой ностальгии по тем временам, когда «компьютер» был непонятной машиной у папы на работе, а генератором псевдослучайных чисел во множестве игр выступал подобный игральный кубик.
Let’s rock, let’s roll
Кубик готов, но чтобы посмотреть на него с другой стороны, нужно лезть и править CSS – не самый user-friendly вариант, прямо скажем. Поступим по-другому.
В начало блока-контейнера (до куба) добавим четыре кнопки:
<button class="arrow a-top"></button>
<button class="arrow a-bottom"></button>
<button class="arrow a-left"></button>
<button class="arrow a-right"></button>
Опять же с помощью абсолютного позиционирования расположим их со всех четырех сторон контейнера, после чего заставим куб вращаться с помощью примерно такой вот магии:
.a-top:hover ~ .cube {
transform: perspective(900px) rotate3d(180,-45,0,-135deg);
}
Селектор «~», в отличие от «+», распространяется не только на непосредственного соседа, но и на «отстоящих» дальше, лишь бы они находились на одном уровне в DOM-дереве, чем мы и пользуемся. При наведении мыши на кнопку .a-top повернем следующий за ней блок .cube так, как нам хочется.
Одновременно с этим изменим перспективное искажение с помощью perspective(900px). Как вы помните (помните, правда?), мы установили такое же значение для куба в начале работы, но если не объявить это свойство снова после того, как куб станет к нам задом, а к лесу (на обоях рабочего стола) передом, то и это искажение вывернется вместе с кубом – ближняя к зрителю часть будет уменьшена, а дальняя – расширена. Выглядит неправдоподобно, поэтому будем назначать perspective заново при каждом повороте.
Ну и чтобы наш куб не прыгал из одного положения в другое, а плавно вращался – добавим ему строчку
transition: all 1s ease;
В переводе на русский — скажем кубу плавно изменять все свойства (all), которые будут изменяться, в течение одной секунды (1s) и делать это по функции ease, т.е. в начале анимации плавно ее ускорить, а в конце – плавно остановить. Любители равномерности вместо ease могут указать linear – в таком случае кубик будет двигаться нудно и бездушно.
Однако, Хьюстон, у нас еще одна проблема. Вращение идет вокруг центра грани-1, а не центра самого куба – что, в общем, логично, ведь это она у нас вписана в блок-куб, а все остальные – «загнуты» или вынесены «вглубь». Впрочем, решается это довольно просто: нужно просто «подвинуть» все грани ближе к наблюдателю с помощью все того же translate3d(0,0,100px) так, чтобы центр вращения совпал с центром куба.
Вот теперь получилось неплохо. Можно писать статью на Хабр, чтобы закрепить материал, поделиться изученным с коллегами и просто выпендриться.
Ссылка на демо
Архив для самых любознательных
P.S. Пока писал статью и пробивался в песочницу, на хабре уже появилась похожая статья, но, думаю, они будут хорошо дополнять друг друга.
Автор: PavlovM