Вызов переднего плана принят: CSS 3D Cube
Опубликовано: 2022-03-10Вам нравятся вызовы? Готовы ли вы взяться за задачу, с которой никогда раньше не сталкивались, и сделать ее в установленные сроки? Что, если при выполнении задания вы столкнетесь с проблемой, которая кажется неразрешимой? Я хочу поделиться своим опытом использования 3D-эффектов CSS в первый раз в реальном проекте и вдохновить вас на новые вызовы.
Это был обычный день, когда мне написал Евгений, менеджер CreativePeople. Он прислал мне видео и объяснил, что разрабатывает концепцию для нового проекта и интересуется, смогу ли я разработать что-то вроде того, что было в видео.
Дальнейшее чтение на SmashingMag:
- Beercamp: Эксперимент с CSS 3D
- Создание адаптивных форм с помощью Clip-Path и выход из коробки
- Давайте поиграем с аппаратным ускорением CSS
Это был трехмерный объект (прямоугольный, если быть точным), который вращался вокруг одной из осей. У меня уже был некоторый опыт работы с CSS 3D, и в голове начало формироваться решение. Я погуглил такие ключевые слова, как «3D-куб CSS», чтобы подтвердить свои идеи, и ответил Юджину, что это возможно.
Следующий вопрос Жени был, возьмусь ли я за проект? Мне нравятся сложные задачи, поэтому я не мог отказаться. В то время я не осознавал, во что ввязываюсь, но был более чем полон решимости.
Заточите свои топоры
Вспомним про топоры — не боевые топоры, а числовые линии, те самые оси, что и в трехмерной декартовой системе координат, которую мы изучали в школе. Как говорит нам Википедия:
Декартова система координат для трехмерного пространства представляет собой упорядоченную тройку прямых (осей), которые попарно перпендикулярны, имеют единую единицу длины для всех трех осей и имеют ориентацию для каждой оси.
На рисунке ниже показано, как оси ориентированы в веб-браузере.

Ось x горизонтальна, ось y вертикальна, а ось z, кажется, выходит из экрана к вам. Нулевое значение оси Z — это плоскость экрана. Помните об этом.
Прояснение перспективы
Для создания 3D-объекта мне понадобился элемент (назовем его «сцена») с перспективой. Перспектива — это глубина сцены, и она зависит от размеров содержащихся в ней объектов.
.scene { perspective: 800px; }
Если перспектива слишком мала, объекты могут искажаться. Если он слишком большой, 3D-эффект будет сведен на нет.
См. Pen jqgMvL Анны Селезнёвой (@askd) на CodePen.
Кроме того, существует только один угол обзора для всех объектов в сцене. И эффект 3D зависит от положения точки обзора.
См. Pen oxKzKv Анны Селезнёвой (@askd) на CodePen.
Итак, как мы рассчитываем перспективу? Я обнаружил, что это зависит от оси вращения. Для оси X подойдет значение высоты, умноженное на 4. Для оси Y это будет значение ширины, умноженное на 4. Вот моя волшебная формула:
const perspective = dimension * 4;
Рассмотрены со всех сторон
Определив перспективу, я приступил к созданию 3D-объекта. Я выбрал куб, потому что он прост и предсказуем. Элемент куба создается как обычный div, относительно позиционированный, с заданными шириной и высотой (скажем, 200px
). Он трансформируется в 3D-объект с помощью свойства transform-style
со значением preserve-3d
. Он указывает браузеру отображать все вложенные элементы в соответствии с правилами трехмерного мира.
В моем случае у куба шесть div (или «сторон»), расположенных абсолютно. Имена классов соответствуют начальным положениям сторон ( back
, left
, right
, top
, bottom
, front
). Вот разметка:
<div class="scene"> <div class="cube"> <div class="side back"></div> <div class="side left"></div> <div class="side right"></div> <div class="side top"></div> <div class="side bottom"></div> <div class="side front"></div> </div> </div>
По умолчанию все стороны будут в одной плоскости. Поэтому мне нужно было их переставить. Вот как это выглядит:
См. Pen mPNwPx Анны Селезнёвой (@askd) на CodePen.
И вот полученный CSS:
.cube { position:relative; width: 200px; height: 200px; transform-style: preserve-3d; } .side { position: absolute; width: 200px; height: 200px; } .back { transform: translateZ(-100px); } .left { transform: translateX(-100px) rotateY(90deg); } .right { transform: translateX(100px) rotateY(90deg); } .top { transform: translateY(-100px) rotateX(90deg); } .bottom { transform: translateY(100px) rotateX(90deg); } .front { transform: translateZ(100px); }
Чтобы повернуть куб, я установил свойство transform
элемента куба на произвольный угол поворота по оси X:
.cube { transform: rotateX(42deg); }
Преодоление недостатков
По заданию я должен был вращать куб только по оси абсцисс, поэтому ни левая, ни правая сторона мне не понадобились. Я добавил подписи, чтобы выровнять начальные позиции остальных сторон.
Я начал вращать куб и обнаружил, что надписи на нижней и задней сторонах отображаются вверх ногами:
См. Pen GZVvMR Анны Селезневой (@askd) на CodePen.
Чтобы решить эту проблему, я повернул каждую из этих сторон по оси x на 180 градусов:
.back { transform: translateZ(-100px) rotateX(180deg); } .bottom { transform: translateY(100px) rotateX(270deg); }
Выход за пределы экрана
Начал наполнять бока реальным контентом и тут же столкнулся с другой проблемой. Мне нужно было отобразить 1-пиксельные пунктирные линии, но они были размытыми и выглядели плохо.

См. Pen VjeBPg Анны Селезневой (@askd) на CodePen.
Вскоре я понял, в чем проблема. Вы помните рекламу 3D-телевидения, в которой изображение выходит за пределы экрана? Что-то подобное было и с моим кубиком.
Если бы вы могли посмотреть на куб с левой или правой стороны, вы бы увидели, что его центр находится в плоскости экрана (ноль на оси z), а передняя сторона находится за экраном. Поэтому он визуально увеличился и расплылся.
См. Pen WwVEMR Анны Селезнёвой (@askd) на CodePen.
Чтобы решить эту проблему, я сдвинул куб по оси Z, чтобы выровнять лицевую сторону по плоскости экрана:
.cube { transform:translateZ(-100px); }
Вот куб почти готов:
См. Pen Xdvery Анны Селезневой (@askd) на CodePen.
Использование магических чисел
Думаю, вы заметили, что я использую магическое число 100
для смещения сторон вдоль оси. Значение 100
равно половине высоты моего тестового куба. Почему половина высоты? Потому что это будет радиус круга, вписанного в сторону куба (который, по-видимому, является квадратом).
const offset = dimension / 2;
Если бы мне нужно было повернуть треугольную призму, круг был бы вписан в треугольник. В этом случае формула для смещения будет выглядеть следующим образом:
const offset = dimension / (2 * Math.sqrt(3));
Сдувание куба
Чтобы считать задачу выполненной, пришлось протестировать результат в разных браузерах.
Картинка, которую я увидел в Internet Explorer, повергла меня в депрессию. Чтобы получить представление о том, о чем я говорю, посмотрите на демо ниже в своем любимом браузере. Я изменил одно свойство, из-за чего куб отображался неправильно в Internet Explorer. Однако не заглядывайте в исходный код, пока не прочитаете абзац под демо ниже.
См. Pen XKWMwV Анны Селезневой (@askd) на CodePen.
Дело в том, что Internet Explorer не поддерживает свойство transform-style
со значением preserve-3d
. Я узнал об этом, просмотрев мой надежный ресурс Can I Use (см. примечание 1). В приведенной выше демонстрации я заменил preserve-3d
на flat
. Вы уже знали это? Эй, я же говорил тебе не подглядывать!
Я расстроилась, но сдаваться не собиралась. Проблема — это возможность узнать что-то новое. Кроме того, я принял вызов.
В поисках точки опоры
Я искал способ создать 3D-объект без использования transform-style: preserve-3d
, и в конце концов обнаружил полезное свойство: transform-origin
. Он определяет центральную точку преобразования элемента. Ниже я создал интерактивную демонстрацию, которая поможет вам понять, как это работает:
См. Pen rLNmBp Анны Селезнёвой (@askd) на CodePen.
Трехмерное вращение элемента в демонстрации очень похоже на лицевую сторону куба, не так ли? Это то, что я использовал.
(Кстати, вы пробовали установить флажок backface-visibility: hidden
во время 3D-поворота? Это свойство используется, чтобы скрыть заднюю часть элемента во время 3D-преобразования.)
Начиная все сначала
Я начал переделывать куб. Мне не нужно было взаимодействовать со сценой в целом, поэтому я удалил свойство perspective
элемента scene
и добавил его к каждому 3D-преобразованию, так что теперь каждый элемент трансформируется независимо. Также я установил новые свойства для каждой стороны: transform-origin
со значением, равным положению центра куба, и backface-visibility: hidden
. Вот как изменились стили:
.scene { } .cube { position: relative; width: 200px; height: 200px; transform: perspective(800px) translateZ(-100px); } .side { position: absolute; transform-origin: 50% 50% -100px; backface-visibility: hidden; }
Пришлось поставить борта в нужные места. Из-за свойства transform-origin
мне не нужно было их сдвигать, а только вращать вокруг осей. Это как волшебство! Давайте посмотрим, как это выглядит:
См. Pen zBYwEm Анны Селезнёвой (@askd) на CodePen.
Вот CSS для размещения сторон:
.back { transform: perspective(800px) rotateY(180deg); } .top { transform: perspective(800px) rotateX(90deg); } .bottom { transform: perspective(800px) rotateX(-90deg); } .front { transform: perspective(800px); }
А здесь вы можете увидеть новый куб в действии:
См. Pen wWvdXd Анны Селезневой (@askd) на CodePen.
Отдавая Цезарю то, что принадлежит Цезарю
Второй кубик выглядит и вращается так же, как и первый. Но в этом случае нужно преобразовывать каждую сторону по отдельности. Это может быть не очень просто, особенно если вы хотите контролировать промежуточный угол поворота.
Кроме того, если вы откроете демоверсию в Chrome, то увидите, что стороны мигают при вращении — очень неприятно.
В конце концов, я применил оба подхода, используя простой тест стиля transform-style: preserve-3d
. Первый куб является кубом по умолчанию. Второй куб предназначен для Internet Explorer и браузеров, не поддерживающих preserve-3d
.
Использование силы математики
Наконец, мне пришлось реализовать эффект параллакса. Обычно этот эффект реагирует на действия пользователя, будь то положение курсора мыши или полосы прокрутки. В этом случае эффект зависит от угла поворота.
См. Pen QENyqm Анны Селезневой (@askd) на CodePen.
Итак, какие данные у меня есть? Во-первых, у меня были начальная и конечная точки положения подписи или, проще говоря, ее offset
вверх и вниз от центра стороны. Во-вторых, у меня был angle
поворота куба.
Я часами пытался разработать формулу. Потом меня осенило. Вот что пришло на ум:

С помощью синусов и косинусов я легко вычислил смещение каждой надписи по углу. Вот какие формулы я придумал:
const front_offset = offset * sin(angle) * -1; const bottom_offset = offset * cos(angle); const back_offset = offset * sin(angle); const top_offset = offset * cos(angle) * -1;
Подводя итоги
Теперь задание выполнено, и я могу насладиться результатом и поделиться им с вами. Посмотрите сами, как это работает. Используйте клавиши прокрутки или стрелки, чтобы вращать промоблок. Также попробуйте потянуть вверх-вниз черный треугольник справа, чтобы управлять углом поворота вручную (к сожалению, в Internet Explorer эта функция не работает). Выглядит довольно хорошо, не так ли? Да и производительность достаточно высокая (около 60 кадров в секунду).
Я очень рад принять участие в разработке этого сайта. Я получил полезный опыт работы с CSS 3D и обнаружил много интересных свойств. Что еще более важно, я понял, что никогда не следует сдаваться; скорее всего, вы найдете способ выполнить задачу.
Надеюсь, вам понравился мой рассказ, и теперь вы готовы принять новые вызовы.