CSSでトランプみたいにカードをカッコよく重ねて並べた~い!
CSS でトランプゲームのようにカードを重ねて並べたくて、地味に試行錯誤したので記録を残しておきます。
Switch の 世界のアソビ大全51 のトランプゲームでの UI を参考にしています。
- 1 列に並べるパターン
- アーチ状に広げるパターン
の 2 種類のサンプルを作りました。
また、「ホバーしたカードを強調させる演出」なんかも試してみました。
このサンプルがすべてではありますが、以降で詳しく説明していきます。
カードを作る
まずはカードを作っておきます。
- 幅
width
を固定値で設定 - 比率を保つために
aspect-ratio
を設定- 今回はブリッジサイズと呼ばれる一般的なトランプのサイズ、 58mm x 89mm の比率を採用 [1]
- 後は適当にスタイルをつける(今回は並べ方がメインなので、カードのデザインはあまり気にしません)
div {
width: 6rem;
aspect-ratio: 58 / 89;
background-color: #c00606;
box-shadow: 0 0 6px black;
border: 0.5rem white solid;
border-radius: 0.5rem;
}
要素を少しずつ重ねて並べる
HTML 要素は基本的に要素の大きさを保って縦積み・横積みされるため、重ねるにはひと工夫が必要です。
今回は以下の条件を想定します。
- コンテナ(親要素)の幅に収まるように、枚数に応じて重なり幅を変えて配置する
- 最小の重なり幅を設定する(カード枚数が少ないときでも多少重なるようにする)
HTML 構造としては以下のように、li
の中に div
(カード)を配置します。
(フレックスアイテムと width
を設定したカードの要素が、異なる要素であることが重要です)
<ul>
<!--
フレックスアイテムとカード自体が親子になるように、
liの中にdiv(カード)を配置する
-->
<li><div></div></li>
<li><div></div></li>
<li><div></div></li>
</ul>
flex
レイアウトを使用することで、子要素の幅が狭まるように設定します。
ただし、親を flex
にするだけでは li
が狭くなってくれません。div
(カード)の大きさを保とうとしてしまうためです。
そこで li
に min-width: 0;
を設定して、縮むようにしてあげます。
すべての子要素が縮むようにしてしまうと、最後の 1 個が親要素の範囲からはみ出してしまうので、最後の 1 個だけは幅を保持するようにします。
ul {
display: flex;
justify-content: center;
max-width: 24rem;
}
li:not(:last-child) {
/* :not(:last-child) に適用することで、基本的にすべて縮めて、最後の1枚だけ幅を保持する */
min-width: 0;
/* 最小の重なり幅。枚数が少ないときでもこの幅は重ねる */
margin-right: -2rem;
}
以下のように最後の 1 枚だけがカードの幅をそのまま保持し、それ以外は均等に狭い幅が割り当てられます。
わかりやすさのために、一旦枠だけ確認すると以下のスクリーンショットのような配置になります。
カードには幅を固定値指定しているため、 li
の領域をはみ出して配置され、結果として重なって配置されます! 🎴🎴🎴
ホバーしたときにカードを強調する
ホバーしたときに、カードを抜き出して表示する演出を追加してみます。
- 他のカードを押しのけて、見える領域を大きくする
- 少し上に移動させる
li {
/* ホバー時アニメーション */
transition-property: flex-shrink, translate;
transition-duration: 0.2s;
/* ホバーしたカードが前面に出ないようにする */
isolation: isolate;
}
li:hover {
flex-shrink: 0;
translate: 0 -1rem;
}
「他のカードを押しのけて、見える領域を大きくする」ために、flex-shrink
を 0
に設定します。
これは min-width: 0;
で縮めた幅を元に戻すことで、このカードに割り当てられる領域を広げるためです。
min-width: auto
には transition
が効かないため、flex-shrink
を使っています。
「少し上に移動させる」ために、translate
を使っています。
translate
(transform
)によってスタッキングコンテキストが生成されて、他のカードよりも前面に表示されてしまいます。これを避けるため、すべての li
に isolation: isolate;
を設定してあらかじめすべての li
にスタッキングコンテキストを生成しておきます。
(逆に、前面に表示したい場合はこれを削除すれば OK です)
アーチ状に広げる
アーチ状に広げるには、三角関数(sin
, cos
)を使います。
CSS の三角関数の活用は ICS MEDIA さんの記事が参考になります。
基本的な使い方の説明は以下の記事を参照してください。
それぞれのカードをどのくらい傾けるか計算するため、「全体が何枚であるか」と「それぞれが何枚目であるか」の情報が必要になるので、CSS 変数を与える必要があります。
HTML 上で各要素に style
属性で渡します。
(テンプレートエンジンや JS フレームワークを使っている場合はループ変数を使って渡すと良いでしょう。)
<ul style="--length: 3;">
<li><div style="--index: 0;"></div></li>
<li><div style="--index: 1;"></div></li>
<li><div style="--index: 2;"></div></li>
</ul>
CSS では以下のように、CSS 変数を使って計算します。
div {
/* アーチ状に並べる */
--degree: calc((var(--index) - ((var(--length) - 1) / 2)) * 6deg);
rotate: var(--degree);
translate: 0 calc((1 - cos(var(--degree))) * 300%);
}
--degree
は、そのカードをどのくらい傾けるかを計算しています。
(var(--length) - 1) / 2
で中央のカードのインデックスを求めています。
それを引いているので、中央のカードが 0deg
になり、端に行くほど傾きが大きくなります。
カードが 1 枚並ぶごとに 6deg
ずつ傾けています(カードの間隔に合わせて目分量で設定)。
傾けるだけではアーチ状にならないので、外側に行くほど下にずらす必要があります。
ここで三角関数でどれだけずらすか計算します。
カードを円周上に配置することを想定し、直線上に並べたときの y 方向の位置との差を計算して、y 方向にtranslate
する距離を求めます(厳密には x 方向にもずらすべきですが、そこまで見た目に影響ないのでここでは無視します)。
単位円を想定すれば、ずらす距離は 1 - cos(var(--degree))
(下図の
下の図は計算式に至るイメージ図です。 --degree
に対応します。
円の半径をカードの大きさに適したサイズとするため、最後に 300%
(カードの縦幅の 3 倍)をかけます(目分量で 300%
という適当な値を入れています)。
まとめ
CSS でトランプのようにカードを重ねて並べる方法を紹介しました。
よければ Web サイトの表現や演出で使ってみたりしてください 🃏🎴🃁
Discussion