FlexboxとCSSアニメーションを使ったSF風カードデザインの作り方
今回は、SF映画で出てくる様な光沢のあるアニメーションパネルの紹介カードをHTMLおよびCSSで設計します。
本記事を通してFlexboxの基本やネストされたFlexbox、CSSアニメーション、境界線、シャドウ、その他使用されるCSSプロパティについておさらいしましょう。
FlexboxとCSSアニメーションを使ってSF風カードデザインを作る
本記事では、SF映画で出てくる様な光沢のあるアニメーションパネルの紹介カードをHTMLおよびCSSで設計します。
今回おさらいして欲しい事
Flexboxを使用して目的のレイアウトを作成し、さらにFlexboxをネストして使用します。
またCSSでSF風の弧を作成するため、丸く透明な境界線を使用し、画像の周りでそれらを回転させます。
最後にbox-shadowとtext-shadowプロパティを使用して、IDカードに最終的なSF風の装飾を加えます。
目標とする完成形
最終的な自己紹介カードは次のようになります。
LPEG
HP:https://lpeg.info/
Mail:contact@lpeg.info
Twitter:https://twitter.com/lpeg_info
背景づくり
ページ背景の作成
まずは紹介カードを配置する黒バックのdivエリアを作成します。
htmlソース:背景枠
<div class="card-wrapper"> </div>
上記divにページ全体をカバーさせ、背景をSF風に暗くします。
CSSスタイル
.card-wrapper { height: 50vh; //注意 width:100%; background-color: #122023; }
なぜ要素の高さに%ではなく「vh」を使用するのか
よく見ると高さは「100%」ではなく「50vh」を使用している事がわかります。
vh単位とは「ビューポートの高さ」の略で、ビューポート単位には他にvw、vminやvmaxなどがあります。
ではなぜ高さに%ではなく、vhを使用する必要があるのでしょうか?
%ベースの要素はその親要素に関連する
htmlページのbody直下にこの自己紹介タグを設置した場合を想定しましょう。
card-wrapperの高さを100%に設定した場合、card-wrapperの高さは親要素であるbody要素の高さの100%になります。
問題はbody要素がデフォルトでは画面全体の高さをカバーしていない事です。
bodyは初期値の高さは100%ではない
body要素の「幅」はデフォルトは100%なので、card-wrapperの幅にwidth: 100%が使用できます。
しかし「高さ」はデフォルトで100%ではないので、幅の時と同じ様にはいきません。
それに対し、ビューポートの単位は親要素ではなく「画面の表示領域」を基準にしています。
高さをvhで設定すれば、要素は親のサイズに関係なく表示領域(ビューポート)の高さ全体を基準にする訳です。
flexboxを使用した上下左右のセンタリング
レイアウト枠の作成
背景枠が準備できたら、次は紹介カードを構成する枠を作成しましょう。
文章欄は「LPEG」とだけ入力しておきます。
htmlソース:レイアウト外枠
<div class="card-wrapper"> <div class="card"> LPEG </div> </div>
外枠にCSSスタイルを追加します:
CSSスタイル
.card { border: 2px solid #0AE0DF; max-width: 30em; margin: auto; color: #fff; padding: 1em; }
cardのborderプロパティに2pxの実線色枠を付けています。
そして「div」はブロック要素であるため、デフォルトでは常に親要素の幅全体をカバーします。
しかし今回は一定の幅にしたいので、max-widthプロパティを「30em」に設定しています。
画面幅にemを使用
これはブラウザのフォントサイズに注意した設定です。
フォントサイズが14pxで30emの領域を設定すると、使用される幅は30em×14pxで420pxになります。
フォント1文字を表示するために1emが使われる事を考慮して、画面幅が決まります。
コンテンツ幅に応じて幅は可変させる事
では別のユーザーがデフォルトのフォントサイズが16pxの別のブラウザでこのコンテンツを表示した場合どうなるでしょうか。
14pxの時と比べてコンテンツ内により多くの文字表示スペースが必要になります。
もしdivの幅がpxで固定されていると、コンテンツがはみ出してレイアウトを崩してしまうのです。
サイズは任意のピクセルに固定するのではなく、コンテンツに合わせて拡大縮小できる様に構築する事を心がけましょう。
flexboxではmargin: autoで垂直方向に中央揃えできる
ブロック要素はmargin: autoを使用すれば水平方向に中央揃えできますが、基本的に垂直方向の中央揃えはできません。
上記の様に通常のレイアウトモードでは縦方向の中央揃えはできない
ただしFlexboxなどの新しいレイアウトモードではmargin: autoで垂直方向の中央揃えが可能です。
CSSスタイル
.card-wrapper { height: 50vh; width:100%; background-color: #122023; display: flex; //ここを追加 }
要素がflex-itemであれば、margin: autoにより要素を水平方向と垂直方向に対して中央揃えにできます。
要素にdisplay: flexを設定すると、その子となる要素は上下方向もセンタリングできる事を覚えておきましょう。
flex-itemのwidth初期値は「0」
さて要素を中央に配置しましたが、代わりにそれまであった要素の幅が消えています。それは何故でしょうか?
これは親要素がflexになった事で、子要素のdivがflex-itemとしてflexboxのコンテキストに入ったためです。
flex-itemは、divにwidthプロパティが設定されていない場合は初期幅を「0」に設定するルールです。
max-widthを指定していますが、これは判定には入りません。
修正方法
そこで子要素にflex-basisプロパティを使用して要素の初期幅を定義すると、幅を戻せます。
CSSスタイル
.card { flex-basis: 100%; //ここを追加 border: 2px solid #0AE0DF; max-width: 30em; margin: auto; color: #fff; padding: 1em; }
「flex-basis」を使用してflex-itemの幅を100%に設定した状態
flexboxのネスト
これでcard要素のコンテンツ枠の準備ができました。
次にfaceとdescの2つのセクションをprofile-row要素で囲って、横並びで入れます。
flexboxを入れ子にして実現していきます。
flexboxの中にさらなるflexbox要素を
face要素にはプロフィール写真画像、desc要素には紹介文が入ります。
htmlソース
<div class="card-wrapper"> <div class="card"> <div class="profile-row"> <div class="face"> 画像 </div> <div class="desc"> LPEG </div> </div> </div> </div>
flex-itemは直接の子孫のみ
cardがflex-itemである事は説明しましたが、次に作成したprofile-rowはflex-itemではありません。
flexboxのルールでは、フレックスコンテナの直接の子孫である「card」だけがflex-itemになり、それ以下は子孫にならないのです。
子孫を離れた要素にflexboxを設定
profile-rowは通常のdiv要素なので、さらにdisplay: flexを設定します。
これによりその子要素であるface要素およびdesc要素がflex-itemになる訳です。
この状態をflexboxのネスト(入れ子)といいます。
CSSスタイル
.profile-row { display: flex; }
flex-basisプロパティを使用し、faceに40%の幅、descに60%の幅で横並びを実現していきます。
.profile-row .face { flex-basis: 40%; } .profile-row .desc { flex-basis: 60%; }
「flex-basis」プロパティを使用して、各要素が占める幅スペースを定義した状態
プロフィール画像の設置
ではface属性の方に画像を設置します。
プロフィール画像にはimgタグを使って自由に写真を入れて下さい。
画像タグの設置と装飾
htmlソース
<div class="card-wrapper"> <div class="card"> <div class="profile-row"> <div class="face"> <img src="../images/css_flexbox_card_img2.jpg" alt="プロフィール名"> </div> <div class="desc"> LPEG </div> </div> </div> </div>
そして画像にCSSスタイルを設定します。
CSSスタイル
.profile-row .face img { max-width: 100%; border-radius: 50%; } .profile-row .desc { padding: 1em; }
imgタグへの設定
ここで使う画像の幅は常にfaceの幅と同じにしたいので、imgの最大幅をmax-width: 100%に設定しています。
またborder-radiusプロパティを50%に設定しています。画像を円形で表示するためです。
内側の弧の作成
次に画像上に2つの弧の要素を挿入して、さらにその周りを回転させる設定をします。
内側の弧要素の作成
まずは2つのうち、一つ目の弧を入れます。
htmlソース
<div class="card-wrapper"> <div class="card"> <div class="profile-row"> <div class="face"> <div class="face-arc-inner"></div> <img src="../images/css_flexbox_card_img2.jpg" alt="プロフィール名"> </div> <div class="desc"> LPEG </div> </div> </div> </div>
CSSスタイル
.profile-row .face { flex-basis: 40%; position: relative; } .profile-row .face img { max-width: 100%; border-radius: 50%; display: block; } .profile-row .face .face-arc-inner { position: absolute; width: 100%; height: 100%; border: 6px solid #0AE0DF; top: -6px; left: -6px; }
弧部分を絶対配置で実現
上記ではface-arc-inner要素に、position: absoluteの絶対配置を入れました。
要素にabsoluteのposition設定をすると、その要素はドキュメントの通常のフローから除外されるため必要に応じた位置設定ができます。
これによりleft、top、bottom、rightの位置を自由に設定できます。
注意
親要素に「relative」を設定する事
left、top、bottom、rightなどの各特性は、「relative」配置である階層の最初の親要素に対するものです。
そのため今回はface要素に「position: relative」を設定しています。
inline-blockの隙間を消す
img要素に「display: block」も設定していますが、それには理由があります。
img要素は「inline-block」がデフォルトであり、inline-block要素はその側面と底面に小さな隙間を生み出すためです。
通常この程度では問題はありませんが、今回は画像の周りに少しでも隙間があると「2つの弧」の表現位置が微妙にずれます。
ですのでimg要素に「display: block」を設定している訳です。
border部分を円弧に変化
次に、face-arc-inner要素を円弧のよう変化させます。
CSSスタイル
.profile-row .face .face-arc-inner { position: absolute; width: 100%; height: 100%; border: 6px solid transparent; border-top-color: #0AE0DF; border-radius: 50%; top: -6px; left: -6px; }
ここで全ての境界線に色を付けるのではなく、「border-top」のみに色を設定しています。
次にborder-radiusを50%にして丸めています。
弧の位置を調整するマイナスマージンは、あなたの環境に合わせてください。
内側の弧が入った状態
外側の弧の作成
では、face-arc-outer要素でもう1つの外側の弧を作成しましょう。
外側の弧要素の作成
htmlソース
<div class="card-wrapper"> <div class="card"> <div class="profile-row"> <div class="face"> <div class="face-arc-outer"></div> <div class="face-arc-inner"></div> <img src="../images/css_flexbox_card_img2.jpg" alt="プロフィール名"> </div> <div class="desc"> LPEG </div> </div> </div> </div>
CSSスタイル
.profile-row .face .face-arc-outer { position: absolute; width: calc(100% + 12px); height: calc(100% + 12px); border: 6px solid transparent; border-bottom-color: #0AE0DF; border-radius: 50%; top: -12px; left: -12px; }
calcについて
Calcは与えられた数式を計算するCSS関数です。
先ほど幅と高さが100%で枠が6pxの内側の円弧を作りましたよね。
これにより内側の弧の全幅は「左端6px + 画像幅100% + 右端6px」になってます。
%に対して幅を自動計算
ここで外側の弧の位置は内側の弧より6px遠くにするので、幅・高さ共に「100% + 12px幅」を付けています。
これを実現するのにcalc()機能が役立ちます。
またborder-bottom-colorの色付き部分を下部に表示したいので、border-bottomに使用しています。
calc関数で12pxを追加して弧と画像の間に6pxの間隔を作成した状態
弧を回転させる
2つの弧の準備ができたので、それぞれ画像の周りを回転させましょう。
CSSアニメーションを使用してこれを行うことができます。
内側の弧のアニメーション
アニメーションには3つの基本的なものが必要です。
・アニメーションの開始状態
・アニメーションの最終状態
・開始状態から終了状態までにかかる時間(アニメーションの速度)
これらのデータは次のようにCSSで提供できます。
CSSスタイル
.profile-row .face .face-arc-inner { position: absolute; width: 100%; height: 100%; border: 6px solid transparent; border-top-color: #0AE0DF; border-radius: 50%; top: -6px; left: -6px; animation-duration: 2s; animation-name: rotate-clock; } @keyframes rotate-clock { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
上記の様に各項目を追記して状態を確かめてみて下さい。
アニメーション設定の説明
@keyframes
アニメーションの定義には@keyframesを使用します。
fromそしてtoは、アニメーションの開始と終了状態を設定するために使用されています。
開始状態(from)…transform: rotate(0deg)、弧を0度回転させる
終了状態(to)…transform: rotate(360deg)、弧を360度回転させる
アニメーション名の設定
その際、@keyframesに「rotate-clock」という名前を設定し、使いたい要素のanimation-nameプロパティにその名前を指定する必要があります。
またアニメーションが完了するまでにかかる時間も設定する必要があります。
そこでanimation-durationプロパティを「2s」に設定します。
追加修正
実際に見てお分かりと思いますが、今回の出力には2つの問題があります。
1つ目:円弧が1回だけ回転して止まる事
2つ目:アニメーションが等速でない事
アニメーションの最初と最後が速い(イージングしている)ので、終始同じ速度になる様にします。
これらの問題を解決するためのプロパティ設定が以下です。
animation-iteration-count…アニメーションを無限に繰り返す
animation-timing-function…常に同じ速度で回転
CSSスタイル
.profile-row .face .face-arc-inner { position: absolute; width: 100%; height: 100%; border: 6px solid transparent; border-top-color: #0AE0DF; border-radius: 50%; top: -6px; left: -6px; animation-duration: 2s; animation-name: rotate-clock; animation-iteration-count: infinite; //追加 animation-timing-function: linear; //追加 }
これで内側の弧は想定通りに回転しているはずです。
外側の弧のアニメーション
次に外側の円弧も同じ方法で、今度は反対方向に回転させましょう。
CSSスタイル
.profile-row .face { flex-basis: 40%; position: relative; margin: 24px; } .profile-row .face .face-arc-outer { position: absolute; width: calc(100% + 12px); height: calc(100% + 12px); border: 6px solid transparent; border-bottom-color: #0AE0DF; border-radius: 50%; top: -12px; left: -12px; animation-duration: 2s; animation-name: rotate-anticlock; animation-iteration-count: infinite; animation-timing-function: linear; } @keyframes rotate-anticlock { from { transform: rotate(0deg); } to { transform: rotate(-360deg); } }
外側の弧には正の360度ではなく負の360度に設定し、反時計回りのアニメーションにします。
実際にコードを追記して結果を確認してみて下さいね。
Googleフォントの使用
次に右欄の説明文がよりSF風になる様「sci-fiフォント」を使用して装飾をしてみましょう。
今回は、Google Fontsの「Orbitronフォント」を選んでみます。
Google Fontsの読込
Google Fontsは無料で豊富なフォントを提供してあり、今回選択するフォントは今回の装飾に非常によくマッチしています。
今回は、Orbitronを使っています。
対象となるフォントの右欄にある「Select this style」をクリックします。
右に表示されるパネルから「Embed」をクリックするとlinkコードが表示されます。
<link href="https://fonts.googleapis.com/css2?family=Orbitron&display=swap" rel="stylesheet">
コードをコピペして、このギミックを掲載するページヘッダー(cssやfontを読み込んでいる部分)に記述します。
次にdesc内に説明文を追加します。
htmlソース
<div class="card-wrapper"> <div class="card"> <div class="profile-row"> <div class="face"> <div class="face-arc-outer"></div> <div class="face-arc-inner"></div> <img src="../images/css_flexbox_card_img2.jpg" alt="プロフィール名"> </div> <div class="desc"> <h1>LPEG</h1> <p>HP:https://lpeg.info/<br> Mail:contact@lpeg.info<br> Twitter:https://twitter.com/lpeg_info</p> </div> </div> </div> </div>
CSSではfont-familyにGoogle Fontsのコードを使用して、「LPEG」欄のフォントを設定します。
CSSスタイル
.profile-row .desc { font-family: 'Orbitron', sans-serif; //読み込んだフォントを設定 color: #a4f3f2; } .profile-row .desc h1 { margin: 0px; color: #fff; }
h1のmarginを0にしている
今回は基本的に見出しh1要素の上部と下部に間隔は必要ないので、h1要素のmarginを0にしているだけです。
出力を見てみましょう:
これで文字は確かにSF風の書体になっています。
しかし問題が発生しましたね。desc要素の高さが広がったことで弧の回転がずれています。
高さが変わると連動するflex-item
通常レイアウトの場合、desc要素のコンテンツの高さがface要素より高くなっても、db要素の方に変化はありません。
ただしFlexboxレイアウトの場合では、desc要素の高さが変わるとface要素の高さも連動して増加してしまいます。
align-self属性の追加
これを制御するため、次の様にflexboxのサブプロパティ「align-self」を使用して、descに連動してface要素の高さが増えないようにします。
CSSスタイル
.profile-row .face { flex-basis: 40%; align-self: center; //追加 position: relative; margin: 24px; }
align-self: centerにより、face要素はdescの紹介文の高さに関係なく同じ高さを維持しています。
グロー(光彩加工)の追加
グローとは光彩、つまり光る様な装飾の事ですね。
実は上2つのサンプル画像ではCSSスタイルを入れていたため、すでに文字に光沢加工がされています。
サンプルのような光沢を付ける方法をご紹介します。
光る装飾をCSSで代用する
CSSには光沢のプロパティがありませんので、代わりにtext-shadowプロパティで代用します。
シャドウに明るい色を付ければ、シャドウが光っているように見えるのですね。
CSSスタイル
.profile-row .desc { font-family: 'Orbitron', sans-serif; color: #d3f8f7; text-shadow: 0px 0px 4px #12a0a0; //追加 letter-spacing: 1px; }
text-shadowプロパティ
最初の値「Xオフセット」…テキストからX方向に影がどれだけ離れるか
2番目の値「yオフセット」…、テキストからy方向に影がどれだけ離れるか
3番目の値…シャドウに与えるぼかしの量
4番目の値…影の色
「LPEG」欄のテキスト間隔が少し狭い様に見えるため、letter-spacingプロパティを使用してテキストの文字間に1pxのスペースも追加しています。
次にレイアウト枠自体と画像にも光沢(影)を追加します。
CSSスタイル
.card { flex-basis: 100%; max-width: 30em; border: 1px solid rgb(97, 245, 245); margin: auto; color: #fff; padding: 1em; background-color: #0D2C36; box-shadow: 0px 0px 3px 1px #12a0a0, inset 0px 0px 3px 1px #12a0a0; } .profile-row .face img { max-width: 100%; border-radius: 50%; display: block; box-shadow: 0px 0px 4px 3px #12a0a0; }
box-shadowプロパティ
text-shadowプロパティはテキストに影を与えるのに対し、box-shadowプロパティは要素に影を与えます。
box-shadowの最初の3つの値は、text-shadowと同じです。
4番目の値は影の広がり具合であり、そして5番目の値はシャドウの色になります。
内側と外側で影設定を重ねる
cardの紹介枠には、一つの要素に対し2つの影設定(外側と内側)を入れています。
box-shadow: 0px 0px 3px 1px #12a0a0, inset 0px 0px 3px 1px #12a0a0;
最初の5つの値は外側の影を作成します。
「,」で区切って「inset」を入れ、内側の影用に5つの値を指定します。
文字に光沢が入った状態
完成
これで完成です。
下のサンプルは文字の大きさの関係上、.cardのmax-widthを30emから40emに変更しています。
LPEG
HP:https://lpeg.info/
Mail:contact@lpeg.info
Twitter:https://twitter.com/lpeg_info
以上SF風な紹介文と画像による紹介カードの作り方を紹介しました。
flexboxによるレイアウト構築およびネスト、そして円弧を回転させるCSSアニメーション、SF風のフォント設定など、いずれも参考になればと思います。
スマホ向けデザインへのレスポンシブ対応
ここまで作った紹介カードですが、スマホ版では画像と紹介文の横並びを解除するのが理想です。
ですのでmediaクエリでスマホ画面の場合のCSSレイアウトを追加します。
この時、faceやdescなどflex-item化した要素のパーセンテージをそれぞれ100%等にすれば横並びが解除されると思うでしょうが、実際には解除されません。
親要素のflex解除
今回についてはそうではなく、親要素(ここではprofile-row)のflexbox設定を解除する事で対応します。
@media only screen and (max-width: 767px) { .profile-row { display: block !important; //flexをblockに変更 } .card-wrapper { height: 80vh; //背景をスマホ版向けに伸ばしている } }
他にflex-basic以外のプロパティなどを使ったレスポンシブルデザイン対応手順もあります。