⛰️

モダンアニメーションのパララックス(視差効果)背景のライブラリを作った

2022/01/16に公開約5,600字

背景のパララックスは比較的モダンなサイトでよく見られる表現です。画像はもちろん背景や画面を大きく使ったビジュアルに使うことによって効果的なスクロール表現を実現することができます。
しかしちょっとした実装としては意外と面倒で、既存のライブラリを使ってもいまいちかゆいところに手が届かない印象です。(例えば、locomotive-scrollは有能だけど背景パララックスだけのために導入するにはめんどくさい、simpleParallaxは要素ごとのパラメータの設定がライブラリだけだとできないなど…)

他のライブラリでも今まで使っていたものでは背景パララックスだけに特化したものはあまりなく、設定や構築をひと手間加えて実装する必要がありました。アニメーションや細かな処理が今ひとつであったり、画像ごとに設定ができなかったり、パフォーマンスの面で改善できたり……など多くの不便を感じでいました。なので今回はそのような点を解消しつつ、シンプルで使いやすい、最低限の機能のみのパララックスを作りました。

作ったもの


https://github.com/yitengjun/ukiyo-js
こんな感じのを作りました。gifだとカクカクしていますが、こちらから詳しく見ることができます。
デモでは分かりやすいように大きく動かしていますが、アニメーションのパラメーターは細かく設定することができます。
デモのようにlocomotive-scrollのようなサイト全体のスムーススクロールと親和性が高いです。

次のような特徴があります。

  • img要素への対応(pictureタグやwebpと合わせて使える)
  • 画像ごとにオプションを設定できる
  • サイト全体のスムーススクロールとの親和性
  • シンプルで最小限な実装

パララックスの最低限の機能のみを残し、そして画像要素のスタイルを最大限に生かしたパララックスになります。img要素やpicture要素にそのままダイレクトに設定しても使うことができます(もちろんbackgroud-imageでも使えます)。

img要素にそのまま使える

<img src="image.jpg" class="parallax">

このようにimg要素やpictureタグにも直接使うことができます。

<picture>
  <source srcset="~" />
  <source srcset="~" />
  <!-- pictureタグの中のimg要素に設定 -->
  <img class="parallax" src="~">
</picture>

pictureタグの場合はタグ内のimg要素に設定してください。

要素ごとにパラメーターの設定ができる

<img src="image.jpg" data-u-scale="1.7" data-u-speed="1.3">

要素ごとにパララックスのパラメーターを設定することができます。

transformやinset,marginのautoとの併用ができる

img {
  top: 50%;
  left: 50%;
  transform: translate(-50%,-50%);
  position: absolute;
  width: 70%;
}

たとえばこのようなimg要素があるとします。
CSSは要素をposition: absoluteとtransformで画面中央に配置するスタイルですが、simpleParallaxの場合では、このようなCSSが要素に適用されていた場合はレイアウトが崩れてしまいます。


色々付けてるとめっちゃ崩れる(simpleParallaxの例)

これはオリジナルの要素にラッパー要素を追加してラッピングするため、オリジナルの要素に適用されていたスタイルがラッパー要素に上手く適用されていないためです。(marginのautoやinset、transformなどを使っていたりすると)

このライブラリではそうしたスタイルとの併用にも対応しました。
オリジナルの要素のスタイルを最大限活かす”vanilla”な仕様で、余計なスタイルをなくし、スタイルが崩れることのないパララックスになっています。

上は上記のtransform+absoluteの画面中央寄せのスタイルを適用したimg要素です。このまま直接パララックスを適用した状態でも、要素は崩れることなくオリジナルの位置を保っていることが分かります。

inset(top/right/bottom/left)の値はauto場合はオリジナルの値が反映され、margin-left / rightのスタイルは常にオリジナルの要素の値が反映されるようになっています。
なのでmargin-left: autoのような右寄せのスタイルもそのまま反映されます。

パララックスのために要素のスタイルが崩れることがないのは嬉しいですね。扱いもしやすく非常に汎用的です。
例外としてはposition: absoluteと併用したときですが、その場合のみ直接パララックス要素にautoの設定がされます。
(とはいえimg要素に直接ガシガシ書いていくこともあまりないかもしれませんが……)

実装

パララックス部分

画面上に対する要素の位置の割合(0-100の値で画面の中心にある状態を50)を計算し、ラッパー要素に対するパララックス要素の高さ(+スケール)の差分を上記0-100の割合でアニメーションさせます。


  _setPosition() {
    this.element.style.transform = `translate3d(0 , ${this._getTranslate()}px , 0)`;
  }

  _getTranslate() {
    const overflow = Math.abs(this.overflow);
    const progress = this._getProgress() / 100;
    const translateY = this.overflow + overflow * progress * this.options.speed;

    return Math.round(translateY);
  }

  _getProgress() {
    const viewportHeight = window.innerHeight;
    const elementHeight = this.wrapper.offsetHeight;

    const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
    const elementOffsetTop = this.wrapper.getBoundingClientRect().top + scrollTop;

    const distance = scrollTop + viewportHeight - elementOffsetTop;
    const percentage = distance / ((viewportHeight + elementHeight) / 100);

    return Math.min(100, Math.max(0, percentage));
  }

実際にパララックスで動く量はその差分のみに制限されるため、画像が途中で切れることなく、そしてその差分を画面上にある割合でフルに動かすので効果的に最大限の動きで視差効果を生み出すことができます。

非同期での画像の読み込み

  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => resolve(img);
    img.onerror = (e) => reject(e);
    img.src = src;
  });

画像の読み込みはpromiseを使って非同期で読み込みます。(本当はdecodeを使いたかったのですが対応ブラウザと後方互換性も兼ねてonloadで読み込みの判定を行っています)

_observerCallback(entries) {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        this.isVisible = true;
        this._update();
      } else 
        this.isVisible = false;
        this._cancel();
      }
    });
  }

Intersection Observerで要素の交差を監視します。
要素が画面上にあるときのみパララックスのアニメーションが行われるようにし、イベントを発火を最小限に抑えます。

オプション

new Ukiyo(image, {
    scale: 1.5, //スケール
    speed: 1.5, //スピード
    willChange: true, //will-change付ける
    wrapperClass: "ukiyo-wrapper" //ラッパー要素のクラス名
})

オプションは全て要素ごとに設定することができます。

要素ごとにパララックスの設定を変えたい

<img src="image.jpg" data-u-scale="1.7" data-u-speed="1.3">

スケールとスピードはそれぞれdata-u-*で設定することができます。

will-change付けたい

<img src="image.jpg" data-u-willchange>

要素ごとにwill-changeオプションを設定できます。
simpleParallaxなどではデフォルトで常にwill-change: transformのスタイルが付与されますが、乱用はかえって逆効果になってしまう可能性があります。特に大量の画像を扱う場合などは慎重に使い所を選びたいプロパティであるため、オプションでwill-changeを適応できるように設定しています。
オプションを有効にすると、パララックスがアクティブな場合のみwill-changetransformが適用されます。画面外にある場合などパララックスが非アクティブな場合はwillchange: autoに切り替わります。

ラッパー要素にクラスを付けたい

<img src="image.jpg" data-u-wrapper-class="cool-name">

これはおまけ程度ですが、ラッパー要素に好きなクラス名を付けることができます。


背景パララックスはスクロール表現に奥行きを与える非常に効果的なアニメーションです。そのまま画像に使うことができるのでコストも低く、アニメーションも派手ではないので導入もしやすいと思います。
詳細はこちらから見ることができます。ぜひ使ってみて下さい⛰️⛰️⛰️

https://github.com/yitengjun/ukiyo-js

Discussion

ログインするとコメントできます