コハム

Webクリエイターのコハムが提供する、Web制作に役立つ情報をまとめたブログです。

Javascriptは不使用!無限スクロールをCSSだけで実装するチュートリアル

Infinite-Scrolling Logos In Flat HTML And Pure CSS

記事は上記記事を意訳したものです。

※当ブログでの翻訳記事は元サイト様に許可を得て掲載しています。


自動スクロールするロゴファームを作るように頼まれたとき、私は自問しました。「つまり、<marquee>のようなものですか?」それほど奇妙なリクエストではありませんが、<marquee>という言葉は、Geocitiesが支配していた「古い」Web時代を思い起こさせます。次は、繰り返し点滅するユニコーンのGIFの背景ですか?

<marquee>要素に手を出そうとしている場合はやめてください。MDNにはそのページのトップに厳しい警告があります。

非推奨: この機能はもはや推奨されていません。一部のブラウザがまだサポートしているかもしれませんが、関連するWeb標準から削除されている可能性があり、削除のプロセス中である可能性があり、互換性のためにのみ保持されている可能性があります。使用を避け、可能であれば既存のコードを更新してください[...]この機能はいつでも機能しなくなる可能性があることを認識してください。

<marquee>が提供する無限スクロール機能は、CSSで確実に実現できます。しかし、私が調査してみると、このような例が非常に少ないことに驚きました。おそらく、自動スクロールする要素は最近の流行ではないのかもしれません。自動スクロールの振る舞いそのものが、アクセシビリティ上の注意喚起として十分な赤信号である可能性があるからです。

いずれにせよ、これを行うためのツールが揃っており、私がどのように進めたかを共有したいと思います。これは、多くの異なるCSS機能を活用した多くの異なる方法で行うことができるものです。私がこれらすべてを徹底的に探求するつもりはありませんが、他の人の考え方を見るのは面白いと思います。それがこの記事から得られるものです。

作成するもの

しかし、まず、完成した結果の例を見てみましょう。

アイデアはかなりシンプルです。何らかのコンテナが必要で、その中に終わりのない無限にスクロールする一連の画像が欲しいと思います。つまり、最後の画像がスライドして入ってくると、シリーズの最初の画像が直接それに続く無限ループを作りたいのです。

だから、こういう計画です。まずHTMLを設定し、次にコンテナを選んで、画像が正しく位置付けられていることを確認してから、それをまとめるためのCSSアニメーションを書きます。

既存の例

前述のように、いくつかのアイデアを探してみました。まさに求めていたものは見つかりませんでしたが、いくつかのデモがインスピレーションを与えてくれました。本当にしたかったのは、<marquee>アイテムを「クローン」する必要なく、CSSのみを使用することでした。

ジェフ・グラハムの「スライディングバックグラウンドエフェクト」は私が求めていたものに近いです。古いですが、オーバーフローを意図的に使用して画像をコンテナから「スライド」させ、永遠にループするアニメーションを行う方法を見るのに役立ちました。しかし、それは背景画像であり、他のプロジェクトで再利用するのが難しい非常に具体的な数値に依存しています。

CodePenのCoding Journeyチャンネルからも、もう1つ素晴らしい例があります。

その効果は確かに求めているものですが、JavaScriptを使用しており、わずかな使用であってもJavaScriptを省いておきたいと考えました。

ライアン・マリガンの「CSSマーキーロゴウォール」が最も近いものです。個々の画像があるロゴファームであり、CSSマスキングが画像を隠すためにどのように使用されるかを示しています。私はそのアイデアを自分の作業に組み込むことができました。

しかし、私が求めているものはまだあります。できるだけ少ないHTMLが必要であり、つまり画像の連続数を作成するために複製する必要のないマークアップが必要です。つまり、画像が「マーキー」コンテナの唯一の子要素である無限スクロールのシリーズを作成できるはずです。

他の場所でもいくつかの例を見つけましたが、これで私を正しい方向に導いてくれるのは十分でした。私に付いて来てください。

HTML

最初にHTML構造を設定しましょう。何よりも、非常に少数の要素で、できるだけ短いファミリーツリーであることが「シンプル」であることを望んでいます。ここでは、「マーキー」コンテナとその中のロゴ画像だけで済みます。

<figure class="marquee">
  <img class="marquee__item" src="logo-1.png" width="100" height="100" alt="Company 1">
  <img class="marquee__item" src="logo-2.png" width="100" height="100" alt="Company 2">
  <img class="marquee__item" src="logo-3.png" width="100" height="100" alt="Company 3">
</figure>

これにより、要素が「平坦」に保たれます。これを動作させるために必要な他の要素はありません。

コンテナの設定

Flexboxは、画像の間に隙間を持つ一連の画像を確立するための最もシンプルなアプローチかもしれません。行方向に流れるようにする必要はないので、デフォルトでそうなります。

.marquee {
  display: flex;
}

画像要素に絶対位置を使用する予定であることを既に知っているので、コンテナに相対位置を設定して、それらを含めることが理にかなっています。そして、画像は絶対位置にあるため、コンテナのサイズに影響を与える高さや幅の寸法がありません。したがって、明示的なブロックサイズ(高さの論理的相当物)を宣言する必要があります。また、画像が表示されている場所から出ると非表示にしたいので、最大幅も設定する必要があります。

.marquee {
  --marquee-max-width: 90vw;

  display: flex;
  block-size: var(--marquee-item-height);
  max-inline-size: var(--marquee-max-width);
  position: relative;
}

ここでいくつかのCSS変数を使用していることに注意してください:1つは画像の高さに基づいてマーキーの高さを定義するもの(--marquee-item-height)、もう1つはマーキーの最大幅を定義するもの(--marquee-max-width)。今すぐマーキーの最大幅に値を与えることができますが、画像の高さを正式に登録して値を割り当てる必要があります。私が進むときにどの変数を使うかを知っているのが好きです。

次に、画像がコンテナの外側にあるときに非表示にする必要があります。したがって、水平オーバーフローを適切に設定します。

.marquee {
  --marquee-max-width: 90vw;

  display: flex;
  block-size: var(--marquee-item-height);
  max-inline-size: var(--marquee-max-width);
  overflow-x: hidden;
  position: relative;
}

そして、ライアン・マリガンがCSSマスクを使用している方法がとても気に入っています。画像が表示されているような印象を作り出します。それを混ぜ込んでみましょう。

.marquee {
  display: flex;
  block-size: var(--marquee-item-height);
  max-inline-size: var(--marquee-max-width);
  overflow-x: hidden;
  position: relative;
  mask-image: linear-gradient(
    to right,
    hsl(0 0% 0% / 0),
    hsl(0 0% 0% / 1) 20%,
    hsl(0 0% 0% / 1) 80%,
    hsl(0 0% 0% / 0)
  );
  position: relative;
}

ここまでの内容は次のとおりです。

マーキーの項目の位置決め

絶対位置付けは、画像をドキュメントフローから引き抜いて手動で位置を決めることを可能にします。

.marquee__item {
  position: absolute;
}

これにより、画像が完全に消えたように見えます。しかし、画像はそこにあります - 画像は互いに直接重なり合って積み重ねられています。

マーキー項目の高さに一致するようにCSS変数を使用することができるようになったので、この時点でそれを使用します。

.marquee__item {
  position: absolute;
  inset-inline-start: var(--marquee-item-offset);
}

マーキー画像をコンテナの外側に押し出すには、--marquee-item-offsetを定義する必要がありますが、その計算は簡単ではないので、次のセクションでどのように行うかを学びます。アニメーションがどうあるべきかは既に知っているので、一定の期間後に初期の遅延が発生し、無限に続行されるものです。一時的なプレースホルダーとしていくつかの変数を使用して、それをプラグインします。

.marquee__item {
  position: absolute;
  inset-inline-start: var(--marquee-item-offset);
  animation: go linear var(--marquee-duration) var(--marquee-delay, 0s) infinite;
}

マーキー項目を無限にアニメートするには、2つのCSS変数を定義する必要があります。1つは期間(--marquee-duration)であり、もう1つは遅延(--marquee-delay)であり、計算する必要があります。これは次のセクションで調べます。

.marquee__item {
  position: absolute;
  inset-inline-start: var(--marquee-item-offset);
  animation: go linear var(--marquee-duration) var(--marquee-delay, 0s) infinite;
  transform: translateX(-50%);
}

最後に、マーキー項目を水平に-50%移動させます。これは、画像のサイズが均等でない場合に扱うための小さな「ハック」です。

画像のアニメーション

アニメーションを機能させるためには、次の情報が必要です。

  • ロゴの幅、
  • ロゴの高さ、
  • 項目数、および
  • アニメーションの期間。

次の設定を使用して変数のセットを使用しましょう。

.marquee--8 {
  --marquee-item-width: 100px;
  --marquee-item-height: 100px;
  --marquee-duration: 36s;
  --marquee-items: 8;
}

注意:BEM修飾子.marquee--8を使用して、8つのロゴのアニメーションを定義しています。--marquee-item-widthの値を知っているので、アニメーションのキーフレームを定義できます。

@keyframes go {
  to {
    inset-inline-start: calc(var(--marquee-item-width) * -1);
  }
}

アニメーションは、マーキー項目を右から左に移動させ、それぞれが右から入ってきて画面外で左端を越え、マーキーのコンテナの外側に移動するようにします。

次に、--marquee-item-offsetを定義する必要があります。マーキー項目をコンテナの右側まで押し出したいので、アニメーションの終了状態とは対照的な位置に配置する必要があります。

オフセットが100% + var(--marquee-item-width)であるべきだと思うかもしれませんが、それでは小さなスクリーンでロゴが重なってしまいます。これを防ぐには、すべてのロゴの最小幅を知る必要があります。以下のように行います。

calc(var(--marquee-item-width) * var(--marquee-items))

しかし、これだけでは不十分です。マーキーのコンテナが大きすぎると、ロゴの幅が最大スペースよりも少なくなり、オフセットがコンテナ内になり、ロゴがマーキーのコンテナ内で見えるようになります。これを防ぐために、max()関数を使用します。

--marquee-item-offset: max(
  calc(var(--marquee-item-width) * var(--marquee-items)),
  calc(100% + var(--marquee-item-width))
);

max()関数は、引数の2つの値のうち、大きい方をチェックします。すべてのロゴの合計幅か、コンテナの最大幅に単一のロゴの幅を加えたもののどちらかです。前者は大きいスクリーンで真であり、後者は小さいスクリーンで真です。

最後に、この式を使用して複雑なアニメーション遅延(--marquee-delay)を定義します。

--marquee-delay: calc(var(--marquee-duration) / var(--marquee-items) * (var(--marquee-items) - var(--marquee-item-index)) * -1);

遅延は、アニメーションの期間を二次多項式で除算したものと等しくなります。二次多項式は、次のような部分で構成されます。項目数と現在のアイテムインデックスの違いをかけます。

var(--marquee-items) * (var(--marquee-items) - var(--marquee-item-index))

アニメーションを「過去」から開始するために、負の遅延(* -1)を使用しています。定義する必要がある残りの唯一の変数は、--marquee-item-index(現在のマーキーアイテムの位置)です。

.marquee--8 .marquee__item:nth-of-type(1) {
  --marquee-item-index: 1;
}
.marquee--8 .marquee__item:nth-of-type(2) {
  --marquee-item-index: 2;
}

/* etc. */

.marquee--8 .marquee__item:nth-of-type(8) {
  --marquee-item-index: 8;
}

最後に、この最終デモをもう一度ご覧ください。

改善

この解決策はもっと良くなるかもしれません、特にロゴの幅が等しくない場合です。一貫してサイズの異なる画像間のギャップを調整するには、アニメーションの遅延をより正確に計算できます。これは可能です、なぜならアニメーションは直線的であるからです。式を見つけようとしましたが、もっと微調整が必要だと思います。

広いスクリーンで大きなギャップが見られる場合は、CSSカスケーディングによって修正できるかもしれません。いくつかのメディアクエリを設定して、画像の数がより少ないマーキーがより大きなギャップを持つことができるようにします。しかし、これはまだずさんな修正であり、もっと良い方法があるかもしれません。

また、この解決策はCSS変数を大量に使用しているため、古いブラウザでは機能しない可能性があります。その場合はバックアップ計画を持っているか、JavaScriptで変数を代入してください。この記事では、その方法を討議しません。

結論

アクセシビリティと機能性にとっても良い方法で無限スクロールマーキーを構築できることを示しました。これは、CSSのみを使用して実現でき、非常に短いHTMLファミリーツリーを持ちます。すべてのコードが揃っているので、最初の見通しを持つことができます。したがって、カスタマイズするのが難しくないです。

もちろん、完璧ではありません。私の最初のプロトタイプでは、ロゴの大きさに基づいてマーキーのコンテナのサイズを動的に変更しました。マスクが適切に機能することを保証するために、JavaScriptを使用しました。しかし、これを行うことは複雑で混乱する可能性があります。ただし、一度実装すると、再利用するのが非常に簡単になります。

何よりも、これはHTMLとCSSのみで行われました。JavaScriptの依存を排除し、その結果、より高速でアクセス可能なウェブページを提供します。スクロールバーが意図せず小さなロゴの横に表示されることはありません。

なんだか古典的で、なんだか懐かしいですが、CSSで無限のスクロールを実現できることを示しました。これがあなたのプロジェクトに役立つことを願っています。

©コハム