デモで学ぶ CSS position: fixed(固定バナー・追従ボタン)
CSS の position で要素を画面に固定する方法をまとめたページ。5つの値の違いから、四隅への配置・画面下部のCTAバー・右端のサイドバナー・z-index の重なり順まで、コピーしてすぐに使えるHTML・CSSのコードを掲載しています。
HTML・CSS・JSコードプレビューツール にて実装確認ができます。
① position の5つの値を比べる — static / relative / absolute / fixed / sticky
枠の中をスクロールしてみてください。
この枠を、ブラウザの画面そのものだと思って見てください。
下にある点線の四角が、紫のボックスの親要素です。
static は初期値で、position を指定していない状態と同じです。
top や left を書いても一切効かず、そのまま流れていきます。
relative は「本来の位置からのズレ」を指定します。
元の場所は空けたまま、見た目だけがずれるのが特徴です。
absolute は親要素の四隅を基準に配置します。
基準になるのは position が static 以外の、いちばん近い祖先です。
fixed は親を無視して、画面そのものを基準に固定します。
sticky は途中まで一緒に流れ、指定位置に来ると止まります。
sticky は親要素の外までは付いてきません。
現在: position: static;
<div class="parent">
<div class="box">box</div>
</div>
.parent {
position: relative;
height: 220px;
border: 2px dashed #94a3b8;
}
.box {
position: static;
}
もっとも混同しやすいのがこの2つです。absolute は「position が static 以外のいちばん近い祖先」を基準にします。上のデモでは点線の親要素が基準になるので、スクロールすると親ごと流れていきます。対して fixed は祖先を一切見ず、画面(ビューポート)そのものを基準にします。だからスクロールしても同じ位置に残り続けます。親要素の中に留めたいなら absolute、画面に貼り付けたいなら fixed と覚えてください。
sticky は「途中までは一緒に流れ、指定位置に来たら止まる」という動きをします。ただし止まっていられるのは親要素が画面内にある間だけで、親の下端まで来ると一緒に流れ去ります。sticky を指定したのに動かない・すぐ消えるという場合は、親要素の高さが足りていないか、祖先のどこかに overflow: hidden が指定されているケースがほとんどです。
position: fixed は画面全体を基準にするため、記事内に埋め込んだ枠の中では本来の動きを再現できません。上のデモは枠を画面に見立てて、枠内スクロールで同じ見え方になるよう作っています。コピペ用コードは実際のページでそのまま使える内容です。
② 四隅と中央下に配置する — top / right / bottom / left
スクロールしても紫のボックスは動きません。
fixed を指定した要素は、top・right・bottom・left で位置を決めます。
4つすべてを書く必要はなく、縦と横で1つずつ選べば決まります。
右上なら top と right、左下なら bottom と left という組み合わせです。
値を 0 にすると画面の端にぴったり貼り付きます。
16px のような余白を入れると、浮いているように見えます。
横方向の中央に置きたいときは、少し工夫が必要です。
left: 50% だけでは、要素の左端が中央に来るだけでズレてしまいます。
そこで transform: translateX(-50%) を足して、自分の幅の半分だけ左へ戻します。
これで要素の中心が画面の中心にそろいます。
中央下はスマートフォンのCTAボタンでよく使われる配置です。
親指が届きやすい位置なので、押してほしいボタンに向いています。
どの配置でも、z-index を指定して他の要素より前面に出すのを忘れないでください。
固定要素を複数置くときは、重ならないように値をずらします。
右下にチャットボタン、その上にトップへ戻るボタン、という組み合わせが典型です。
bottom を 16px と 76px のように離せば、縦に並べられます。
画面が狭い端末では固定要素が本文を覆いすぎるので、数を絞ってください。
ここが最後の行です。
現在: top: 16px; right: 16px;
<div class="fixed-box">position: fixed</div>
.fixed-box {
position: fixed;
top: 16px;
right: 16px;
z-index: 100;
padding: 6px 12px;
border-radius: 6px;
background: #7c3aed;
color: #fff;
font-size: 0.75rem;
font-weight: 700;
}
fixed の要素を横方向の中央に置くとき、left: 50% だけでは中央になりません。50% の位置に来るのは要素の左端なので、要素の幅のぶんだけ右へずれてしまいます。transform: translateX(-50%) を足すと、自分自身の幅の半分だけ左へ戻るため、中心どうしがぴったりそろいます。要素の幅が決まっていなくても使えるので、文字数が変わるボタンでもそのまま機能します。
fixed を指定した要素は、その場所の高さを確保しなくなります。つまり後続のコンテンツは、その要素が存在しないかのように上へ詰まります。これは便利な反面、固定した要素が本文に重なる原因にもなります。画面下部にバーを置く場合の対処は③で扱います。
③ 画面下部に固定するCTAバー — 本文が隠れる問題への対処
いちばん下までスクロールしてみてください。
画面下部に固定したボタンは、常に目に入るため反応率が高くなります。
問い合わせ・購入・予約など、押してほしい導線に向いた配置です。
ただし fixed はドキュメントフローから外れるという性質があります。
つまり、バーの高さぶんの場所を本文側が確保してくれません。
その結果、本文をいちばん下までスクロールすると最後の行がバーの裏に隠れます。
読者からは「文章が途中で切れている」ようにしか見えません。
対処はとても簡単で、本文側にバーと同じ高さの余白を足すだけです。
body や本文のラッパーに padding-bottom を指定します。
バーの高さが 56px なら、padding-bottom も 56px にそろえます。
数値を2か所に書くと片方だけ直し忘れるので、CSS変数にすると安全です。
–bar-height: 56px と定義して、高さと余白の両方から参照します。
上のボタンで余白を付け外しして、最後の行の見え方を比べてみてください。
スマートフォンでは、画面の下端に近いほど親指が届きやすくなります。
バーの高さは、指で押しやすい 48px 以上を目安にすると失敗しません。
文字だけをリンクにせず、バー全体を押せる範囲にしておくと親切です。
ここが最後の行です。隠れていませんか?
現在: 余白なし — 最後の行がバーに隠れます
<main class="page-body">
<p>ページの内容</p>
</main>
<div class="cta-bar">
<a href="/contact/">お問い合わせはこちら</a>
</div>
:root {
--bar-height: 56px;
}
.cta-bar {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
height: var(--bar-height);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
background: #2563eb;
border-top: 2px solid rgba(255, 255, 255, 0.2);
}
.cta-bar a {
color: #fff;
font-size: 0.95rem;
font-weight: 700;
text-decoration: none;
}
fixed の要素は高さを確保しないため、本文の最後の行がバーの裏に回り込みます。本文のラッパーにバーと同じ高さの padding-bottom を指定すれば解決します。値を2か所に直書きすると片方だけ直し忘れるので、–bar-height のような CSS 変数にまとめておくと安全です。バーの高さを変えるときも1か所の修正で済みます。
iPhone の画面下端にはホームバーが表示されるため、bottom: 0 のバーと重なってボタンが押しにくくなることがあります。padding-bottom: env(safe-area-inset-bottom) を足しておくと、その端末でだけ自動的に余白が確保されます。対応していない環境では 0 として扱われるため、そのまま書いて問題ありません。
④ 画面の端に固定するサイドバナー — 縦位置は中央そろえ
スクロールしてもバナーは画面の端に残ります。
サイドバナーは、本文の邪魔をせず常に見えている導線として使われます。
資料請求・お問い合わせ・キャンペーンの入口としてよく置かれます。
横位置は right: 0 か left: 0 で、画面の端にぴったり貼り付けます。
縦位置は top: 50% と transform: translateY(-50%) で中央にそろえます。
top: 50% だけだと要素の上端が中央に来るので、下へずれて見えます。
自分の高さの半分だけ上へ戻すことで、中心どうしがそろいます。
bottom: 20% のように割合で指定する書き方もあります。
この場合は画面の高さに応じて位置が変わるので、縦長の端末では下がります。
気をつけたいのが、スマートフォンでの見え方です。
画面幅が狭いとバナーが本文に重なり、文字が読めなくなることがあります。
メディアクエリで、狭い画面では非表示にするか横向きのバーに切り替えてください。
閉じるボタンを付けておくのも親切です。押されたら display: none にします。
広告色が強いバナーを常に出し続けると、離脱の原因にもなります。
文字を縦に並べたいときは writing-mode: vertical-rl を使う方法もあります。
ただし1文字ずつ要素を分けたほうが、色分けや高さの調整はしやすくなります。
ここが最後の行です。
現在: right: 0; top: 50%;
<div class="side-banner">
<span class="banner-item bx1">資</span>
<span class="banner-item bx2">料</span>
<span class="banner-item bx3">請</span>
<span class="banner-item bx4">求</span>
</div>
.side-banner {
position: fixed;
right: 0;
top: 50%;
transform: translateY(-50%);
z-index: 100;
width: 46px;
display: flex;
flex-direction: column;
}
.banner-item {
height: 46px;
line-height: 46px;
text-align: center;
font-size: 0.95rem;
font-weight: 700;
color: #fff;
}
.bx1 { background: #3b82f6; }
.bx2 { background: #8b5cf6; }
.bx3 { background: #ec4899; }
.bx4 { background: #f59e0b; }
@media (max-width: 768px) {
.side-banner {
display: none;
}
}
横方向の中央寄せと同じ考え方です。top: 50% で来るのは要素の上端なので、そのままだと下へずれて見えます。transform: translateY(-50%) で自分の高さの半分だけ上へ戻すと、中心が画面の中央にそろいます。バナーの項目数が増減しても位置がずれないのが利点です。bottom: 20% のように割合で指定する方法もありますが、こちらは画面の高さによって見え方が変わります。
画面幅が狭い端末では、サイドバナーが本文に重なって文字が読めなくなります。コピペ用コードに入れてある @media (max-width: 768px) のブロックで非表示にするか、③のような横向きのバーに切り替えてください。あわせて閉じるボタンを用意しておくと、必要ないと感じた読者がバナーを消せるようになります。常に消せない広告は離脱の原因になります。
⑤ 固定した要素が他の要素に隠れる — z-index と重なり順
z-index: 10;
紫のボックスは、緑のパネルの前に出ていますか?
fixed は「画面に固定する」指定であって、「前面に出す」指定ではありません。重なり順を決めているのは z-index という別のプロパティです。
z-index を書かないと、後から出てくる positioned 要素のほうが前に来ます。真ん中のボタンで z-index: 100 を付けると、きちんと前面に出ます。
厄介なのが3つ目です。祖先に opacity・transform・filter などが付くと「スタッキングコンテキスト」という独立した箱ができ、その中では z-index をいくら大きくしても箱ごと後ろに回されます。
現在: z-index 未指定 — 後の要素に隠れます
<div class="fixed-box">position: fixed</div>
<div class="panel">本文中のパネル</div>
.fixed-box {
position: fixed;
top: 16px;
right: 16px;
}
.panel {
position: relative;
z-index: 10;
}
z-index が働くのは、position が static 以外の要素(および flex / grid の子要素)だけです。position を指定していない要素に z-index を書いても、まったく無視されます。逆に、重なり順を制御したい相手が positioned 要素なら、こちらにも z-index を指定して勝負する必要があります。数値は大小の比較でしか使われないので、100 と 200 でも 1 と 2 でも結果は同じです。
祖先要素に transform・filter・backdrop-filter・will-change・opacity(1未満)などが指定されていると、そこに独立した重なりの箱(スタッキングコンテキスト)が作られます。その箱の中の要素は、どれだけ z-index を大きくしても箱ごとまとめて前後関係が決まるため、箱の外にある要素を追い越せません。z-index: 9999 にしても前に出ないときは、値をさらに上げるのではなく、祖先にこれらの指定がないかを開発者ツールで確認してください。なお position: sticky を指定した要素も、z-index の値に関係なく必ずスタッキングコンテキストを作ります。
上のデモで使っている transform: translateZ(0) は、GPU で描画を速くする目的でよく足される指定です。ところがこれは重なりの箱を作るだけでなく、子孫の position: fixed / absolute の基準(含みブロック)にもなります。つまり「前面に出ない」と「画面に固定されない」が同時に起きます。アニメーションライブラリやCSSフレームワークが当てていることも多いため、固定まわりで挙動がおかしいときは、まず祖先の transform を疑ってください。
このカードのデモは重なり順を見るためのものなので、スクロールしない枠にしています。紫のボックスは position: absolute で枠の右上に置いていますが、重なり順の決まり方は position: fixed の場合とまったく同じです。コピペ用コードは実際のページでそのまま使える position: fixed 版です。
transform・filter・backdrop-filter・perspective・will-change が指定されていると、その要素が position: fixed の基準(含みブロック)になります。結果として、画面に固定したはずの要素がその祖先の中に閉じ込められ、スクロールで一緒に流れていきます。アニメーション用に付けた transform: translateZ(0) や、CSSフレームワークが当てているスタイルが原因になることが多いので、固定が効かないときは真っ先にここを疑ってください。
position: fixed が効きません。
原因は大きく3つです。1つ目は top / right / bottom / left を1つも指定していないケースで、この場合は本来の位置にとどまったまま固定されるため、動いていないように見えます。2つ目は祖先要素に transform や filter が指定されているケースで、fixed の基準が画面ではなくその祖先に変わってしまいます。3つ目は祖先に overflow: hidden があって、はみ出した部分が切り取られているケースです。開発者ツールで親をさかのぼって確認してください。
fixed と sticky はどちらを使えばいいですか?
最初から最後まで画面に貼り付けておきたいなら fixed、途中までは一緒に流れて、指定位置に来たら止まってほしいなら sticky です。sticky はドキュメントフローに残るため、本文が裏に隠れる問題が起きず、③のような padding での逃がしも不要になります。一方で親要素の外までは付いてこないという制約があります。ヘッダーの固定についてはこちらで詳しく比較しています。
z-index を 9999 にしても前面に出ません。
値をさらに上げても解決しません。祖先要素のどこかに opacity(1未満)・transform・filter などが指定され、そこに独立したスタッキングコンテキストが作られているのが原因です。その箱の中では z-index がいくら大きくても、箱そのものの重なり順を超えられません。対処は、その祖先から該当の指定を外すか、固定したい要素を箱の外(body の直下など)へ移動させることです。⑤のデモで実際の挙動を確認できます。
スマートフォンで固定バナーが本文に重なってしまいます。
画面幅が狭い端末では、サイドバナーは本文の可読性を大きく損ないます。@media (max-width: 768px) { .side-banner { display: none; } } で非表示にするか、③のような画面下部の横向きバーに切り替えるのが定番です。画面下部のバーを残す場合は、本文側に padding-bottom を忘れずに入れてください。あわせて閉じるボタンを用意しておくと、読者が自分で消せるようになります。
iPhone で画面下部のバーが見切れます。
iPhone の画面下端にはホームバーの領域があり、bottom: 0 のバーと重なってボタンが押しにくくなります。padding-bottom: env(safe-area-inset-bottom) を足すと、その領域のぶんだけ自動で余白が確保されます。対応していないブラウザでは 0 として扱われるため、そのまま書いて問題ありません。あわせて <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> の指定が必要です。
当サイトで公開しているWebデザインやUIの実装例は、一覧として以下記事に纏めています。

