3D CSSプリンターを作成し、実際に印刷する機能を実装しましょう!

この間から、CSSでこれらの3Dシーンを楽しみながら作成しています—通常はライブストリームで。

各デモは、何か違うことを試すか、CSSで物事を行う方法を考え出す機会です。私がよくやることの一つは、ストリームで何を試して作るかの提案を受けることです。最近の提案は「3D」で印刷するプリンターでした。そして、これが私がまとめたものです!

CSSで3Dのものを作る

I’ve written about making things 3D with CSS before. The general gist is that most scenes are a composition of cuboids.

直方体を作るためには、CSS変形を使用して直方体の側面を配置できます—魔法のプロパティはtransform-styleです。これをpreserve-3dに設定することで、要素を第三の次元で変形できます:

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

一度これらのシーンを数個作成すると、作業を高速化する方法を学ぶようになります。私はHTMLのプリプロセッサとしてPugを使用しています。ミックスイン機能により、直方体をより迅速に作成できます。この記事のマークアップ例はPugを使用していますが、各CodePenデモでは「View Compiled 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位置

直方体をシーンに配置して回転させるまではあまり印象的ではありません。再び、何かを作成しながらシーンを操作するためにカスタムプロパティを使用します。Dat.GUIはここで非常に便利です。

デモを検査すると、コントロールパネルを使用してシーンのCSSカスタムプロパティを更新できます。このようなCSSカスタムプロパティのスコープ化により、多くの繰り返しコードを節約し、DRY(Don’t Repeat Yourself)原則を保つことができます。

複数の方法があります

CSSでは多くのことがありますが、それを行う方法は1つだけではありません。しばしば、直方体からシーンを構成し、必要に応じて物事を配置できます。ただし、管理が難しくなることがよくあります。しばしば、グループ化する必要があるか、ある種のコンテナを追加する必要があります。

この例を考えてみましょう。ここでは、椅子が移動できる独自のサブシーンです。

最近の多くの例はそれほど複雑ではありません。私は押し出しに頼っています。これは、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

次に、mixinを使用してすべてのコンテナに直方体を落とすことができます。次に、各直方体に必要な「厚さ」を適用します。厚さはスコープ付きカスタムプロパティによって決定されます。このデモでは、ヘリコプターを構成する直方体の--thicknessプロパティを切り替えます。これは、最初の2Dマッピングがどのように見えたかのアイデアを与えます。

それがCSSで3Dのものを作成する方法の概要です。コードを掘り下げると、確かにいくつかのトリックが明らかになります。しかし、一般的には、シーンをスケルトン化し、直方体で埋め、直方体を着色します。直方体の側面を区別するために、しばしば色の異なる色合いが必要になります。追加の詳細は、直方体の側面に追加できるもの、または直方体に適用できる変換です。たとえば、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;
}

この例では3つの色合いを使用しましたが、時にはもっと必要な場合もあります。このデモはそれをまとめていますが、スコープ付きのカスタムプロパティを変更できます。”thickness”の値は直方体の押し出し量を変えます。変形と寸法は”extrusion”クラスを持つコンテナ要素に影響を与えます。

プリンターのスキャフォールディング

始めるには、必要なすべての部品をスキャフォールディングできます。練習を重ねるとこれがより明らかになりますが、一般的なルールはすべてを箱として視覚化しようとすることです。それは何かを分解する方法について良いアイデアを与えてくれます:

.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

ここで目指しているものを視覚化できるか試してください。2つの側面のピースは中央にギャップを残します。そして、上部に渡す直方体と背面を埋める直方体があります。そして、用紙トレイを構成する2つの直方体です。

その段階に達したら、直方体を配置するだけです。こんな感じです:

.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-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カスタムプロパティを利用できます。プロパティを参照することで、連鎖の各アニメーションに対して正しい遅延を計算できます。紙は同時に転送されて飛びます。1つのアニメーションがコンテナを移動し、もう1つが紙を回転させます。それらのアニメーションが終了すると、紙はfeedアニメーションでプリンターに供給されます。アニメーションの遅延は、同時に実行される最初の2つのアニメーションの期間に等しくなります。

このデモを実行してください。コンテナ要素を赤と緑に着色しました。.paper__flyerの疑似要素を使用して、紙を表現します。しかし、コンテナ要素が実際に大変な作業を行います。

紙がいつ反対側から出てくるのか疑問に思うかもしれませんが、実際には紙は常に同じ要素ではありません。プリンタに入るために1つの要素を使用し、プリンタから飛び出すときの紙には別の要素を使用します。これは、追加の要素が私たちの生活をより楽にする別の例です。

紙はループを行うために複数の要素を使用し、その後、紙はその要素の端に配置されます。このデモをより多くの着色されたコンテナ要素で実行すると、その動作がどのように行われているかがわかります。

再び、試行錯誤が必要であり、コンテナ要素の使用をどのように活用できるかを考える必要があります。オフセット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")

スタイリングを加えることで、このようなものが得られます。

フォームのネイティブな動作とrequiredおよびtype="url"の使用により、URLのみを受け入れます。さらに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の画像が必要です。上記の「Unsplash」URLを使用し、画像間で共有すると、機能しない場合があります。これは、異なる画像が表示されるシナリオに遭遇する可能性があるためです。

画像のソースが取得できたら、プレビュー画像のソースをそのURLに設定し、フォームの入力値をリセットします。

アニメーションをトリガーするには、プレビュー画像の「load」イベントにフックできます。イベントが発生すると、印刷する紙の断片の新しい要素を作成し、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)
  // 一定時間後に状態をリセット
  setTimeout(() => {
    printing = false
    SUBMIT.removeAttribute('disabled')
    PRINTER.classList.remove('printing')
  }, 4500)
})

一定時間後に、状態をリセットできます。代替アプローチは、バブリング animationend イベントをデバウンスすることです。しかし、アニメーションがどれくらいの時間かかるかを知っているので、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; /* 赤に相当 */
}

CSSとほんの少しのJavaScriptで作られた「動作する」プリンターができました。

以上です!

CSS、ほんの少しのJavaScript、そしてPugを活用して機能的な3Dプリンターを作る方法を見てきました。以下の画像リンクをURLフィールドに追加して、試してみてください。または、お好みの画像リンクを選んでみてください!

https://source.unsplash.com/random

このために、いくつかの異なることをカバーしました。

  • CSSで3Dのものを作る方法
  • Pugミックスインの使用
  • DRYを保つためにスコープ付きカスタムCSSプロパティの使用
  • 押し出しを使用して3Dシーンを作成
  • JavaScriptでフォームを処理
  • カスタムプロパティでアニメーションタイムラインを構成

これらのデモを作成する楽しさは、多くの場合、特定の形状をどのように作成したり、特定のアニメーションを構築したりする問題に直面することです。何かを行う方法は一つだけではありません。

3D CSSでどんなクールなものを作れるか?見てみたいですね!

いつも通り、読んでくれてありがとう。もっと見たい?Twitterで私に会いに来て、ライブストリームもチェックしてください!

3D CSSプリンターに関するよくある質問(FAQ)

3D CSSプリンターとは何ですか?

A 3D CSS Printer is a unique concept that uses Cascading Style Sheets (CSS), a style sheet language used for describing the look and formatting of a document written in HTML, to create a 3D representation of a printer. This innovative approach allows developers to create interactive and visually appealing web elements that can enhance user experience.

3D CSSプリンターはどのように動作しますか?

A 3D CSS Printer works by using CSS properties to create a 3D model of a printer. It uses properties such as transform, perspective, and animation to create the 3D effect. The printer is made up of multiple elements, each styled and positioned to create the overall 3D effect. The animation property is then used to create the printing effect.

3D CSSプリンターをカスタマイズできますか?

はい、3D CSSプリンターをカスタマイズすることができます。色、サイズ、さらにはアニメーションの速度も変更可能です。これはプリンターのCSSプロパティを変更することで行われます。例えば、色を変更するにはプリンター要素のbackground-colorプロパティを変更します。

3D CSSプリンターをウェブサイトに組み込むにはどうすればいいですか?

3D CSSプリンターをウェブサイトに組み込むには、CSSとHTMLのコードをウェブサイトのコードにコピーする必要があります。CSSコードはHTML文書のheadセクションに含まれていることを確認し、HTMLコードはプリンターがウェブページ上に表示される場所に配置します。

3D CSSプリンターをアニメーション化することは可能ですか?

はい、3D CSSプリンターをアニメーション化することは可能です。アニメーションはCSSのanimationプロパティを使用して実現されます。このプロパティは、アニメーションの開始と終了状態、および中間のステップを定義するキーフレームを作成することができます。

3D CSSプリンターはどのブラウザでサポートされていますか?

3D CSSプリンターはCSSのtransformおよびanimationプロパティをサポートする全ての現代のブラウザで動作するはずです。これにはGoogle Chrome、Mozilla Firefox、Safari、Microsoft Edgeなどのブラウザが含まれます。

3D CSSプリンターを商用目的で使用することはできますか?

はい、3D CSSプリンターを商用目的で使用することができます。ただし、使用するコードのライセンス条件を確認し、遵守していることを確認することが常に良いアイデアです。

3D CSSプリンターを作成するためにはどのようなスキルが必要ですか?

3D CSSプリンターを作成するためには、HTMLとCSSに関する良い理解が必要です。transform、perspective、animationなどのCSSプロパティに精通していることが望ましいです。3Dモデリングの基本的な知識も役立つかもしれませんが、必須ではありません。

3D CSSプリンターでJavaScriptを使用できますか?

はい、3D CSSプリンターでJavaScriptを使用できます。プリンターはCSSのみを使用して作成できますが、JavaScriptを使用して、ユーザーのアクションに基づいてアニメーションを開始または停止するなど、インタラクティビティを追加できます。

3D CSSプリンターについてさらに学ぶためのリソースはありますか?

3D CSSプリンターについてさらに学ぶためのオンラインリソースはたくさんあります。SitePoint、CSS-Tricks、MDN Web Docsなどのウェブサイトには、CSSアニメーションと3D変形に関する包括的なチュートリアルとガイドがあります。YouTubeにもこのトピックに関する動画チュートリアルがたくさんあります。

Source:
https://www.sitepoint.com/3d-css-printer/