Выполняю, подождите...
 
 
Библиотека

3D на HTML и CSS: вращающийся куб

Создать 3D-объекты с помощью HTML и CSS совершенно не сложно. Другое дело, что возможности разметки не позволяют делать простым подходом сложные геометрические фигуры без использования дополнительных средств, вроде Canvas и SVG. Тем не менее, даже ограниченные возможности HTML-разметки позволяют делать достаточно красивые вещи. И в этой статье, на примере маленькой посадочной страницы, мы рассмотрим, как сделать 3D объект — куб, грани которого будут представлять собой блоки информации.

Пример посадочной страницы c 3D-элементами

Взгляните на страницу во фрейме, которую мы будем пытаться сделать. Обозначим ее, как аналогичную посадочной, потому что она имеет основные элементы таковой: навигацию и блоки информации. Сверху находятся ссылки, по клику на которые будет осуществляться переход на соответствующие грани куба посредством его вращения. Ниже, в качестве дополнения, добавлены еще пару ссылок, чтобы продемонстрировать приемы 3D в HTML и CSS. Поиграйтесь с HTML-кубом, а затем детально разберем, как это все работает.

Страница c вращающимся кубом, демонстрирующая возможности 3D в HTML и CSS.

И так, вот для начала весь код тела нашей посадочной страницы. Я исключил некоторые CSS-эффекты вроде теней, чтобы не осложнять разбор кода, но вы можете их рассмотреть самостоятельно, открыв исходный код данной посадочной страницы.

<nav>
    <a href="#back">Задняя грань</a>
    <a href="#top">Верхняя грань</a>
    <a href="#front">Фронтальная грань</a>
    <a href="#bottom">Нижняя грань</a>
    <a href="#left">Левая грань</a>
    <a href="#right">Правая грань</a>
</nav>

<section class="cube">
    <article class="back">
        <img src="town.png" alt="">
    </article>
    <article class="top">
        <img src="car.png" alt="">
    </article>
    <article class="front">
        <h2>Фронтальная грань</h2>
        <p>Представьте, что грань — это единица материала и здесь находится какой-то анонс или картинка из галереи.</p>
    </article>
    <article class="bottom">
        <img src="soyuz.png" alt="">
    </article>
    <article class="left">
        <img src="ship.png" alt="">
    </article>
    <article class="right">
        <img src="plane.png" alt="">
    </article>
</section>
nav {
    display: flex;
    justify-content: space-around;
    padding: 10px 0 10px;
    border-bottom: solid 1px #90f9;
}

nav a {
    color: #fff;
    border-radius: 10px;
    padding: 0 10px;
    margin: 10px 10px;
}

.cube {
    width: 300px;
    height: 300px;
    position: absolute;
    top:  calc(50% - 300px / 2);
    left: calc(50% - 300px / 2);
    transform-style: preserve-3d;
    transform: perspective(700px)  rotateX(-36deg) rotateY(-27deg);
    transition: all 1s ease;
}

.cube > article {
    height: 100%;
    width: 100%;
    padding: 20px;
    box-sizing: border-box;
    position: absolute;
    transform-style: preserve-3d;
    background: linear-gradient(0deg, #070b0f, #111b29);
    color: #fff;
    line-height: 150%;
    letter-spacing: 0.05em;
    box-shadow:
        0 0 1px #f00,
        0 0 1px #f00,
        0 0 1px #f00,
        0 0 1px #f00,
        0 0 10px #d0e9 inset,
        0 0 35px #53d5;
}

.back   {transform: rotateX(-180deg) translateZ(150px);}
.top    {transform: rotateX( 90deg)  translateZ(150px);}
.front  {transform:                  translateZ(150px);}
.bottom {transform: rotateX(-90deg)  translateZ(150px);}
.left   {transform: rotateY(-90deg)  translateZ(150px);}
.right  {transform: rotateY( 90deg)  translateZ(150px);}

.cube > article h2 {
    text-transform: uppercase;
    color: #900;
    font-size: 20px;
    font-weight: 600;

}

.cube > article p {
    font-weight: 200;
    font-size: 15px;
}

.cube > article img {
    width: 100%;
}

Навигация по такой посадочной странице была бы затруднительна без JavaScript, поэтому дополним ее несколькими строками простенького кода, который мы также подробно разберем ниже.

function selectEdge(){
    const cube = document.querySelector('.cube');
    const degs = { 
        back:   {X: -180, Y:   0},
        top:    {X:   90, Y:   0},
        front:  {X:    0, Y:   0},
        bottom: {X:  -90, Y:   0},
        left:   {X:    0, Y: -90},
        right:  {X:    0, Y:  90},
    }

    document.querySelectorAll('nav a').forEach(elem => {
        elem.addEventListener('click', (e) => {
            e.preventDefault();
            let edgeName = e.target.hash.replace(/#/, '');
            cube.style.transform = 'perspective(700px) rotateX('+degs[edgeName].X+'deg) rotateY('+degs[edgeName].Y+'deg)';
        })
    });
}

document.addEventListener('DOMContentLoaded', () => {
    selectEdge();
});

Что ж, теперь давайте шаг за шагом разберем, как устроена данная 3D-страница.

Как делается куб на HTML и CSS

Сделать куб на HTML и CSS благодаря возможностям CSS-функциям 3D трансформации очень просто. Всё, что представляется из себя данная геометрическая фигура — это развернутые на требуемые углы и смещенные в определенные стороны полигоны, то есть блочные элементы (div, article или подобное).

Изначально у нас есть набор из шести блоков <article> обернутые в основной блок <section> с классом .cube. Зададим основному блоку размер 300px, указав свойства left и height в его классе. Позиционируем блок абсолютно position: absolute;, чтобы можно было разместить его в любой точке экрана. Эту точку мы обозначим свойствами top и left, рассчитав с помощью CSS-функции calc() центр видимой области страницы. Затем, нам нужно указать основному блоку будущего куба с помощью свойства transform-style: preserve-3d;, что он должен отображать дочерние элементы (будущие грани) в трехмерном представлении. Чтобы увидеть куб во всей красе, к нему нужно сразу применить некоторую подобную трансформацию  transform: perspective(700px) rotateX(-36deg) rotateY(-27deg);.
Если этого не сделать, то мы просто увидим одну сторону куба, в то время как другие просто спрячутся за ней. С помощью функции perspective() указывается что-то вроде фокусного расстояния в 3D пространстве, без которого куб будет выглядеть неестественным. А функциями rotateX() и rotateY() мы просто повернем наш HTML куб на презентабельные углы.

Теперь, займемся сторонами куба. Назначим каждому элементу <article> класс соответственно тому, какую сторону он займет. Укажем элементам размеры равные 100%, то есть идентичные родительскому блоку. Позиционируем блоки абсолютно, чтобы они расположились относительно одной и той же точки внутри родительского блока <section>. Зададим фон блокам. Я указал градиент background: linear-gradient(0deg, #070b0f, #111b29); и добавил границ с помощью теней box-shadow, чтобы выделить ребра куба.

И вот мы добрались до самого интересного. Если мы одному дочернему блоку с помощью CSS-трансформации зададим поворот по оси X равным 90° и сместим этот блок на половину его размера по оси оказавшейся перпендикулярной этому блоку, то таким образом мы получим верхнюю грань куба. Аналогично этому, если мы уже развернем другой блок на -90° и так же сместим, то получим нижнюю грань. Фронтальная грань и так развернута как нужно, поэтому для нее потребуется только смещение. А вот для боковых сторон куба поворот нужно осуществлять уже по оси Y.

Таким образом должны получится подобные свойства для граней HTML куба:

.back   {transform: rotateX(-180deg) translateZ(150px);}
.top    {transform: rotateX( 90deg)  translateZ(150px);}
.front  {transform:                  translateZ(150px);}
.bottom {transform: rotateX(-90deg)  translateZ(150px);}
.left   {transform: rotateY(-90deg)  translateZ(150px);}
.right  {transform: rotateY( 90deg)  translateZ(150px);}

Обратите внимание, что порядок перечисления CSS-функций трансформации важен. Каждая следующая трансформация выполняется относительно результата предыдущей функции. Поэтому не стоит удивляться, что смещение для всех граней куба в 3D-пространстве выполнено только по оси Z.

И вот, куб готов! И теперь таких можно сделать сколько угодно много на странице, просто копируя HTML и меняя лишь значения позиционирования копиям. И тут всё слишком просто, чтобы остановиться на этом. Поэтому пойдем дальше и займемся CSS-анимацией получившегося 3D HTML-объекта.

Вращающийся куб HTML / CSS

Анимировать куб еще проще. Для этого потребуется создать один ключевой кадр с промежуточным значением. Проще говоря, наш 3D-объект из своего исходного значения будет плавно переходить к промежуточному кадру и вновь возвращаться в свое исходное положение:

@keyframes rotation {
    50% {transform:perspective(700px) rotateX(360deg) rotateY(720deg);}
}

Затем, в класс .cube нужно добавить свойство анимации с указанием объявленных ключевых кадров:

animation: rotation 30s ease-in-out infinite;

Здесь длительность анимации 30 секунд, а функция времени установлена соответственно плавному началу и концу анимации. Используется бесконечное повторение.

Если вы хотите, чтобы анимация куба длилась без замедления и непрерывно, то нужно вместо промежуточного установить конечный 100% {...} ключевой кадр отличный от начального на n * угол полного оборота. То есть, например, если начальный угол по оси Y установлен равным -27° , то конечный ключевой кадр должен содержать угол равный -27° + 360° * n, где n, как вы уже догадались — количество оборотов. Кроме этого, понадобиться сменить временную функцию с ease-in-out на linear.

Навигация по посадочной 3D-странице

Веб-страница должна быть интерактивной, а точнее откликаться на действия пользователя. Но в нашем случае нет стандартных событий браузера, чтобы мы могли как-то перемещаться между блоками, то есть гранями куба. Поэтому придется добавить такой интерактивности самостоятельно, прибегнув к помощи JavaScript.

Создадим функцию selectEdge(), которая будет вращать куб на выбранные по ссылкам грани.
Внутри этой функции добавим две константы: элемент нашего 3D HTML-объекта и углы поворота куба соответствующие названиям якорей в ссылках и названиям сторон. Эти данные, для удобства дальнейшего использования, заключены в объект.

const cube = document.querySelector('.cube');
const degs = { 
    back:   {X: -180, Y:   0},
    top:    {X:   90, Y:   0},
    front:  {X:    0, Y:   0},
    bottom: {X:  -90, Y:   0},
    left:   {X:    0, Y: -90},
    right:  {X:    0, Y:  90},
}

Следующим шагом требуется описать действия ко клику на каждую из ссылок в меню. Для этого воспользуемся проходом по элементам ссылок меню с помощью метода forEach():

document.querySelectorAll('nav a').forEach(elem => {
    elem.addEventListener('click', (e) => {
        e.preventDefault();
        let edgeName = e.target.hash.replace(/#/, '');
        cube.style.transform = 'perspective(700px) rotateX('+degs[edgeName].X+'deg) rotateY('+degs[edgeName].Y+'deg)';
    })
});

Поочередно в каждой итерации к элементу ссылки (elem) добавляется событие click, внутри функции обратного вызова описывается следующий сценарий:

Отменяется стандартное событие браузера по клику на ссылку (e) — e.preventDefault();

Получаем название грани куба из якоря ссылки let edgeName = e.target.hash.replace(/#/, '');

Назначаем новые свойства трансформации нашему кубу cube.style.transform ='perspective(…', где в свойства подставляются значения из объекта константы degs.

Завершив написание функции добавим ее вызов, который выполниться после загрузки страницы:

document.addEventListener('DOMContentLoaded', () => {
    selectEdge();
}

С JavaScript мы закончили, но остался еще один нюанс. Нам нужно добавить в класс .cube CSS-свойство перехода transition: all 1s ease;. Без него куб будет мгновенно перепрыгивать на новые свойства. Но не забудьте также убрать анимацию из этого класса, которую мы добавляли в предыдущей главе, иначе она будет мешать переходам по ссылкам.

Теперь вы можете совершенствовать и дополнять получившийся результат, чтобы получить действительно объемные и красивые веб-страницы.

В заключении хочу отметить, что разобранный код немного упрощен и отличается от кода посадочной страницы, которая представлена в начале статьи. Оттуда вырезаны пара функций JavaScript для демонстрации и некоторые стилистические эффекты CSS. Это сделано для того, чтобы не усложнять разбор кода для новичков излишними дополнениями. Но вы всегда можете открыть код в браузере и посмотреть как это устроено. А на этом всё. Желаю всем успехов!