CSS flexboxで実装する、レスポンシブ対応のステップのコンポーネントを解説
Post on:2021年9月16日
Webページで使用されるステップのコンポーネント、サークルに囲まれた1,2,3と各ステップを繋ぐ中央のラインを実装するCSSのテクニックを紹介します。
レスポンシブ対応、そしてライト・ダークモード対応、水平・垂直に配置されたステップだけでなく、数字ではなく日時を繋いだタイムラインにも応用できます。
Building A Stepper Component
by Ahmad Shadeed
下記は各ポイントを意訳したものです。
※当ブログでの翻訳記事は、元サイト様にライセンスを得て翻訳しています。
はじめに
私は先日、レスポンシブ対応のステップのコンポーネントの実装方法とそれらを繋ぐ中央のラインの扱い方について質問を受けました。そのデベロッパーからもらったデモは少し複雑で、不要なCSSがたくさん使用されていました。私はこのことについて記事しようと思いました。
この記事では、ステップのコンポーネントのさまざまなデザインを検討し、それぞれをHTMLとCSSで実装する最善の方法を解説します。準備はいいですか?
では、さっそく見ていきましょう。
ステップのコンポーネントとは、長いコンテンツを関連するコンテンツに分割してグループ化することで、長いコンテンツでも簡単にナビゲートできます。
ステップのコンポーネント
私がCSSを学び始めた頃、これを実装するのは挑戦でした。そして、同じように感じる人が他にいると思います。この記事ではケースバイケースの例を取り上げ、それぞれを解決するための方法を単純化して解説していきたいと思います。
実装: 水平ステップ 1
水平ステップのコンポーネント
上記は、アイテム間にラインが入った水平ステップのコンポーネントです。このコンポーネントの要件は、下記の通りです。
- レスポンシブ
- サイズ変更が簡単
- マジックナンバーはなし
- ライトモードとダークモードの両方で動作
HTMLは、ol要素で実装します。
1 2 3 4 5 6 7 |
<ol class="c-stepper"> <li class="c-stepper__item"> <h3 class="c-stepper__title">Step 1</h3> <p class="c-stepper__desc">Some desc text</p> </li> <!-- 他のステップ --> </ol> |
CSSでは、flexboxを使用してアイテムを水平にレイアウトします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
.c-stepper { display: flex; flex-wrap: wrap; } .c-stepper__item { flex: 1; display: flex; flex-direction: column; text-align: center; } .c-stepper__item:before { --size: 3rem; content: ""; position: relative; z-index: 1; display: block; width: var(--size); height: var(--size); border-radius: 50%; margin: 1rem auto 0; } |
これで下記のように表示されます。
デモのキャプチャ
flexアイテム内のコンテンツは中央に配置され、flex: 1;のおかげで各アイテムは他の兄弟と同じになります。
参考: CSS Flexbox の基礎知識と使い方をやさしく解説
次に、各アイテムの間にラインを追加する方法を検討する必要があります。
1 2 3 4 5 |
.c-stepper__item:not(:last-child):after { content: ""; height: 2px; background-color: #e0e0e0; } |
ラインを疑似要素で実装
明示的なwidthがないのに、疑似要素がどうやって全幅を占めるのか不思議に思うかもしれません。なぜかというと、flexアイテムなので、水平方向のスペースいっぱいに引き伸ばされているからです。
まずは、ラインを上に移動させる必要があります。これはflexアイテムなので、orderプロパティの恩恵を受けることができます。
1 2 3 4 5 6 |
.c-stepper__item:not(:last-child):after { content: ""; height: 2px; background-color: #e0e0e0; order: -1; } |
次に、適切な位置に配置したいのですが、position: absolute;は必要ないので使用しません。
1 2 3 4 5 6 7 8 9 |
.c-stepper__item:not(:last-child):after { content: ""; position: relative; top: 1.5rem; left: 50%; height: 2px; background-color: #e0e0e0; order: -1; } |
上記のCSSについて解説します。
- position: relative;は、ドキュメントフローから外さずにラインを制御することができます。
- topは、円の高さの半分に相当します。
- left: 50%;は、ラインの始点が円の中心から次のアイテムの円の中心まで続くようになります。これにより、各ラインに進捗に応じたカラーをつけることができます。
水平ステップのコンポーネント、とりあえず完成
ステップの前後にスペースがあるバージョン
それぞれの円の周りにストロークを加え、アイテムの下にも同じ背景を入れるか、あるいはもう少し工夫して、ダークモードとライトモードの両方に効果的な実装方法があります。
ステップの前後にスペースがあるバージョン
実装に入る前に、ラインが実際には各円の下に隠れていることをお見せしたいと思います。数字の円のopacityを下げて、見えるようにしました。
実際には、ラインは円の下で繋がっている
これは、ラインにleft: 50%;を使用しているためです。では、左右からのオフセットを与える方法を見てみましょう。
left: 50%;を使用しているから
left: 50%;を使用しているので、親の中心からスタートすることになります。
ラインは親の中心からスタートしている
ラインを円の中心からではなく、円の端から始めるにはcalc()関数で円の半径を加えます。
参考: CSSのcalc()関数を使うとスゴイ便利!ページのレイアウト、要素やフォントのサイズ指定など実装テクニックのまとめ
calc()に円の半径を加える
calc()関数に必要なスペース(ここでは8px)を加えることで、ラインの左側にスペースができます。
さらに、スペース分をcalc()に加える
最後に、反対側のスペースも作成する必要があります。幅は100%なので、円の幅と左右のスペースの値を引き算します。
反対側のスペース分もcalc()に加える
さらに、CSSの変数を利用することで、手動で値を編集することなくサイズを変更することができます。
1 2 3 4 5 6 7 8 9 |
.c-stepper { --size: 3rem; --spacing: 0.5rem; } .c-stepper__item:not(:last-child):after { width: calc(100% - var(--size) - calc(var(--spacing) * 2)); left: calc(50% + calc(var(--size) / 2 + var(--spacing))); } |
変数の値でサイズ変更するのは、下記をご覧ください。
変数の値でサイズ変更
このようにラインを実装することで、効果を偽造するために余計なボーダーなどを実装せずにダークモードで動作させることができます。
上: ライトモードでの表示、下: ダークモードでの表示
実際の動作は、下記でご覧ください。
See the Pen
Horizontal Stepper: Example 1 by Ahmad Shadeed (@shadeed)
on CodePen.
実装: 水平ステップ 2
水平ステップのコンポーネント
上記は、水平ステップ 1と同様にアイテム間にラインが入った水平ステップのコンポーネントです。1との違いはアイテムの横にテキストが加わり、ラインの長さがテキストの長さに応じて変化することです。
1 2 3 4 5 6 |
<ol class="c-stepper"> <li class="c-stepper__item"> <h3 class="c-stepper__title">Step 1</h3> </li> <!-- Other steps --> </ol> |
先ほどの例と同様に、flexboxを使用してアイテムを水平にレイアウトします。1番目と2番目のステップにのみflex: 1;を適用することに注目してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
.c-stepper { display: flex; flex-wrap: wrap; } .c-stepper__item { display: flex; align-items: center; gap: 0.5rem; } .c-stepper__item:not(:last-child) { flex: 1; } .c-stepper__item:before { --size: 3rem; content: ""; display: block; flex: 0 0 var(--size); height: var(--size); border-radius: 50%; } |
1番目と2番目のステップにのみflex: 1;を適用
ラインは、疑似要素で実装します。flex: 1;を使用するので、残りのスペースをラインで埋めることができます。
1 2 3 4 5 6 7 |
.c-stepper__item:not(:last-child):after { content: ""; flex: 1; height: 2px; background-color: #e0e0e0; margin-inline-end: 0.5rem; } |
flex: 1;で、残りのスペースをラインで埋める
ラインを実装するために、.c-stepper__itemでgapとmargin-inline-endという論理プロパティを使用することで、コンポーネントがLTRとRTLの両方で機能させることができます。
LTRとRTLの両方で機能する
実際の動作は、下記でご覧ください。
See the Pen
Horizontal Stepper: Example 2 by Ahmad Shadeed (@shadeed)
on CodePen.
実装: 垂直ステップ 1
垂直ステップのコンポーネント
これは最初の例と似ていますが、方向が垂直です。flexboxを使用してアイテムをレイアウトしてみます。
1 2 3 4 5 6 7 8 9 |
<ol class="c-stepper"> <li class="c-stepper__item"> <div class="c-stepper__content"> <h3 class="c-stepper__title">Step 2</h3> <p>Some desc text</p> </div> </li> <!-- Other steps --> </ol> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
.c-stepper__item { display: flex; gap: 1rem; } .c-stepper__item:before { --size: 3rem; content: ""; position: relative; z-index: 1; flex: 0 0 var(--size); height: var(--size); border-radius: 50%; background-color: lightgrey; } |
次は興味深い部分、つまりラインです。ラインは、コンテンツよりも少し高い位置にあるべきです。どうすれば、固定の高さやマージンを使用せずに実現できると思いますか?
padding-bottomを使用して実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
.c-stepper__item { position: relative; display: flex; gap: 1rem; padding-bottom: 4rem; } .c-stepper__item:not(:last-child):after { content: ""; position: absolute; left: 0; top: 0; transform: translateX(1.5rem); width: 2px; height: 100%; background-color: #e0e0e0; } |
ラインとステップの間にスペースを作るには、水平ステップの実装で使用したのと同じ数式を適用するか、単純にtopとbottomプロパティを使用します。bottomの使用について指摘してくれた@DrMonochromerに感謝します。
padding-bottomを使用する
1 2 3 4 5 6 7 8 9 10 |
.c-stepper { --size: 3rem; --spacing: 0.5rem; } .c-stepper__item:not(:last-child):after { top: calc(var(--size) + var(--spacing)); transform: translateX(calc(var(--size) / 2)); height: calc(100% - var(--size) - calc(var(--spacing) * 2)); } |
これで、ラインとステップの間にスペースを空けることができます。
topとbottomプロパティを使用する
実際の動作は、下記でご覧ください。
See the Pen
Vertical Stepper: Example 1 by Ahmad Shadeed (@shadeed)
on CodePen.
実装: 垂直ステップ 2
垂直ステップ(タイムライン)のコンポーネント
上記はステップのようには見えないかもしれません。どちらかというとタイムラインです。名称に関係なく、これを実装するのにもflexboxを使用します。
まず、HTMLのマークアップは下記のようになっています。要素の順序はデザイン通りではなく、セマンティクスや重要性に応じて実装されていることに注目してください。
1 2 3 4 5 6 7 8 9 10 |
<ol class="c-timeline"> <li class="c-timeline__item"> <div class="c-timeline__content"> <h3 class="c-timeline__title">Paris</h3> <p class="c-timeline__desc">On time</p> </div> <time class="c-timeline__time">10:03</time> </li> <!-- Other items --> </ol> |
基本的なCSSです。
1 2 3 4 5 6 7 8 9 10 |
.c-timeline__item { display: flex; gap: 1.5rem; } .c-timeline__content { order: 1; /* Reorder the content as per the design. */ padding-bottom: 3rem; } |
これで、下記のように表示されます。
基本構造
次に、ラインと円をどこに含めるか決めたいと思います。
私は.c-timeline__contentがその仕事に最適だと思いました。ラインと円は疑似要素で実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
/* The separator line */ .c-timeline__item:not(:last-child) .c-timeline__content:before { content: ""; position: absolute; right: 100%; top: 0; height: 100%; width: 2px; background-color: #d3d3d3; } /* The circle */ .c-timeline__content:after { content: ""; position: absolute; left: -12px; top: 0; width: 20px; height: 20px; background-color: #fff; z-index: 1; border: 2px solid #d3d3d3; border-radius: 50%; } |
ラインと円を疑似要素で実装
次に、time要素の幅を制限する必要があります。また、タイムラインのコンテンツは利用可能なスペースの残りを埋める必要があるため、flex: 1;を使用します。
1 2 3 4 5 6 7 |
.c-timeline__time { flex: 0 0 100px; } .c-timeline__content { flex: 1; } |
次のステップでは.c-timeline__contentのコンテンツを右に、つまりCSSの論理プロパティではendに配置します。
参考: 論理プロパティにおける考え方
1 2 3 4 |
.c-timeline__time { flex: 0 0 100px; text-align: end; } |
endに配置
各アイテムの時間について重要な部分をハイライトしたいと思います。場合によっては、コンテンツをコントロールすることができず、意図的または誤って非常に長いコンテンツを追加してしまうことがあるかもしれません。
デフォルトではflexアイテムはコンテンツの最小サイズよりも縮小しません。.c-timeline__time要素のコンテンツが非常に長い場合、flex: 0 0 100px;であっても下記のように表示されます。
コンテンツが非常に長い場合
これに対応するためには、要素にmin-width: 0;を追加してflexboxを最小コンテンツサイズ未満に縮小するように強制する必要があります。また、長い単語を分割するようにoverflow-wrapも追加します。
1 2 3 4 5 6 7 |
.c-timeline__time { flex: 0 0 100px; text-align: end; min-width: 0; overflow-wrap: break-word; padding-bottom: 1rem; } |
実際の動作は、下記でご覧ください。
See the Pen
Vertical Stepper: Example 2 by Ahmad Shadeed (@shadeed)
on CodePen.
終わりに
この記事があなたのお役に立てれば幸いです。
コメントや提案があれば、@shadeed9までお願いします。
sponsors