Каждая демонстрация — это возможность попробовать
Создание вещей в 3D с помощью CSS
Я уже писал о создании 3D с помощью CSS. Общий смысл в том, что большинство сцен представляют собой композицию кубоидов.
Чтобы создать прямоугольный параллелепипед, мы можем использовать
* {
}
Как только вы создадите несколько таких сцен, вы начнете искать способы ускорить процесс. Мне нравится использовать Pug в качестве препроцессора HTML. Возможность миксина дает мне возможность быстрее создавать кубоиды. В примерах разметки в этой статье используется Pug. Но для каждой демонстрации CodePen вы можете использовать опцию «Просмотр скомпилированного HTML», чтобы увидеть вывод HTML:
mixin cuboid ()
.cuboid (class≠attributes.class)
— let s = 0
while s < 6
.cuboid__side
— s++
Использование +cuboid () (class="printer__top") приведет к этому:
Затем у меня есть набор блоков CSS, которые я использую для размещения кубоидов. Радость здесь в том, что мы можем использовать пользовательские свойства CSS для определения свойств прямоугольного параллелепипеда (как показано в видео выше):
.cuboid {
// Defaults
—width: 15;
—height: 10;
—depth: 4;
height: calc (var (—depth) * 1vmin) ;
width: calc (var (—width) * 1vmin) ;
position: absolute;
transform: translate3d (0, 0, 5vmin) ;
}
.cuboid > div:
height: calc (var (—height) * 1vmin) ;
width: 100%;
position: absolute;
top: 50%;
left: 50%;
transform: translate (-50%, -50%) rotateX (-90deg) translate3d (0, 0, calc ((var (—depth) / 2) * 1vmin));
}
.cuboid > div:
height: calc (var (—height) * 1vmin) ;
width: 100%;
transform: translate (-50%, -50%) rotateX (-90deg) rotateY (180deg) translate3d (0, 0, calc ((var (—depth) / 2) * 1vmin));
position: absolute;
top: 50%;
left: 50%;
}
.cuboid > div:
height: calc (var (—height) * 1vmin) ;
width: calc (var (—depth) * 1vmin) ;
transform: translate (-50%, -50%) rotateX (-90deg) rotateY (90deg) translate3d (0, 0, calc ((var (—width) / 2) * 1vmin));
position: absolute;
top: 50%;
left: 50%;
}
.cuboid > div:
height: calc (var (—height) * 1vmin) ;
width: calc (var (—depth) * 1vmin) ;
transform: translate (-50%, -50%) rotateX (-90deg) rotateY (-90deg) translate3d (0, 0, calc ((var (—width) / 2) * 1vmin));
position: absolute;
top: 50%;
left: 50%;
}
.cuboid > div:
height: calc (var (—depth) * 1vmin) ;
width: calc (var (—width) * 1vmin) ;
transform: translate (-50%, -50%) translate3d (0, 0, calc ((var (—height) / 2) * 1vmin));
position: absolute;
top: 50%;
left: 50%;
}
.cuboid > div:
height: calc (var (—depth) * 1vmin) ;
width: calc (var (—width) * 1vmin) ;
transform: translate (-50%, -50%) translate3d (0, 0, calc ((var (—height) / 2) * -1vmin)) rotateX (180deg) ;
position: absolute;
top: 50%;
left: 50%;
}
Используя пользовательские свойства, мы можем управлять различными характеристиками кубоидов и так далее:
—width: ширина прямоугольного параллелепипеда на плоскости
—height: высота прямоугольного параллелепипеда на плоскости
—depth: глубина прямоугольного параллелепипеда на плоскости
—x: положение X на плоскости
—y: положение Y на плоскости
Это не очень впечатляет, пока мы не поместим прямоугольный параллелепипед в сцену и не повернем его. Опять же, я использую пользовательские свойства для управления сценой, пока я работаю над
Если вы просматриваете демонстрацию, с помощью панели управления обновляются пользовательские свойства CSS на сцене. Такая область видимости пользовательских свойств CSS позволяет сэкономить много повторяющегося кода и делает вещи СУХИМИ.
Больше, чем один способ
Как и многие вещи в CSS, это можно сделать несколькими способами. Часто вы можете составить сцену из прямоугольных параллелепипедов и расположить их так, как вам нужно. Хотя управлять им может быть сложно. Часто возникает необходимость сгруппировать вещи или добавить
Рассмотрим этот пример, где стул является собственной подсценой, которую можно перемещать.
Многие недавние примеры не такие сложные. Я тянулся к экструзии. Это означает, что я могу отобразить все, что я делаю, в
.helicopter
.helicopter__rotor
.helicopter__cockpit
.helicopter__
.helicopter__chair
.helicopter__
.helicopter__
.helicopter__dashboard
.helicopter__tail
.helicopter__fin
.helicopter__triblade
.helicopter__
.helicopter__stabilizer
.helicopter__skids
.helicopter__skid—left.helicopter__skid
.helicopter__skid—right.helicopter__skid
.helicopter__wing
.helicopter__
.helicopter__
.helicopter__launchers
.helicopter__launcher.helicopter__launcher—left
.helicopter__launcher.helicopter__launcher—right
.helicopter__blades
Затем мы можем поместить кубоиды во все контейнеры с помощью миксина. Затем примените необходимую «толщину» к каждому параллелепипеду. Толщина определяется пользовательскими свойствами области действия. Эта демонстрация переключает —thicknessсвойства кубоидов, из которых состоит вертолет. Это дает представление о том, как изначально выглядело
Это суть того, как создавать
Рассмотрим урезанный пример:
.scene
.extrusion
+cuboid () (class="extrusion__cuboid")
Новый CSS для создания кубоида с экструзией может выглядеть так. Обратите внимание, что мы также включаем настраиваемые свойства с ограниченной областью действия для цвета каждой стороны. Было бы разумно отказаться от некоторых значений по умолчанию под: rootздесь или резервными значениями:
.cuboid {
width: 100%;
height: 100%;
position: relative;
}
.cuboid__side:
background: var (—
height: calc (var (—thickness) * 1vmin) ;
width: 100%;
position: absolute;
top: 0;
transform: translate (0, -50%) rotateX (90deg) ;
}
.cuboid__side:
background: var (—
height: 100%;
width: calc (var (—thickness) * 1vmin) ;
position: absolute;
top: 50%;
right: 0;
transform: translate (50%, -50%) rotateY (90deg) ;
}
.cuboid__side:
background: var (—
width: 100%;
height: calc (var (—thickness) * 1vmin) ;
position: absolute;
bottom: 0;
transform: translate (0%, 50%) rotateX (90deg) ;
}
.cuboid__side:
background: var (—
height: 100%;
width: calc (var (—thickness) * 1vmin) ;
position: absolute;
left: 0;
top: 50%;
transform: translate (-50%, -50%) rotateY (90deg) ;
}
.cuboid__side:
background: var (—
height: 100%;
width: 100%;
transform: translate3d (0, 0, calc (var (—thickness) * 0.5vmin));
position: absolute;
top: 0;
left: 0;
}
.cuboid__side:
background: var (—
height: 100%;
width: 100%;
transform: translate3d (0, 0, calc (var (—thickness) * -0.5vmin)) rotateY (180deg) ;
position: absolute;
top: 0;
left: 0;
}
В этом примере мы использовали три оттенка. Но иногда вам может понадобиться больше. Эта демонстрация объединяет это, но позволяет вам изменять настраиваемые свойства области действия. Значение «толщины» изменит экструзию прямоугольного параллелепипеда. Преобразования и размеры повлияют на содержащий элемент с классом «экструзия».
Подмости для принтера
Для начала мы можем собрать все необходимые детали. С практикой это становится более очевидным. Но общее правило состоит в том, чтобы попытаться визуализировать все в виде коробок. Это дает вам хорошее представление о том, как
.scene
.printer
.printer__side.printer__side—left
.printer__side.printer__side—right
.printer__tray.printer__tray—bottom
.printer__tray.printer__tray—top
.printer__top
.printer__back
Посмотрите, можете ли вы визуализировать то, что мы собираемся сделать здесь. Две боковые части оставляют зазор посередине. Затем у нас есть прямоугольный параллелепипед, который находится сверху, и тот, который заполняет заднюю часть. Затем два куба, чтобы составить лоток для бумаги.
Как только вы окажетесь на этом этапе, это случай заполнения кубоидов, который выглядит следующим образом:
.scene
.printer
.printer__side.printer__side—left
+cuboid () (class="cuboid--side")
.printer__side.printer__side—right
+cuboid () (class="cuboid--side")
.printer__tray.printer__tray—bottom
+cuboid () (class="cuboid--tray")
.printer__tray.printer__tray—top
+cuboid () (class="cuboid--tray")
.printer__top
+cuboid () (class="cuboid--top")
.printer__back
+cuboid () (class="cuboid--back")
Обратите внимание, как мы можем повторно использовать имена классов, такие как cuboid—side. Эти кубоиды, вероятно, будут иметь одинаковую толщину и использовать одни и те же цвета. Их положение и размер определяются содержащим элементом.
Собрав все вместе, мы можем получить
В
Добавление некоторых деталей
Теперь вы, возможно, заметили, что деталей больше, чем если бы вы просто добавляли цвета с каждой стороны. И это сводится к поиску способов добавить дополнительные детали. У нас есть разные варианты в зависимости от того, что мы хотим добавить.
Если это изображение или некоторые базовые изменения цвета, мы можем использовать
Например, верхняя часть принтера имеет детали, а также отверстие принтера. Этот код относится к верхней стороне верхнего прямоугольного параллелепипеда. Градиент обрабатывает открытие принтера и детали:
.cuboid—top {
—thickness: var (—depth) ;
—
}
Для логотипа медведя мы могли бы использовать псевдоэлемент
.cuboid—top > div:
content: '';
position: absolute;
top: 7%;
left: 10%;
height: calc (var (—depth) * 0.12vmin) ;
width: calc (var (—depth) * 0.12vmin) ;
background: url («https: //assets.codepen.io/605876/avatar.png») ;
transform: rotate (90deg) ;
filter: grayscale (0.5) ;
}
Если нам нужно добавить более подробные детали, нам, вероятно, придется отказаться от использования кубовидного миксина. Например, в верхней части нашего принтера будет экран предварительного просмотра с использованием imgэлемента:
.printer__top
.cuboid.cuboid—top
.cuboid__side
.cuboid__side
.cuboid__side
.cuboid__side
.screen
.screen__preview
img.screen__
.cuboid__side
.cuboid__side
Добавьте еще немного деталей, и мы готовы добавить немного бумаги!
Бумажное путешествие
Какой принтер без бумаги? Мы хотим анимировать, как бумага летит в принтер и вылетает с другого конца.
Мы можем добавить блок бумаги в сцену с прямоугольным параллелепипедом, а затем использовать отдельный элемент, который будет действовать как один лист бумаги:
.
+cuboid () (class="cuboid--paper")
.
.cuboid.cuboid—paper
.cuboid__side
.paper
.paper__flyer
.cuboid__side
.cuboid__side
.cuboid__side
.cuboid__side
.cuboid__side
Но анимация бумаги, летящей в принтер, требует проб и ошибок. Целесообразно поиграть с различными преобразованиями в инспекторе DevTools. Это хороший способ увидеть, как все будет выглядеть. Часто также проще использовать
: root {
—
}
.
animation: transfer calc (var (—
}
.
animation: fly calc (var (—
}
.
animation: feed calc (var (—
}
@keyframes transfer {
to {
transform: translate (0, -270%) rotate (22deg) ;
}
}
@keyframes feed {
to {
transform: translate (100%, 0) ;
}
}
@keyframes fly {
0% {
transform: translate3d (0, 0, 0) rotateY (0deg) translate (0, 0) ;
}
50% {
transform: translate3d (140%, 0, calc (var (—height) * 1.2)) rotateY (-75deg) translate (180%, 0) ;
}
100% {
transform: translate3d (140%, 0, var (—height)) rotateY (-75deg) translate (0%, 0) rotate (-180deg) ;
}
}
Вы заметите, что там довольно много calcиспользования. Чтобы составить временную шкалу анимации, мы можем использовать пользовательские свойства CSS. Ссылаясь на свойство, мы можем рассчитать правильные задержки для каждой анимации в цепочке. Бумага переносится и летит одновременно. Одна анимация обрабатывает перемещение контейнера, другая — вращение бумаги. Как только эти анимации заканчиваются, бумага подается в принтер вместе с feedанимацией. Задержка анимации равна продолжительности первых двух анимаций, которые запускаются одновременно.
Запустите эту демонстрацию, где я раскрасил элементы контейнера в красный и зеленый цвета. Мы используем.paper__flyerпсевдоэлемент, чтобы представить лист бумаги. Но
Вам может быть интересно, когда бумага выходит на другом конце. Но на самом деле бумага не является одним и тем же элементом повсюду. Мы используем один элемент для входа в принтер. И еще элемент для бумаги, когда она вылетает из принтера. Это еще один случай, когда дополнительные элементы облегчают нашу жизнь.
Бумага использует более одного элемента для создания петли, а затем бумага позиционируется на краю этого элемента. Запуск этой демонстрации с большим количеством цветных элементов контейнера показывает, как это работает.
Опять же, это требует проб и ошибок, а также размышлений о том, как мы можем использовать элементы контейнера. Наличие контейнера со смещением
Печать
У нас все на месте. Теперь дело за тем, чтобы
form.
label (for="print") Print URL
input#print (type='url’ required placeholder="URL for Printing")
input (type="submit" value="Print")
С некоторой стилизацией мы получаем
Собственное поведение форм и использование requiredи type="url"означает, что мы принимаем только
Отправка нашей формы не работает так, как мы хотим, а также анимация печати запускается один раз при загрузке. Обойти это можно было бы, запуская анимацию только тогда, когда к принтеру применяется определенный класс.
Когда мы отправляем форму, мы можем запросить
Начнем с обработки отправки формы. Мы собираемся предотвратить событие по умолчанию и вызвать PROCESSфункцию:
const PRINT = e => {
e.preventDefault ()
PROCESS ()
}
const PRINT_FORM = document.querySelector ('form’)
PRINT_FORM.addEventListener ('submit’, PRINT)
Эта функция будет обрабатывать запрос на наш источник изображения:
let printing = false
const PREVIEW = document.querySelector ('img.screen__
const SUBMIT = document.querySelector ('[type="submit"]')
const URL_INPUT = document.querySelector ('[type="url"]')
const PROCESS = async () => {
if (printing) return
printing = true
SUBMIT.disabled = true
const res = await fetch (URL_INPUT.value)
PREVIEW.src = res.url
URL_INPUT.value = ''
}
Мы также устанавливаем printingпеременную в true, которую мы будем использовать для отслеживания текущего состояния, и отключаем кнопку формы.
Почему мы делаем запрос на изображение вместо того, чтобы установить его на изображение? Нам нужен абсолютный
Как только у нас есть источник изображения, мы устанавливаем источник изображения предварительного просмотра на этот
Чтобы запустить анимацию, мы можем подключиться к событию загрузки нашего изображения предварительного просмотра. Когда событие срабатывает, мы создаем новый элемент для листа бумаги для печати и добавляем его к printerэлементу. В то же время мы добавляем printingкласс к нашему принтеру. Мы можем использовать это для запуска первой части нашей бумажной анимации:
PREVIEW.addEventListener ('load’, () => {
PRINTER.classList.add ('printing’)
const PRINT = document.createElement ('div’)
PRINT.className = 'printed’
PRINT.innerHTML = `
<img class="printed__image" src=${PREVIEW.src}/>
PRINTER.appendChild (PRINT)
// After a set amount of time reset the state
setTimeout (() => {
printing = false
SUBMIT.removeAttribute ('disabled’)
PRINTER.classList.remove ('printing’)
}, 4500)
})
Через определенное время мы можем сбросить состояние. Альтернативным подходом может быть подавление всплывающего animationendсобытия. Но мы можем использовать setTimeout, так как знаем, сколько времени займет анимация.
Однако наша печать не в правильном масштабе. И это потому, что нам нужно масштабировать изображение до листа бумаги. Для этого нам понадобится небольшой кусочек CSS:
.printed__image {
height: 100%;
width: 100%;
}
Также было бы неплохо, если бы индикаторы на передней панели принтера сообщали о том, что принтер занят. Мы могли бы настроить оттенок одного из индикаторов, когда принтер печатает:
.
background: hsla (var (—
}
.printing {
—
}
Соедините это вместе, и мы получим «рабочий» принтер, созданный с помощью CSS и небольшого количества JavaScript.
Это оно!
Мы рассмотрели, как можно создать функциональный
Для этого мы рассмотрели множество различных вещей, в том числе следующие:
как сделать 3D вещи с помощью CSS
использование миксинов Pug
использование настраиваемых свойств CSS с ограниченной областью действия, чтобы все было СУХИМ
использование экструзии для создания
обработка форм с помощью JavaScript
составление временных шкал анимации с настраиваемыми свойствами
Удовольствие от создания этих демонстраций заключается в том, что многие из них ставят различные проблемы, которые необходимо решить, например, как создавать определенные формы или создавать определенные анимации. Часто есть более одного способа сделать
Какие классные вещи вы могли бы сделать с помощью 3D CSS? Я бы хотел увидеть!