コーポレートサイトなどで、例えば自社のパートナー企業のロゴ画像を並べて一定間隔で繰り返し流れていく無限ループのスライダーコンテンツをどこかで見かけた方もいるのではないでしょうか。
今回はそんな無限ループする簡単なスライダーのサンプルを CSS のみで作った場合と JavaScript を利用した場合に分けてご紹介します。
まずは今回作成するデモをご覧ください。
See the Pen Untitled by digistate (@digistate) on CodePen.
See the Pen Infinite Logo Loop Slider by digistate (@digistate) on CodePen.
今回のロゴスライダーの要件は以下とします。
- 1スライド内に表示するロゴ数に合わせて、1つのロゴの幅(%)をCSSカスタムプロパティで定義する
- 1ループにかかるスライド時間を既定値(10秒)以外にする場合は、CSSカスタムプロパティで対応する
- スライド方向(左から右、または右から左)はスライダーごとに指定可能可能にする(セレクタの有無で判定)
- マウスオーバー時にアニメーションの一時停止有無を指定可能にする(セレクタの有無で判定)
- アニメーションは CSS の animation / @keyframes で行う
- 1スライド内のロゴ数を判別し、1つのロゴの幅を自動で算出する(ロゴ数や幅を限定しない)
- 1ループにかかるスライド時間は
data
属性でスライダーごとに指定できるようにする - スライド方向(左から右、または右から左)はスライダーごとに指定可能にする(セレクタの有無で判定)
- アニメーションは
requestAnimationFrame
メソッドを利用する(setInterval
は利用しない)
HTML
スライダーの HTML は、.slider
セレクタが付いたラッパー要素内に、さらに .slides
セレクタが付いたインナーラッパー要素があり、この .slides
内に単体のスライド(.slide
)が必要数分存在する状態になっています。
<div class="slider-container"> <div class="slider"> <div class="slides"> <div class="slide"> <!-- スライドコンテンツ --> </div> <div class="slide"> <!-- スライドコンテンツ --> </div> ... </div> </div> <div class="slider"> <div class="slides"> <div class="slide"> <!-- スライドコンテンツ --> </div> <div class="slide"> <!-- スライドコンテンツ --> </div> ... </div> </div> </div>
.slide
内には実際に表示したい各スライドのコンテンツ(ロゴ画像やアイコン)を指定しておきます。
HTML でのポイントは、スライダーの流れ(ロゴの移動)を切れ間なく表示するため、1つのスライダー(.slider
-> .slides
)につき、同じ内容のスライド(.slide
)を2つ用意しておきます。
<div class="slider"> <div class="slides"> <div class="slide"> <img src="logo1.png" /> <img src="logo2.png" /> <img src="logo3.png" /> <img src="logo4.png" /> <img src="logo5.png" /> </div> <div class="slide"> <img src="logo1.png" /> <img src="logo2.png" /> <img src="logo3.png" /> <img src="logo4.png" /> <img src="logo5.png" /> </div> </div> </div>
CSS(SCSS)
CSS では、外側の.slider
要素は表示幅を 100% にしておき、直下のインナーラッパー要素(.slides
)については倍のサイズ(200%)にすることで、.slides
内にある2つの .slide
のうち1つ(100%分)が見える状態にします(※もう片方の .slide
はエリア外(オーバーフロー領域)にある状態)。
このレイアウトを表現する具体的な CSS は以下のようになっています。
.slider-container { overflow: hidden; } .slider { width: 100%; position: relative; } .slides { display: flex; width: 200%; } .slide { flex-basis: 50%; display:flex; align-items: center; }
.slides
の幅(200%) に対し、.slide
は flex-basis: 50%
とすることで .slides
の半分、つまり 100% = ビューポートの幅で表示しています。
ここまでは CSS版、JavaScript 版共通のコードとなります。
CSS 版の場合
ここからは CSS のみで無限ロゴループスライダーを表示する場合の構造を解説します。
CSS カスタムプロパティ
アニメーション時間(--duration
)とロゴの幅(--logo-width
)は、.slides
のインラインスタイルにカスタムプロパティで指定し、CSS でそれを受け取るようにします。
<!-- アニメーション時間は12秒、スライド内にロゴが5枚ある(100% / 5枚 = 20%/枚)場合 --> <div class="slides" style="--duration:12s;--logo-width:20%;"> ... </div>
例えば、スライド内にロゴが6つある場合は、100% / 6枚 ≒ 16.67% / 枚としてロゴ1枚あたりの幅を--logo-width
に割り当てます。
.slide{ ... i{ flex-basis: var(--logo-width, 20%); } }
動作判定用セレクタ
CSS のみでスライダーのアニメーション方向の指定とマウスオーバー時にアニメーションを一時停止するためのセレクタを用意します。
.slides
に .alternate
というセレクタがある場合は、規定とは逆の左から右に移動するアニメーションを割り当て、.allow-pause
というセレクタがある場合は、スライダー上にカーソルがある場合はアニメーションを一時停止するようにします。
<div class="slides alternate allow-pause"> ... </div>
.slides{ ... &.allow-pause{ &:hover{ animation-play-state: paused; } } }
:hover
セレクタに対して、animation-play-state: paused
を指定することでマウスオーバー時はアニメーションを一時停止します。
CSS アニメーションの定義
CSSでは、animation / @keyframes を利用して表示エリアに対して 200% の幅の .slides
( 100%幅の .slide
x 2 をラップ)を自身の半分(50%分 = エリアの100%分)の距離を指定時間かけて右から左に移動するアニメーションを定義します。
アニメーションの時間は、--duration
というカスタムプロパティで変更できるようにしておきます。
.slides { ... animation: slide var(--duration, 10s) linear infinite; } @keyframes slide { 0% { transform: translateX(0); } 100% { transform: translateX(-50%); } }
なお、.slides
に .alternate
セレクタがある場合は、左から右に移動するアニメーションになるようにします。
.slides { ... &.alternate{ animation: slide_alternate var(--duration, 10s) linear infinite; } } @keyframes slide_alternate { 0% { transform: translateX(-50%); } 100% { transform: translateX(0%); } }
JavaScript 版の場合
JavaScript では、.slides
を一定速度で左、または右へ移動(translateX
)し、50%( = .slide
1つ分 )移動したらリセットする動作を繰り返すことで、ロゴスライダーの無限ループを表現します。
このリセットまで(50%移動)の周期を指定した時間をかけて実行するよう計算することで、スライダーの速度が調整されます。
ループ間隔時間の取得
.slides
が50%移動するまでの時間(ループ間隔)は、data
属性で.slides
ごとに指定して自由に変更できるようにします。
<!-- 10秒間隔でループ --> <div class="slides" data-duration="10"> ... </div>
今回のデモでは、data-duration
でループ間隔を1秒単位で指定できるようにしています。
上記の場合は、10秒で横並びのロゴが入れ替わります。
// ループ1回分の時間 const duration = parseInt( target.dataset.duration ) * 1000 || 10000;
data-duration
は秒単位で指定されているので、JavaScript では取得した値に 1000 を掛けてミリ秒に変換しておきます。
スライド要素の取得
スライダーの構成要素のうち、JavaScript で取得する要素は、重複する2つの .slide
をラップしている親要素の .slides
を取得します。
スライダーの動作は .slides
単位で処理するので、取得したすべての .slides
について順番にスライダーを実行します。
// .slides の取得 const slides = document.getElementsByClassName('slides'); for ( let i = 0; i < slides.length; ++i ) { // 現在の .slides const target = slides[ i ]; // スライダーを実行 }
ロゴの表示幅の計算
スライダーのエリア内に1度に表示するロゴなどの画像やアイコンは、.slide 内にある要素数に応じて等分割します。
// ロゴ数の取得 const childNum = target.firstElementChild.children.length; // ロゴの幅の算出 const logoWidth = ( (100 / childNum ) * 100 / 100 ).toFixed(2); // ロゴの幅をセット(CSS カスタムプロパティ) target.style.setProperty( '--logo-width', `${ logoWidth }%` );
スライダーの進行方向
スライダーの進行方向は、.slides
に .alternate
セレクタがある場合は、左から右に移動するようにします。
// スライダーの進行方向(右から左 or 左から右) const isAlternate = target.classList.contains( 'alternate' );
アニメーションの実行(ループ処理)
スライダーのアニメーションは、.slides
をX軸方向に指定時間かけて移動し、移動幅が 50% (.slide
1つ分)に到達したら 0% にリセットして再び移動するループ処理を行っています。
// 開始時間 let startTime = 0; // 経過時間 let elapsed = 0; // 進捗(0-1) let progress = 0; const loop = ( currentTime ) => { if ( !startTime ) { startTime = currentTime; } // 現在の経過時間 elapsed = currentTime - startTime; // 現在の進捗 progress = Math.min( 1, elapsed / duration ); // 進捗が 100%(位置が 50%)になったらリセットして再ループ if ( progress >= 1 ) { startTime = 0; elapsed = 0; progress = 0; } // スライドの位置を更新 if ( isAlternate ) { // 左から右の場合 target.style.transform = `translate3d(${ -50 + progress * 50 }%, 0, 0)`; } else { // 右から左の場合 target.style.transform = `translate3d(-${ progress * 50 }%, 0, 0)`; } // 次のフレームをリクエストする window.requestAnimationFrame( loop ); } // ループを開始 window.requestAnimationFrame( loop );
ループ処理には、requestAnimationFrame
を利用していますが、この場合はモニターのリフレッシュレート(1秒間に画面を更新できる回数)を基準に処理が実行されるため、指定時間で正確に処理をループさせるには、処理開始のタイムスタンプを保持し、処理を繰り返すごとに経過時間を割り出して指定時間に達したら開始時刻のタイムスタンプや経過時間、進捗をリセットして再び時間を計測します。
まとめる
ここまでの処理をまとめると、全体の JavaScript のコードは以下のようになります。
window.addEventListener( 'DOMContentLoaded', ( event ) => { const slides = document.getElementsByClassName('slides'); for ( let i = 0; i < slides.length; ++i ) { // 対象ラッパー要素 const target = slides[ i ]; // ループ1回分の時間 const duration = parseInt( target.dataset.duration ) * 1000 || 10000; // スライダーの進行方向(右から左 or 左から右) const isAlternate = target.classList.contains( 'alternate' ); // ロゴ数の取得 const childNum = target.firstElementChild.children.length; // ロゴの幅の算出 const logoWidth = ( (100 / childNum ) * 100 / 100 ).toFixed( 2 ); // ロゴの幅をセット target.style.setProperty( '--logo-width', `${ logoWidth }%` ); // 開始時間 let startTime = 0; // 経過時間 let elapsed = 0; // 進捗(0-1) let progress = 0; const loop = ( currentTime ) => { if ( !startTime ) { startTime = currentTime; } // 現在の経過時間 elapsed = currentTime - startTime; // 現在の進捗 progress = Math.min( 1, elapsed / duration ); // 進捗が 100%(位置が 50%)になったらリセットして再ループ if ( progress >= 1 ) { startTime = 0; elapsed = 0; progress = 0; } // スライドの位置を更新 if ( isAlternate ) { // 左から右の場合 target.style.transform = `translate3d(${ -50 + progress * 50 }%, 0, 0)`; } else { // 右から左の場合 target.style.transform = `translate3d(-${ progress * 50 }%, 0, 0)`; } // 次のフレームをリクエストする window.requestAnimationFrame( loop ); } // ループを開始 window.requestAnimationFrame( loop ); }; } );
今回のデモでは、ロゴスライダーを想定してみましたが、ロゴ画像やアイコンでなくても、例えば物件紹介サイトで部屋の間取りや屋内写真が流れるようにして、クリックするとポップアップ表示するなど、使い道はアイディア次第で色々とでてきそうです。
今回のデモのようなコンテンツも、提供中の WordPress プラグイン「DigiPress Ex – Blocks」などのカスタムブロックとして今後実装するかもしれません。