JavaScript不要!スクロールに応じて要素を固定するCSS「position:sticky;」の使い方

しばらくブログを書くタイミングが無かったので久しぶりの更新です。書き方を忘れないうちに更新しておこうと思います。今回はちょっと前に見たCSS position:sticky;のご紹介です。

スポンサードリンク

position:sticky;とは

一般的にスクロールに応じた要素の固定といえばposition:fixed;を使ったり、jQuery(JavaScript)などを使用してスクロール位置を取得してアレコレするイメージですよね。

そんなときはposition:sticky;を使ってみましょう。CSSのみでスクロールに応じて固定・固定解除ができるようになります。relativefixedの中間のような値です。

ブラウザ対応状況

残念ながらIE・Edge・Operaは非対応

ブラウザ対応状況はCan I useから確認すると、IEとEdgeとOpera Miniは全滅、Chromeはバージョン56から、Androidは5.0から対応。モダンブラウザに絞れる案件なら普通に使えそうなCSSですね。

デモ・ソースコード

言葉で説明しづらいのでサンプルページを作成してみました。まずはこちらをご確認ください。右上にある「EDIT ON CODEPEN」を押すと別窓表示されます。

うまく見れないという方に向けてgifアニメを作りました。

このようにスクロール位置が見出しを超えると上部にピタッと固定されます。現在使用しているテーマでも同様に、見出しとサイドバーの目次を上部に固定表示にしています。

固定されるタイミングはスクロール位置がposition:sticky;を指定した要素の位置を超えるときで、固定要素を内包する親要素の範囲まで固定が継続します。

今回のサンプルではsectionの中にh2タグを入れているので、親要素のsectionの高さ分だけ固定されます。サイドバーにある広告(を模した画像)や目次も固定されます。

このブログのように固定されないウィジェットの中に、途中から現れる目次だけ固定するといった使い方も可能です。

ソースコード・解説

説明しやすいようにコードを簡略化しています。全体のソースを確認したい時は、先ほどのデモからhtml・SCSSのタブを開いて確認して下さい。


<div id="contents">
	<main>
		<section>
			<h2 class="sticky">見出しテキスト</h2>
			<p>本文本文本文本文~~~~~~~</p>
		</section>
		<section>
			<h2 class="sticky">見出しテキスト</h2>
			<p>本文本文本文本文~~~~~~~</p>
		</section>
		<section>
			<h2 class="sticky">見出しテキスト</h2>
			<p>本文本文本文本文~~~~~~~</p>
			<table>
				<thead class="thead-sticky">
					<tr>
						<th></th>
						<th>音声通話プラン</th>
						<th>データSMSプラン</th>
						<th>データ専用プラン</th>
						<th>音声通話プラン</th>
						<th>データSMSプラン</th>
						<th>データ専用プラン</th>
						<th>音声通話プラン</th>
						<th>データSMSプラン</th>
						<th>データ専用プラン</th>
					</tr>
				</thead>
				<tbody>
					<tr>
						<th class="th-sticky">3GB</th>
						<td>1,600円</td>
						<td>1,050円</td>
						<td>900円</td>
						<td>1,600円</td>
						<td>1,050円</td>
						<td>900円</td>
						<td>1,600円</td>
						<td>1,050円</td>
						<td>900円</td>
					</tr>
					<tr>
						<th class="th-sticky">7GB</th>
						<td>1,600円</td>
						<td>1,750円</td>
						<td>1,600円</td>
						<td>1,600円</td>
						<td>1,750円</td>
						<td>1,600円</td>
						<td>1,600円</td>
						<td>1,050円</td>
						<td>900円</td>
					</tr>
					<tr>
						<th class="th-sticky">13GB</th>
						<td>3,500円</td>
						<td>2,950円</td>
						<td>2,800円</td>
						<td>3,500円</td>
						<td>2,950円</td>
						<td>2,800円</td>
						<td>1,600円</td>
						<td>1,050円</td>
						<td>900円</td>
					</tr>
					~~~~~~~~
				</tbody>
			</table>
		</section>
	</main>
<!-- /#contents --></div>

見出しごとにsection要素で囲まれてその中にh2pタグ、tableなどが入っています。


/* ↓スクロールに応じて要素を固定↓ */
.sticky {
    position: -webkit-sticky; /* Safari用 */
    position:sticky; /* 要素をスクロールに応じて固定 */
    top:0; /* 縦スクロールに追従 */
    z-index: 100; /* z-indexで最前面に持ってくる */
}
.thead-sticky {
    position: -webkit-sticky; /* Safari用 */
    position: sticky; /* 要素をスクロールに応じて固定 */
    top: 0; /* 縦スクロールに追従 */
    z-index: 3; /* z-indexで前面に持ってくる */
}
.th-sticky {
    position: -webkit-sticky; /* Safari用 */
    position: sticky; /* 要素をスクロールに応じて固定 */
    left: 0; /* 横スクロールに追従 */
    z-index: 2; /* z-indexで前面に持ってくる */
}

そして今回登場するのがposition:sticky;です。このタグはSafari(iOS含む)だとベンダープレフィックスが必要です。

position:fixed;と同じように、固定する要素とスクロール位置を比較する場所を数値で指定します。縦スクロールを基準に固定したいならtopやbottomを、横スクロールを基準にしたいときはleftやrightを指定して下さい。

今回のサンプルではtable内の行列にあるtr要素をそれぞれ固定してみました。こうすることでExcelのような固定表示が可能です。

z-indexは固定した要素が後ろに隠れてしまわないように、数値を大きくして前面に持ってきています。これでスクロール位置が指定要素の位置を超えた時に固定されます。

あとはお好みに応じてスタイルを調整すればOK。サンプルではSCSSを使って記述しています。


h2 {
	padding: 1em 1.25em;
	line-height: 1.25;
	border-top:2px solid $red;
	box-shadow:0 1px 4px rgba(0,0,0,0.3);
	background-color: #fff;
}
table {
	width: 100%;
	max-width: 680px;
	height:300px;
	overflow: auto;
	white-space: nowrap;
	display: inline-block;
	overflow-x: auto;
	table-layout:fixed;
	box-sizing:border-box;
	tr {
		th, td {
			position:relative;
			padding:1em;
			text-align:center;
			vertical-align:middle;
			border:1px solid #dedede;
			box-sizing:border-box;
		}
		th {
			background-color: #faebeb;
		}
	}
	tbody {
		tr:hover {
			th { background-color: #fdd8d8; }
			td {
				background-color: #f2f2f2;
				&:hover { background-color: #dedede; }
			}
		}
	}
}

見出しタグを打つたびにsection要素で囲むのが面倒くさいと思う方はjQueryで解決しましょう。次の見出しが来るまでの範囲をsectionタグで囲ってくれます。


$(function(){
	// h2から次のh2の間をsectionで囲む
	$('h2').each(function(i){
		$(this).nextUntil('h2').add(this).wrapAll('<section></section>');
	});
});

これだと全てのh2を囲ってしまうので、サイトのhtml構造に合わせてセレクタは適宜変更して下さい。

不具合について

  • 固定したい要素を内包する親要素、先祖要素にoverflow:hidden;が入っていると効かない
  • border-collapse: collapse;との相性が悪い
  • Firefox、Operaではtable内のセル固定が効かない

このようにいくつかバグが存在するため、治るのを待つか諦めてJSを使って実装するかしたほうが良さそうです。IEとEdgeが完全非対応なので実案件で使うには割り切りが必要です。

とはいえ、JSを使わずにここまで複雑な処理をこなせるようになったとは驚きですね。

これをもしJSで対応するとしたら、ウィンドウと要素の高さやスクロール位置などをリアルタイムに取得してアレコレする必要があるので処理が重くなってしまいます。

要素が固定されると鬱陶しく感じるかもしれないので、本当に必要なところか考えて固定させておきましょう。広告などを固定する際は規約に違反していないか事前に確認しておきましょう。

この記事は以下の記事を参考にして書きました。

鹿
writer : 鹿
このブログを管理している鹿。Webデザインとガジェットが好き。
  • Feedly(RSS)で
    ブログを購読してみる
    購読する
  • Push7(プッシュ通知)で
    ブログを購読する
    購読する