Создайте 3D-принтер CSS, который действительно печатает

Создайте 3D-принтер CSS, который действительно печатает Изучение

Каждая демонстрация — это возможность попробовать что-то новое или разработать способы работы с CSS. Одна вещь, которую я часто делаю, — это прислушиваюсь к предложениям о том, что мы должны попробовать и внести в стрим. Недавнее предложение было о принтере, который печатает в «3D». И вот что я собрал!

Создание вещей в 3D с помощью CSS

Я уже писал о создании трехмерных объектов с помощью CSS. Общая суть в том, что большинство сцен представляют собой композицию кубоидов.

Чтобы создать кубоид, мы можем использовать преобразования CSS для позиционирования сторон кубоида — магическое свойство transform-style. Установка этого значения preserve-3dпозволяет нам преобразовывать элементы в третьем измерении:

* {
  transform-style: preserve-3d;
}

Как только вы создадите несколько таких сцен, вы начнете находить способы ускорить процесс. Мне нравится использовать 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«)приведет к следующему:

<div class="cuboid printer__top">
  <div class="cuboid__side"></div>
  <div class="cuboid__side"></div>
  <div class="cuboid__side"></div>
  <div class="cuboid__side"></div>
  <div class="cuboid__side"></div>
  <div class="cuboid__side"></div>
</div>
Затем у меня есть набор CSS, который я использую для размещения кубоидов. Радость здесь в том, что мы можем использовать настраиваемые свойства CSS для определения свойств кубоида (как показано на видео выше):
.cuboid {
  // Defaults
  --width: 15;
  --height: 10;
  --depth: 4;
  height: calc(var(--depth) * 1vmin);
  width: calc(var(--width) * 1vmin);
  transform-style: preserve-3d;
  position: absolute;
  font-size: 1rem;
  transform: translate3d(0, 0, 5vmin);
}
.cuboid > div:nth-of-type(1) {
  height: calc(var(--height) * 1vmin);
  width: 100%;
  transform-origin: 50% 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotateX(-90deg) translate3d(0, 0, calc((var(--depth) / 2) * 1vmin));
}
.cuboid > div:nth-of-type(2) {
  height: calc(var(--height) * 1vmin);
  width: 100%;
  transform-origin: 50% 50%;
  transform: translate(-50%, -50%) rotateX(-90deg) rotateY(180deg) translate3d(0, 0, calc((var(--depth) / 2) * 1vmin));
  position: absolute;
  top: 50%;
  left: 50%;
}
.cuboid > div:nth-of-type(3) {
  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:nth-of-type(4) {
  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:nth-of-type(5) {
  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:nth-of-type(6) {
  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, есть несколько способов сделать это. Часто вы можете составить сцену из кубоидов и расположить объекты так, как вам нужно. Однако это может оказаться непросто. Часто возникает необходимость сгруппировать вещи или добавить какой-нибудь контейнер.

Читайте также:  6 характеристик, которые сделают вас выдающимся разработчиком

Рассмотрим этот пример, где стул — это отдельная подсцена, которую можно перемещать.

Многие недавние примеры не такие сложные. Я добирался до экструзии. Это означает, что я могу отобразить все, что я делаю, в 2D-элементах. Например, вот вертолет, который я недавно создал:

.helicopter
  .helicopter__rotor
  .helicopter__cockpit
    .helicopter__base-light
    .helicopter__chair
      .helicopter__chair-back
      .helicopter__chair-bottom
    .helicopter__dashboard
  .helicopter__tail
  .helicopter__fin
    .helicopter__triblade
    .helicopter__tail-light
  .helicopter__stabilizer
  .helicopter__skids
    .helicopter__skid--left.helicopter__skid
    .helicopter__skid--right.helicopter__skid
  .helicopter__wing
    .helicopter__wing-light.helicopter__wing-light--left
    .helicopter__wing-light.helicopter__wing-light--right
  .helicopter__launchers
    .helicopter__launcher.helicopter__launcher--left
    .helicopter__launcher.helicopter__launcher--right
  .helicopter__blades

Затем мы можем поместить кубоиды во все контейнеры с помощью миксина. Затем нанесите необходимую «толщину» на каждый кубоид. Толщина определяется настраиваемыми свойствами с заданной областью действия. Эта демонстрация переключает —thicknessсвойство для кубоидов, из которых состоит вертолет. Это дает представление о том, как выглядело двухмерное отображение в начале.

В этом суть того, как создавать трехмерные объекты с помощью CSS. Углубление в код наверняка откроет некоторые уловки. Но, как правило, создайте каркас сцены, заполните кубоиды и раскрасьте кубоиды. Вам часто нужны разные оттенки цвета, чтобы мы могли различать стороны кубоида. Любые дополнительные детали — это либо вещи, которые мы можем добавить к стороне кубоида, либо преобразования, которые мы можем применить к кубоиду. Например, вращение и перемещение по оси Z.

Давайте рассмотрим урезанный пример:

.scene
  .extrusion
    +cuboid()(class="extrusion__cuboid")

Новый CSS для создания кубоида с экструзией может выглядеть так. Обратите внимание, как мы включаем настраиваемые свойства с заданной областью для цвета каждой стороны. Было бы разумно отказаться от некоторых значений по умолчанию для :rootзначений здесь или для резервных значений:

.cuboid {
  width: 100%;
  height: 100%;
  position: relative;
}
.cuboid__side:nth-of-type(1) {
  background: var(--shade-one);
  height: calc(var(--thickness) * 1vmin);
  width: 100%;
  position: absolute;
  top: 0;
  transform: translate(0, -50%) rotateX(90deg);
}
.cuboid__side:nth-of-type(2) {
  background: var(--shade-two);
  height: 100%;
  width: calc(var(--thickness) * 1vmin);
  position: absolute;
  top: 50%;
  right: 0;
  transform: translate(50%, -50%) rotateY(90deg);
}
.cuboid__side:nth-of-type(3) {
  background: var(--shade-three);
  width: 100%;
  height: calc(var(--thickness) * 1vmin);
  position: absolute;
  bottom: 0;
  transform: translate(0%, 50%) rotateX(90deg);
}
.cuboid__side:nth-of-type(4) {
  background: var(--shade-two);
  height: 100%;
  width: calc(var(--thickness) * 1vmin);
  position: absolute;
  left: 0;
  top: 50%;
  transform: translate(-50%, -50%) rotateY(90deg);
}
.cuboid__side:nth-of-type(5) {
  background: var(--shade-three);
  height: 100%;
  width: 100%;
  transform: translate3d(0, 0, calc(var(--thickness) * 0.5vmin));
  position: absolute;
  top: 0;
  left: 0;
}
.cuboid__side:nth-of-type(6) {
  background: var(--shade-one);
  height: 100%;
  width: 100%;
  transform: translate3d(0, 0, calc(var(--thickness) * -0.5vmin)) rotateY(180deg);
  position: absolute;
  top: 0;
  left: 0;
}

В этом примере мы выбрали три оттенка. Но иногда может понадобиться больше. Эта демонстрация объединяет все это вместе, но позволяет вам изменять настраиваемые свойства с заданной областью действия. Значение «толщины» изменит вытяжку кубоида. Преобразования и размеры будут влиять на содержащий элемент с классом «выдавливание».

Scaffolding a Printer

Для начала мы можем собрать все необходимые детали. По мере практики это становится более очевидным. Но общее правило — стараться все визуализировать в виде ящиков. Это дает вам хорошее представление о том, как что-то сломать:

.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. Эти кубоиды могут иметь одинаковую толщину и цвета. Их положение и размер определяются содержащим элементом.

Собрав все вместе, мы можем получить что-то вроде этого.

Разборка демонстрации показывает различные кубоиды, из которых состоит принтер. Если выключить выдавливание, можно увидеть квартиру, содержащую элементы.

Добавление деталей

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

Читайте также:  Расширение спектра прямой последовательностью в беспроводных сетях

Если это изображение или некоторые базовые изменения цвета, мы можем использовать background-imageградиенты для наложения слоев и так далее.

Например, на верхней части принтера есть детали, а также отверстие для принтера. Этот код обращается к верхней стороне верхнего кубоида. Градиент обрабатывает открытие принтера и детали:

.cuboid--top {
  --thickness: var(--depth);
  --shade-one: linear-gradient(#292929, #292929) 100% 50%/14% 54% no-repeat, linear-gradient(var(--p-7), var(--p-7)) 40% 50%/12% 32% no-repeat, linear-gradient(var(--p-7), var(--p-7)) 30% 50%/2% 12% no-repeat, linear-gradient(var(--p-3), var(--p-3)) 0% 50%/66% 50% no-repeat, var(--p-1);
}

Для логотипа медведя мы могли бы использовать псевдоэлемент background-imageили даже дотянуться до него и расположить его:

.cuboid--top > div:nth-of-type(1):after {
  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");
  background-size: cover;
  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__preview-img
    .cuboid__side
    .cuboid__side

Добавьте еще несколько деталей, и мы готовы добавить немного бумаги!

Paper Journey

Что за принтер без бумаги? Мы хотим оживить бумагу, летящую в принтер и вылетающую из другого конца. Что-то вроде этой демонстрации: щелкните в любом месте, чтобы увидеть лист бумаги, поданный в принтер и распечатанный.

Мы можем добавить в сцену блок бумаги с кубоидом, а затем использовать отдельный элемент, который будет действовать как один лист бумаги:

.paper-stack.paper-stack--bottom
  +cuboid()(class="cuboid--paper")
.paper-stack.paper-stack--top
  .cuboid.cuboid--paper
    .cuboid__side
      .paper
        .paper__flyer
    .cuboid__side
    .cuboid__side
    .cuboid__side
    .cuboid__side
    .cuboid__side

Но для анимации влетающей в принтер бумаги требуется метод проб и ошибок. Разумно поиграть с разными преобразованиями в инспекторе DevTools. Это хороший способ увидеть, как все будет выглядеть. Часто также проще использовать элементы оболочки. Мы используем.paperэлемент для переноса, а затем используем.paper__flyerдля анимации подачи бумаги:

:root {
  --load-speed: 2;
}

.paper-stack--top .cuboid--paper .paper {
  animation: transfer calc(var(--load-speed) * 0.5s) ease-in-out forwards;
}
.paper-stack--top .cuboid--paper .paper__flyer {
  animation: fly calc(var(--load-speed) * 0.5s) ease-in-out forwards;
}
.paper-stack--top .cuboid--paper .paper__flyer:after {
  animation: feed calc(var(--load-speed) * 0.5s) calc(var(--load-speed) * 0.5s) forwards;
}

@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псевдоэлемент для представления листа бумаги. Но элементы контейнера делают тяжелую работу:

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

В бумаге для создания цикла используется более одного элемента, а затем лист размещается на краю этого элемента. Запуск этой демонстрации с более цветными элементами контейнера показывает, как она работает.

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

Печать

У нас все на месте. Теперь нужно что-то напечатать. Для этого мы собираемся добавить форму, которая позволяет пользователям передавать URL-адрес изображения:

form.customer-form
  label(for="print") Print URL
  input#print(type='url' required placeholder="URL for Printing")
  input(type="submit" value="Print")

Немного стилизовав, мы получим что-то вроде этого.

Читайте также:  Экспорт фреймов данных в несколько листов Excel в R

Собственное поведение форм и использование requiredи type=»url»означает, что мы принимаем только URL. Мы могли бы пойти дальше с помощью a patternи проверить определенные типы изображений. Но некоторые хорошие URL-адреса для случайных изображений не включают тип изображения, например https://source.unsplash.com/random.

Отправка нашей формы ведет себя не так, как мы хотим, а также анимация печати запускается один раз при загрузке. Способом обойти это было бы запускать анимацию только тогда, когда к принтеру применяется определенный класс.

Когда мы отправляем форму, мы можем сделать запрос URL-адреса, а затем установить srcдля изображений в нашей сцене — одно изображение является предварительным просмотром экрана на принтере, а другое — изображением на одной стороне бумаги. Фактически, когда мы печатаем, мы собираемся добавить новый элемент для каждого напечатанного листа бумаги. Таким образом, каждый отпечаток выглядит так, как будто он складывается в стопку. Мы можем удалить загруженный листок бумаги.

Начнем с обработки отправки формы. Мы собираемся предотвратить событие по умолчанию и вызвать 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__preview-img')
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, которую мы будем использовать для отслеживания текущего состояния, и отключим кнопку формы.

Почему мы делаем запрос на изображение, а не устанавливаем его на изображение? Нам нужен абсолютный URL-адрес изображения. Если мы воспользуемся указанным выше URL-адресом «Unsplash», а затем поделимся им между изображениями, это может не сработать. Это потому, что мы можем столкнуться со сценариями, в которых отображаются разные изображения.

Когда у нас есть источник изображения, мы устанавливаем источник изображения предварительного просмотра на этот URL-адрес и сбрасываем входное значение формы.

Чтобы запустить анимацию, мы можем подключиться к событию «загрузка» нашего изображения предварительного просмотра. Когда событие срабатывает, мы создаем новый элемент для листа бумаги для печати и добавляем его к printerэлементу. В то же время мы добавляем printingкласс к нашему принтеру. Мы можем использовать это для запуска первой части нашей бумажной анимации:

PREVIEW.addEventListener('load', () => {
  PRINTER.classList.add('printing')
  const PRINT = document.createElement('div')
  PRINT.className = 'printed'
  PRINT.innerHTML = `
    <div class="printed__spinner">
      <div class="printed__paper">
        <div class="printed__papiere">
          <img class="printed__image" src=${PREVIEW.src}/>
        </div>
      </div>
      <div class="printed__paper-back"></div>
    </div>
  `
  PRINTER.appendChild(PRINT)
  // After a set amount of time reset the state
  setTimeout(() => {
    printing = false
    SUBMIT.removeAttribute('disabled')
    PRINTER.classList.remove('printing')
  }, 4500)
})

Через установленное время мы можем сбросить состояние. Альтернативным подходом было бы устранение всплывающего animationendсобытия. Но мы можем использовать a setTimeout, так как знаем, сколько времени займет анимация.

Однако наша печать не в правильном масштабе. И это потому, что нам нужно масштабировать изображение до листа бумаги. Для этого нам понадобится небольшой кусочек CSS:

.printed__image {
  height: 100%;
  width: 100%;
  object-fit: cover;
}

Также было бы неплохо, если бы индикаторы на передней панели принтера сообщали, что принтер занят. Мы можем настроить оттенок одного из индикаторов, когда принтер печатает:

.progress-light {
  background: hsla(var(--progress-hue, 104), 80%, 50%);
}
.printing {
  --progress-hue: 10; /* Equates to red */
}

Сложите это вместе, и у нас есть «рабочий» принтер, сделанный с помощью CSS и немного JavaScript.

Заключение

Мы рассмотрели, как создать функциональный 3D-принтер с CSS, небольшим количеством JavaScript и использованием Pug. Попробуйте добавить следующую ссылку на изображение в поле URL-адреса или другое на ваш выбор и попробуйте!

https://source.unsplash.com/random

Для этого мы рассмотрели множество разных вещей, в том числе следующие:

  • как создавать трехмерные объекты с помощью CSS
  • использование миксинов Pug
  • использование настраиваемых свойств CSS с заданной областью действия, чтобы сохранить СУХИЕ вещи
  • а также использование экструзии для создания 3D-сцен
  • обработка форм с помощью JavaScript
  • составление временной шкалы анимации с настраиваемыми свойствами

Удовольствие от создания этих демонстраций заключается в том, что многие из них создают различные проблемы, которые необходимо преодолеть, например, как создавать определенные формы или создавать определенные анимации. Часто есть несколько способов сделать что-то.

Оцените статью
bestprogrammer.ru
Добавить комментарий