【Three.js】画面のスクロールに合わせて3D空間の物体を動かす

2021年4月18日JavaScript,Three.js

夜中に考えた文章って良くないよね。

はじめに

前回作った球体を動かしてみたいと思います。画面の奥の方でクルクル回る見出しみたいなカンジ。

できた

デモサイトはこちら

これは「おしゃれ」と言わざるを得ません。

文字を3Dにしたり、球体の周りに配置する方法は前回の記事で説明済みなので、アニメーションのところだけ説明します。

その前にCSSを見てみましょう。

CSS

めちゃめちゃ。メディアクエリーでレスポンシブにしてるとこです↓。

@media (min-width: 1050px) and (max-width: 1199px) {
  p {
    font-size: 20px;
  }
}
@media (min-width: 950px) and (max-width: 1049px) {
  .wrap {
    padding: 60px;
  }
  p {
    padding-left: 80px;
    font-size: 18px;
  }
}
@media (min-width: 820px) and (max-width: 949px) {
  .wrap {
    padding: 40px;
  }
  p {
    padding-left: 60px;
    font-size: 16px;
  }
}
@media (max-width: 819px) {
  .wrap {
    width: 75%;
    flex-direction: column;
    padding: 40px;
  }
  img {
    width: 100%;
    padding: 20px;
  }
  p {
    padding-left: 0;
    padding-top: 20px;
    font-size: 16px;
    text-align: center;
  }
}
@media (max-width: 649px) {
  .wrap {
    width: 85%;
    padding: 40px;
  }
  h1 {
    font-size: 60px;
    padding: 30px;
  }
}
@media (max-width: 549px) {
  .wrap {
    padding: 20px;
    margin: 10px;
  }
  img {
    padding: 10px;
  }
  p {
    padding-left: 0px;
  }
}
@media (max-width: 449px) {
  h1 {
    font-size: 50px;
    padding: 20px;
    border: 8px solid rgba(255, 255, 255);
  }
}

なんか難しいというか、なんというか…。画面サイズ変えながら、

「ここもうちょっとアレだなぁ…フォントサイズ16px、っと。アレ?そうするとココの余白が広すぎるなぁ。幅を…80%。アレ?写真もデカくなってんじゃん!paddingを小さくすればいいのか!?どうだ?アレー!?テキストの幅と揃わなくなって、なんかヘンじゃん!!」

ずーっとコレ。もはや「何が効いていて、何が効いていないのか」すら分からない状態。どうやったらキレイにまとまるのでしょうか?

まぁ、今回の本題はそこじゃないので良しとします。はい。すいませんでした。

本題

本題のJavaScriptに入ります。

3つの球体をひとまとめにする

画面のスクロールに合わせて球体を動かすワケですが、ひとつずつ動かすのは大変。なので、1つのコンテナの中に3つの球体を入れることにしました↓。

// 見出しを全部入れるコンテナ
const titleContainer = new THREE.Object3D();
titleContainer.position.set(0, 3, 0);
scene.add(titleContainer);

// works
const worksTitle = new THREE.Object3D();
worksTitle.position.set(0, -200, 0);
titleContainer.add(worksTitle);

// about
const aboutTitle = new THREE.Object3D();
aboutTitle.position.set(0, -400, 0);
titleContainer.add(aboutTitle);

// news
const newsTitle = new THREE.Object3D();
newsTitle.position.set(0, -600, 0);
titleContainer.add(newsTitle);

これで、コンテナを動かす処理だけ書けば、3つの球体も一緒に動いてくれるようになります。

スクロールイベントを使ってコンテナを動かす

if (worksTop < windowH * mag) {
  if (worksFlag === false) {
    worksFlag = true;
    TWEEN.removeAll();
    new TWEEN.Tween(titleContainer.position)
      .to({x: 0, y: 203, z: 0}, 1500)
      .easing(TWEEN.Easing.Exponential.InOut)
      .start();
  } 
} else {
  if (worksFlag === true) {
    worksFlag = false;
    TWEEN.removeAll();
    new TWEEN.Tween(titleContainer.position)
      .to({x: 0, y: 3, z: 0}, 1500)
      .easing(TWEEN.Easing.Exponential.InOut)
      .start();
  } 
}

worksTopはココのことです↓。

windowHには画面の高さが、magには1.5が入っています。

「画面の高さの1.5倍の位置に境界線を置いて、そこをworksTopが超えたらコンテナを動かす」というワケです。

絵にするとこう↓。

いろんなサイトで見かける「スクロールすると下からフワッと出てくるヤツ」。あれと同じです。

今回の困ったこと

パソコンだと問題なくできてると思うんですけど、スマホだと色々困ったことが。

ブラウザの上の方にあるヤツ。アドレスバーっていうんですかね。

コイツがねぇ。邪魔ですねぇ。何言ってるか分からないかもしれないけど説明しますね。

説明

3D空間の描画をしてるのがwebGLRendererっていうレンダラーなんですけど、描画領域って

webGLRenderer.setSize(window.innerWidth, window.innerHeight)

で設定してるんですね。ブラウザの表示領域の幅と高さで決まるんです。

で、アドレスバーって下にスクロールすると消えるじゃないですか。そうするとブラウザの表示領域が広くなりますよね。でもレンダラーの描画領域はそのままだから、なんかスキマができちゃうんですよ。

なんとか直したんですけど、それは「自分のスマホ」では直ってるっていうだけでして。全ての実機で試せるワケもないので、機種によってはおかしな事になっているんじゃなかろうかと。なんかモヤモヤ。

そこで「こういう3D的な表現を使っている、プロが作ったサイトはどうなっているんだろう?」と思って色々見てみました。

そしたらアレですね。たぶんブラウザのスクロール機能は使ってないんじゃないかなぁと思います。スクロールはするんですけど、それは「スワイプすると3D空間のモノが動いてスクロールしてるように見える」ってだけで。はい。なるほどなぁと思いました。

あとがき

今回も大変でした。でも、スクロールするものを作るときに気を付けることが分かったので良かったです。

僕の中では「最高傑作」といってもいいほどのおしゃれ感。これは期待できるんじゃないでしょうか。ありがとうございました。

おしゃれ度

★★★☆☆

審査員長のコメント

良いと思います。3Dを使いながらもそれを前面に押し出すでもなく、あくまでも主役はコンテンツなのだと。そういった考えは非常に素晴らしい。

でもアナタ…やってしまいましたねぇ…。

「白黒」「英語」「外国の風景」。お手軽におしゃれを演出することができるこれらの要素。これを使って★4を狙いに行くという、その浅ましさよ。魂胆が見え見えです。デザインどうこうではなく、その姿勢にガッカリしました。もっと「おしゃれ」に対して真摯に向き合ってみてください。次は期待しています。

Posted by ナカタ