マグネットのように背景色が移動するタブボタンの作り方

タブのボタンをカレント表示させる際に、背景色がマグネットのようにスライドして追従するアニメーションをつけてみました。

Chrome 125からサポートされたCSS Anchor Positioningを使用すればCSSだけで実装できそうですが、今回は幅広いブラウザでの対応が必要だったのでJavaScriptを使用しました。

実装例

早速ですが以下が実装例です。

背景色がマグネットのようにスライドするタブ

タブボタンをクリックするとグレーの背景色が磁石のように遅れて追従します。今回はタブボタンは1行ですが、複数行となった場合にも対応しています。

実装のポイント

HTML、CSS、JavaScriptの3つに分けて簡単に解説します。

HTML

HTMLの構造は以下のとおりです。

index.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は以下のとおりです。アニメーションに関連する部分のみを抜き出しています。

style.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プロパティを設定しています。今回はtransformwidthheightの3つのプロパティに対してアニメーションを設定しています。

また基準となる要素を中心に背景要素を移動させるため、親要素のc-tabposition: relative;を設定しています。今回はc-tabに対して設定しましたが、HTMLの構造によって変わってくるので適宜調整が必要です。

JavaScript

基本的なタブの処理は置いておいて、磁石のように追従する部分のコードを中心に解説します。

背景色を動かすために、updateTabBackgroundという関数を作成しています。

script.js
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つの処理を行っています。

  • 背景の位置とサイズを取得する処理
  • 背景の位置とサイズを反映する処理

背景の位置とサイズを取得

script.js
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()widthheightプロパティにアクセスし、アクティブなボタンの幅と高さを取得しています。

背景のサイズと位置を反映

script.js
// 背景の位置とサイズを反映
tabBg.style.transform = `translate(${x}px, ${y}px)`;
tabBg.style.width = `${buttonWidth}px`;
tabBg.style.height = `${buttonHeight}px`;

背景要素tabBgに対して、上記で取得した相対座標をtranslatex,yに代入し、位置を反映しています。ここでy座標も反映しているため、ボタンが複数行に並んでいた場合にも、背景色がマグネットのようにスライドして追従します。

またアクティブなボタンの幅と高さをwidthheightに代入し、反映しています。

背景色を動かす関数を実行

updateTabBackground関数を、初期表示時ウィンドウリサイズ時タブボタンクリック時に実行します。これにより動的に背景要素のwidthheighttranslateの値が変化するようになります。

script.js
function tabControl(event) {
// 省略...
// タブボタンクリック時
updateTabBackground();
}
// 初期表示時
updateTabBackground();
// ウィンドウリサイズ時
window.addEventListener("resize", updateTabBackground);

以上「マグネットのように背景色が移動するタブボタン」の作り方でした。

今回は背景色をアニメーションさせましたが、背景要素のスタイルを調整することで下線(アンダーライン)やドットなど様々な形状に対応できそうです。

この記事を書いた人

写真:ブール 代表 西川雅人

Masato Nishikawa

ウェブ制作会社・デザイナー様向けに、コーディングやWordPress実装を承っているフリーランスです。繁忙期のサポートや、継続的な外注先をお探しでしたら、お気軽にお声がけください。

コーディングや
WordPress実装の
ご相談を承っています。

新規制作から運用フェーズの修正まで柔軟に対応します。
お見積りや実装可否の確認など、お気軽にご連絡ください。