マグネットのように背景色が移動するタブボタンの作り方
タブのボタンをカレント表示させる際に、背景色がマグネットのようにスライドして追従するアニメーションをつけてみました。
Chrome 125からサポートされたCSS Anchor Positioningを使用すればCSSだけで実装できそうですが、今回は幅広いブラウザでの対応が必要だったのでJavaScriptを使用しました。
実装例
早速ですが以下が実装例です。
タブボタンをクリックするとグレーの背景色が磁石のように遅れて追従します。今回はタブボタンは1行ですが、複数行となった場合にも対応しています。
実装のポイント
HTML、CSS、JavaScriptの3つに分けて簡単に解説します。
HTML
HTMLの構造は以下のとおりです。
<div class="c-tab" data-tab="wrapper"> <ul class="c-tab__list" role="tablist" data-tab="list"> <li class="c-tab__list-item" data-tab="item" role="presentation"> <button role="tab" id="tab01" class="c-tab__button" aria-expanded="true" aria-selected="true" aria-controls="tabpanel01" data-tab="button">タブ1</button> </li> <!-- 以下繰り返し --> </ul> <span class="c-tab__bg" data-tab="bg"></span> <div class="c-tab__body"> <div id="tabpanel01" class="c-tab__body-item" role="tabpanel" aria-labelledby="tab01" aria-hidden="false" tabindex="0"> <p>タブ1のコンテンツ</p> </div> <!-- 以下繰り返し --> </div></div>よくあるタブのHTMLの構造です。role等を設定することで、スクリーンリーダーでもタブの情報を読み上げられるようにしています。
背景色の要素はdata-tab="bg"という属性を付与したspanタグで、タブボタンの下に配置しています。
CSS
CSSは以下のとおりです。アニメーションに関連する部分のみを抜き出しています。
/* 背景の親要素 */.c-tab { position: relative;}
/* 省略:タブボタン、タブコンテンツなどのスタイル */
/* 背景要素 */.c-tab__bg { display: block; position: absolute; top: 0; left: 0; background-color: #f0f0f0; z-index: -1; pointer-events: none; transition-property: transform, width, height; transition-duration: 0.3s; transition-timing-function: ease;}背景要素(c-tab__bg)がアニメーションするように、transitionプロパティを設定しています。今回はtransform、width、heightの3つのプロパティに対してアニメーションを設定しています。
また基準となる要素を中心に背景要素を移動させるため、親要素のc-tabにposition: relative;を設定しています。今回はc-tabに対して設定しましたが、HTMLの構造によって変わってくるので適宜調整が必要です。
JavaScript
基本的なタブの処理は置いておいて、磁石のように追従する部分のコードを中心に解説します。
背景色を動かすために、updateTabBackgroundという関数を作成しています。
const tabWrapper = document.querySelector('[data-tab="wrapper"]');const tabBg = tabWrapper.querySelector('[data-tab="bg"]');const tabList = tabWrapper.querySelector('[data-tab="list"]');
// 背景色を動かす処理function updateTabBackground() { const activeButton = tabWrapper.querySelector('[aria-selected="true"]');
if (activeButton) { const buttonRect = activeButton.getBoundingClientRect(); const listRect = tabList.getBoundingClientRect();
// 背景の位置とサイズを取得 const x = buttonRect.left - listRect.left; const y = buttonRect.top - listRect.top; const buttonWidth = buttonRect.width; const buttonHeight = buttonRect.height;
// 背景の位置とサイズを反映 tabBg.style.transform = `translate(${x}px, ${y}px)`; tabBg.style.width = `${buttonWidth}px`; tabBg.style.height = `${buttonHeight}px`; }}この関数の中では大きく分けて2つの処理を行っています。
- 背景の位置とサイズを取得する処理
- 背景の位置とサイズを反映する処理
背景の位置とサイズを取得
const buttonRect = activeButton.getBoundingClientRect();const listRect = tabList.getBoundingClientRect();
// 背景の位置とサイズを取得const x = buttonRect.left - listRect.left;const y = buttonRect.top - listRect.top;const buttonWidth = buttonRect.width;const buttonHeight = buttonRect.height;getBoundingClientRect()でビューポートに対するリスト要素の位置と、タブボタンの位置を取得し、その差分からリストに対するボタンのオフセット(相対座標)を計算しています。
またgetBoundingClientRect()のwidthとheightプロパティにアクセスし、アクティブなボタンの幅と高さを取得しています。
背景のサイズと位置を反映
// 背景の位置とサイズを反映tabBg.style.transform = `translate(${x}px, ${y}px)`;tabBg.style.width = `${buttonWidth}px`;tabBg.style.height = `${buttonHeight}px`;背景要素tabBgに対して、上記で取得した相対座標をtranslateのx,yに代入し、位置を反映しています。ここでy座標も反映しているため、ボタンが複数行に並んでいた場合にも、背景色がマグネットのようにスライドして追従します。
またアクティブなボタンの幅と高さをwidthとheightに代入し、反映しています。
背景色を動かす関数を実行
updateTabBackground関数を、初期表示時、ウィンドウリサイズ時、タブボタンクリック時に実行します。これにより動的に背景要素のwidth、height、translateの値が変化するようになります。
function tabControl(event) { // 省略... // タブボタンクリック時 updateTabBackground();}
// 初期表示時updateTabBackground();
// ウィンドウリサイズ時window.addEventListener("resize", updateTabBackground);以上「マグネットのように背景色が移動するタブボタン」の作り方でした。
今回は背景色をアニメーションさせましたが、背景要素のスタイルを調整することで下線(アンダーライン)やドットなど様々な形状に対応できそうです。
