Вызов переднего плана принят: CSS 3D Cube

Опубликовано: 2022-03-10
Краткое резюме ↬ Вам нравятся вызовы? Готовы ли вы взяться за задачу, с которой никогда раньше не сталкивались, и сделать ее в установленные сроки? Что, если при выполнении задания вы столкнетесь с проблемой, которая кажется неразрешимой? Я хочу поделиться своим опытом использования 3D-эффектов CSS в первый раз в реальном проекте и вдохновить вас на новые вызовы. Это был обычный день, когда мне написал Евгений, менеджер CreativePeople . Он прислал мне видео и объяснил, что разрабатывает концепцию для нового проекта и интересуется, смогу ли я разработать что-то вроде того, что было в видео.

Вам нравятся вызовы? Готовы ли вы взяться за задачу, с которой никогда раньше не сталкивались, и сделать ее в установленные сроки? Что, если при выполнении задания вы столкнетесь с проблемой, которая кажется неразрешимой? Я хочу поделиться своим опытом использования 3D-эффектов CSS в первый раз в реальном проекте и вдохновить вас на новые вызовы.

Это был обычный день, когда мне написал Евгений, менеджер CreativePeople. Он прислал мне видео и объяснил, что разрабатывает концепцию для нового проекта и интересуется, смогу ли я разработать что-то вроде того, что было в видео.

Дальнейшее чтение на SmashingMag:

  • Beercamp: Эксперимент с CSS 3D
  • Создание адаптивных форм с помощью Clip-Path и выход из коробки
  • Давайте поиграем с аппаратным ускорением CSS

Это был трехмерный объект (прямоугольный, если быть точным), который вращался вокруг одной из осей. У меня уже был некоторый опыт работы с CSS 3D, и в голове начало формироваться решение. Я погуглил такие ключевые слова, как «3D-куб CSS», чтобы подтвердить свои идеи, и ответил Юджину, что это возможно.

Еще после прыжка! Продолжить чтение ниже ↓

Следующий вопрос Жени был, возьмусь ли я за проект? Мне нравятся сложные задачи, поэтому я не мог отказаться. В то время я не осознавал, во что ввязываюсь, но был более чем полон решимости.

Заточите свои топоры

Вспомним про топоры — не боевые топоры, а числовые линии, те самые оси, что и в трехмерной декартовой системе координат, которую мы изучали в школе. Как говорит нам Википедия:

Декартова система координат для трехмерного пространства представляет собой упорядоченную тройку прямых (осей), которые попарно перпендикулярны, имеют единую единицу длины для всех трех осей и имеют ориентацию для каждой оси.

На рисунке ниже показано, как оси ориентированы в веб-браузере.

Правосторонняя трехмерная декартова система координат с осью Z, указывающей на зрителя.
Правосторонняя трехмерная декартова система координат с осью Z, указывающей на зрителя. (Изображение: Wikimedia Commons) (Посмотреть большую версию)

Ось 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 поворота куба.

Я часами пытался разработать формулу. Потом меня осенило. Вот что пришло на ум:

Графики функций синуса и косинуса
Графики функций синуса и косинуса (Изображение: Wikimedia Commons) (Просмотреть большую версию)

С помощью синусов и косинусов я легко вычислил смещение каждой надписи по углу. Вот какие формулы я придумал:

 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 и обнаружил много интересных свойств. Что еще более важно, я понял, что никогда не следует сдаваться; скорее всего, вы найдете способ выполнить задачу.

Надеюсь, вам понравился мой рассказ, и теперь вы готовы принять новые вызовы.