オンラインでご相談ください!

ご相談内容が完全に固まっていない場合でも
遠慮なくミーティングの予約をどうぞ

TOP デザイン・技術 いろいろなパターンで画像をループさせてみた

いろいろなパターンで画像をループさせてみた

いろいろなパターンで画像をループさせてみた

初めまして。
BALANCeのエンジニアの岡村です。普段は、主にWebフロントエンド全般ですが、ビルド環境を整えたり、サーバ触ったり、unityやったりと幅広くやってます。

今回は、Webで画像のループをさせる際にいくつかの実装方法を試したので、その検証内容と結果についてご紹介します。

具体的には、最近実装を担当した下記サイトのフッターにある、「join」や「scroll」などが書かれた部分です。

https://cc.day/

検証した3つの実装方法

上記サイトのループ部分を、次の3つの実装方法に分けて、どれが一番スムーズかを検証しました。

  1. マイフレーム毎にdomの要素のx値を直接更新
  2. cssのkeyframe animationを使ってループ
  3. canvas2d を使ってループ

ちなみに当初の予想では【3】のcanvas2dが圧倒的にスムーズかなと思っていました。

が、実際にやってみると予想とは違う結果だったので、検証してみて良かったです。
コード(github)
demo
それぞれ共通の演出としてhover時にループ速度が速くなるようにしてます。

【1】 マイフレーム毎にdomの要素のx値を直接更新

コード

こちらは、初めに要素を横に並べて、それぞれの要素に対して、マイフレーム左に動かしています。


//マイフレームごとに呼ばれるupdate関数
update(diff) {
    const frameRate = Math.min(diff / ((1 / 60) * 1000), 1);
    this.currentPosX += this.speed.value * frameRate;

    this.v = this.currentPosX;
    this.inner.style.transform = `translate3d(${-this.v}px, 0px, 1px)`;
    if (this.v > this.resetPoint) {
      this.v = 0;
      this.currentPosX = this.currentPosX - this.resetPoint;
    }
  }

この方法を試してわかったのは以下の問題点です。

  • 並べた要素が画面幅を超えるように作るため、必要以上の要素を並べる必要がある
  • 並べた要素数が多ければ多いほど、マイフームで余計なループと余計なdom操作が必要になる
  • 大きいdomを動かす必要がある

結果的にあまり良いパフォーマンスが出ませんでした。特にhover時にカタカタとカクツク瞬間が見えます。

【2】cssのkeyframe animationを使ってループ

コード
こちらは、dom構造は【1】と変わりませんが、ループはcss keyframe animationを使ってループさせています。

また、速度を上げる際には、あらかじめ取得しておいたAnimationオブジェクトのplaybackRateを上げたい速度に合わせて変更しています。


wrap.addEventListener("mouseenter", (e) => {
    gsap.to(animation, 2, {
      playbackRate: 6,
      ease: "expo.out",
    });
  });

  wrap.addEventListener("mouseleave", (e) => {
    gsap.to(animation, 2, {
      playbackRate: 1,
      ease: "expo.out",
    });
  });

初めはcss animationのdurationをjsから変更する方法で実装していたのですが、この方法だと他のモックと違った動きをしたので、上記の方法にしました。

パフォーマンス的には上々で、滑らかに動いてると思います。

その原因として考えられるのは以下の点です。

  • そもそもhover時にdomの変更を加えていない
  • cssでのanimationだとブラウザへの負荷が少ない?(未検証)

補足

durationを動的に変えた場合、例えば『duration 2s を設定していて、1sに変える場合』に、こんな動作をします。
css animationは内部で経過時間を計測していて、2000ms経過したときにkeyframesの100%の値になるような動きをします。
そのため、animationの途中(1000ms)でdurationを1sに修正した場合は、1000ms = keyframes 100%になるため、いきなりアニメーションが終わったり、リピートが始まったりします。

【3】canvas2d を使ってループ

コード
こちらは、dom構造は至ってシンプルで、canvasを同じ高さで置いてます。

画像を読み込み、画面内の画像だけを描画しています。

  drawImg(index = 0, x = 0) {
    const img = this.imgs[index % this.imgs.length];
    if (!img) return;
    const imgWidth = (img.naturalWidth / img.naturalHeight) * HEIGHT;

    const _x = x + imgWidth * pixelRatio + MARGIN * pixelRatio;

    if (_x >= 0 || x >= window.innerWidth * pixelRatio) {
      //   this.drawImg(index + 1, _x);

      const y = (this.canvas.height - HEIGHT * pixelRatio) / 2;
      this.ctx.drawImage(
        img,
        0,
        0,
        img.naturalWidth,
        img.naturalHeight,
        x,
        y,
        imgWidth * pixelRatio,
        HEIGHT * pixelRatio
      );
    }

    if (x < window.innerWidth * pixelRatio)
      this.drawImg((index % this.imgs.length) + 1, _x);
  }

この方法では下記の問題があります。

  • 画面幅が広くなればなるほど、ループ回数など描画コストが上がる
  • retinaや、4kなどの解像度も重さに影響される

こちらのデモでは、解像度の分canvasを大きくしてるので描画自体は綺麗に見えますが、その一方でコストが上がってます。

この方法よりも【2】のほうがスムーズだと感じる原因は、やはり【2】のほうが「cssでのanimationだとブラウザへの負荷が少ない可能性がある」からなのかなーと思っています。

終わりに

3つの方法を検証した結果、今回の例では【2】がベスト、【3】がベターという結果になりました。

ただ、【2】は今回実験的なAPIを利用しているので、環境によっては違う動作になってしまうかもしれません(現状はIE以外は対応してるみたいです)。

また、今回の例では【1】は最もパフォーマンスが悪いという結果となりましたが、後から別の演出を追加したい時など、コードに柔軟性はあります。

そのため一旦【1】の方法で作って、最終的にこうした簡単な動きだけで良い、となったタイミングで【2】にするのがいいのかもしれません。

DOWNLOAD

未公開実績も多数!
BALANCeの【デザイン・技術】サービス説明資料には、『Javascript』『CSS』の実績が多数掲載されています。
また、関連した会社資料も無料ダウンロードできますので、ぜひご覧ください!

CONTACT

お見積もりやご提案に関しては、費用は発生いたしません。お気軽に、お問い合わせください。

RECRUIT

BALANCeでは共に働く仲間を随時お待ちしております。
印象に残るプロダクトを一緒に作りませんか?

採用情報はこちら

タグから選ぶ