【謹賀新年🎍】寅の絵を描いてCSS初めしよう!
あけましておめでとうございます🎍🐯🌅🙏✨
今年は寅年ということで寅のイラストでCSS初めしてみたので、描いたイラストとどうやって描いたのかを簡単にご紹介したいと思います。
CSSで描いた寅のイラスト
早速こちらが今回描いた寅のイラストになります。こちらのイラストを参考にして可愛い感じにしてみました。全てHTMLとCSSだけで描いています。
友人に見せたところベネッセのトラみたいだと言われた
環境
MacBookPro + Chromeで動作確認しています。(a11yやレスポンシブには配慮していません。)
ソースコード
ちょっとイラストを描くだけだったので、Saasは使わず、生のCSSで書いています。
CodePen
HTML
全文
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="stylesheet.css" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text x=%2250%%22 y=%2250%%22 style=%22dominant-baseline:central;text-anchor:middle;font-size:90px;%22>🐯</text></svg>"
/>
<title>HAPPY NEW YEAR !</title>
</head>
<body>
<div class="container">
<div class="ear"></div>
<div class="ear" style="--right: 0"></div>
<div class="face">
<div class="eye" style="--left: 20%"></div>
<div class="eye" style="--right: 20%"></div>
<div class="around-mouth"></div>
<div class="nose">
<div class="mouth"></div>
</div>
<div class="beard" style="--left: 15%"></div>
<div class="beard" style="--right: 15%; --rotate: -1"></div>
<div class="top-pattern"></div>
<div class="side-pattern"></div>
<div class="side-pattern" style="--right: 0; --rotate: -1"></div>
</div>
</div>
</body>
</html>
余談ですが、🐯をファビコンにするのはcatnoseさんの記事を真似してやりました。
CSS
全文
/* カスタムプロパティ */
:root {
--tiger-color: #fcdb57;
--tiger-border: #594639;
--right: auto;
--left: auto;
--rotate: 1;
}
/* 共通のスタイル */
.face *,
.face *::before,
.face *::after {
position: absolute;
content: "";
}
/* コンテナ */
.container {
position: relative;
width: 260px;
height: 220px;
}
/* 輪郭 */
.face {
position: relative;
width: 250px;
height: 210px;
overflow: hidden;
background-color: var(--tiger-color);
border: solid 5px var(--tiger-border);
border-radius: 50% / 60% 60% 40% 40%;
}
/* 耳 */
.ear {
position: absolute;
right: var(--right);
z-index: -1;
width: 60px;
height: 60px;
background-color: var(--tiger-color);
border: solid 5px var(--tiger-border);
border-radius: 50%;
}
.ear::before {
position: absolute;
top: 50%;
left: 50%;
width: 40px;
height: 40px;
content: "";
background-color: white;
border-radius: 50%;
transform: translate(-50%, -50%);
}
/* 目 */
.eye {
top: 45%;
right: var(--right);
left: var(--left);
width: 25px;
height: 25px;
background-color: var(--tiger-border);
border-radius: 50%;
}
/* 鼻 */
.nose {
top: 57%;
left: 50%;
width: 35px;
height: 25px;
background-color: var(--tiger-border);
border-radius: 50% / 40% 40% 60% 60%;
transform: translateX(-50%);
}
/* 口 */
.mouth {
top: 23px;
left: 50%;
width: 70px;
height: 15px;
transform: translateX(-50%);
}
.mouth::before,
.mouth::after {
box-sizing: border-box;
width: 35px;
height: 15px;
content: "";
border: solid 5px var(--tiger-border);
border-top: 0;
border-radius: 0 0 75px 75px;
}
.mouth::before {
left: 0;
}
.mouth::after {
left: 50%;
}
/* ひげ */
.beard {
top: 67%;
right: var(--right);
left: var(--left);
width: 25px;
height: 3px;
background-color: var(--tiger-border);
}
.beard::before,
.beard::after {
right: calc(var(--rotate) * -5px);
width: 25px;
height: 3px;
background-color: var(--tiger-border);
}
.beard::before {
top: -13px;
transform: rotate(calc(var(--rotate) * 20deg));
}
.beard::after {
top: 13px;
transform: rotate(calc(var(--rotate) * -20deg));
}
/* 口周りの白い部分 */
.around-mouth {
bottom: 0;
left: 50%;
width: 150px;
height: 100px;
background-color: white;
border-radius: 50% / 80% 80% 20% 20%;
transform: translateX(-50%);
}
/* 模様(上) */
.top-pattern {
left: 50%;
width: 20px;
height: 70px;
background-color: var(--tiger-border);
border-radius: 50% / 0% 0% 100% 100%;
transform: translateX(-50%);
}
.top-pattern::before,
.top-pattern::after {
left: 50%;
height: 15px;
background-color: var(--tiger-border);
border-radius: 50% / 70% 70% 30% 30%;
transform: translateX(-50%);
}
.top-pattern::before {
top: 20%;
width: 70px;
}
.top-pattern::after {
top: 55%;
width: 60px;
}
/* 模様(左右) */
.side-pattern {
top: 50%;
right: var(--right);
width: 70px;
height: 15px;
background-color: var(--tiger-border);
border-radius: 50%;
transform: translateX(calc(var(--rotate) * -50%))
rotate(calc(var(--rotate) * -15deg));
}
.side-pattern::before,
.side-pattern::after {
position: absolute;
width: 70px;
height: 15px;
background-color: var(--tiger-border);
border-radius: 50%;
}
.side-pattern::before {
top: -25px;
left: calc(var(--rotate) * 10px);
}
.side-pattern::after {
bottom: -25px;
left: calc(var(--rotate) * -10px);
}
実装のポイント
1から解説するととてつもなく長くなってしまうのでいくつかのポイントに絞って紹介したいと思います。
HTMLの構造
まずは寅のイラストをいくつかのパーツに分けます。
今回はコンテナという透明なボックスを起点に耳パーツと顔パーツに分け、顔パーツの中でさらに目や口など細かいパーツに分けました。
口は鼻にくっついているので鼻を起点にして位置を考えた方が実装しやすいだろうと考え、鼻の子要素にしています。
また、当初は顔の子要素に耳があったのですが、口周りの白い部分や模様が顔からはみ出ないようにする際に都合が悪かったため、コンテナを作るようにしました。(詳細は後述)
<div class="container">
<div class="ear"></div>
<div class="ear" style="--right: 0"></div>
<div class="face">
<div class="eye" style="--left: 20%"></div>
<div class="eye" style="--right: 20%"></div>
<div class="around-mouth"></div>
<div class="nose">
<div class="mouth"></div>
</div>
<div class="beard" style="--left: 15%"></div>
<div class="beard" style="--right: 15%; --rotate: -1"></div>
<div class="top-pattern"></div>
<div class="side-pattern"></div>
<div class="side-pattern" style="--right: 0; --rotate: -1"></div>
</div>
</div>
HTMLの構造を簡単に図にしてみました。
構成図
position: absoluteで親要素を基準に位置決め
container
とface
以外の全ての要素のスタイルにposition: absolute
を適用しています。
absolute
とは、親要素からどれくらいの距離か?で位置を指定するものです。
例えば「目」の縦位置は「顔」の真ん中あたり、横位置は「顔」の端から少し内側のところにありますよね。このようにCSSでイラストを描く際は「親要素からどれくらいの位置にあるか?」で位置を考えることが多いので、ほぼ全ての子要素にposition: absolute
を使っています。
具体的な位置はtop
bottom
right
left
プロパティを用いて、親要素から上/下/右/左へどれくらい進むかで指定します。(例:left: 50%
top: 23px;
)
顔(親要素)からの位置で目の位置を指定する
positionプロパティについてはこちらの記事がとてもわかりやすいです。
擬似要素 before afterを活用しよう
擬似要素である::before
や::after
は本来、content
プロパティを使用して、要素の前後に装飾的な内容を追加するためのものです。例えば、以下[1] のように使います。
/* リンクの前にハートを追加 */
a::before {
content: "♥";
}
一方、CSSでイラストを描く場合、::before
や::after
はわざわざHTMLのタグを増やすほどでもない細かいパーツの実装や同じパーツであるものの実装上は2つ〜3つの要素に分けないといけないパーツの実装に役立ちます。
もちろん擬似要素を使わず普通に子要素にしてしまっても仕上がりは変わりませんが、::before
や::after
を使うことでHTMLをスッキリさせることができるのです。
具体的には以下のパーツで::before
や::after
を使っています。例えば顔の模様(上)なら縦線がtop-pattern
、横線のうち上にある方がtop-pattern::before
で下にある方がtop-pattern::after
です。
寅のイラストでbeforeやafterを使っている箇所
こちらはear::before
の実装例です。content
を空にし、後は普通の要素と同様にしてスタイルを書いています。
.ear::before {
position: absolute;
top: 50%;
left: 50%;
width: 40px;
height: 40px;
content: "";
background-color: white;
border-radius: 50%;
transform: translate(-50%, -50%);
}
共通のスタイルをまとめる
セレクタを活用
CSSでイラストを描こうとするとえげつない記述量になりがちです。
イラストである以上ある程度は仕方がないのですが、できる限り記述量を減らすために、あらゆる箇所で出てくるスタイルはまとめて定義しておきましょう。
.test { position: absolute; }
の.test
の部分はセレクタと呼ばれる部分で、単にクラス名や要素名が指定されることが多いですが、実はもっと詳しく条件を指定することができます。
今回の場合はface
の全ての子要素内のposition: absolute
、::before
::after
でのcontent: ""
は毎回出てくるのでまとめて定義しておきます。
/* 共通のスタイル */
.face *,
.face *::before,
.face *::after {
position: absolute;
content: "";
}
「*」は全ての要素 という意味になります。.face
の後にスペースを開けて*
を書くことで 「faceクラス内の全ての子要素(直接の子要素でなくてもいい)」 という意味になります。
そこからさらに::before
をつけると「faceクラス内の全ての子要素の::before
要素」という意味になります。
そして、「,」で区切ることで「.face *
あるいは.face *::before
あるいは.face *::after
で以下のスタイルを適用します」と言う意味になり、同じスタイルを複数のセレクタにまとめて適用することができます。
カスタムプロパティで定数を定義
寅の黄色や焦げ茶色などのカラーコードは定数として定義することで、可読性を上げることができますし、「やっぱりこの寅ピンクにしたいなあ」なんてときも簡単に変更できるようになります。
CSSにはカスタムプロパティという機能があり、変数のような役割を果たします。
/* カスタムプロパティ */
:root {
--tiger-color: #fcdb57;
--tiger-border: #594639;
}
--変数名: 値
でカスタムプロパティを定義することができます。:root
というところに書くと全ての箇所でこのカスタムプロパティが適用されるようになります。
カスタムプロパティを使いたいときは
background-color: var(--tiger-color);
のようにvar(プロパティ名)
と書きます。
関数の引数のようにカスタムプロパティを使いたい
前項でカスタムプロパティを定数として使いましたが、少し工夫すれば関数の引数みたいに使うことができます。
顔には左右対称のパーツがいくつか存在します。例えば目です。
素朴に実装しようとすると、以下のように「右目クラス」と「左目クラス」を作ると思います。
<div class="left-eye"></div>
<div class="right-eye"></div>
.left-eye, .right-eye {
top: 45%;
width: 25px;
height: 25px;
background-color: var(--tiger-border);
border-radius: 50%;
}
.left-eye {
left: 20%;
}
.right-eye {
right: 20%;
}
left-eye
とright-eye
で違うのは左右の位置のみです。しかし、その違いがあるゆえにコード量が増えてしまっています。
カステムプロパティを使えば、「eyeという1つのクラスを定義し、渡される引数の値で適用されるスタイルを変える」という処理を実現することができます。
実際のコードを見てみましょう。
まず、:root
に--right
--left
プロパティを定義し、初期値をauto
に設定します。auto
というのはleft
やright
プロパティの初期値で、これらのプロパティを無効にするために使います。
次にeye
クラスの中でright
やleft
プロパティの値として、先ほど定義したカスタムプロパティを指定します。
:root {
/* 前略 */
--right: auto;
--left: auto;
}
.eye {
top: 45%;
right: var(--right);
left: var(--left);
width: 25px;
height: 25px;
background-color: var(--tiger-border);
border-radius: 50%;
}
このままではleft
right
ともにauto
になってしまいます。左目のときはleft: 20%;
、右目のときはright: 20%;
にしたいのでHTML側からカスタムプロパティの値を渡してあげましょう。
<div class="eye" style="--left: 20%"></div>
<div class="eye" style="--right: 20%"></div>
このようにすることでカステムプロパティの値を上書きすることができます。
ちなみにcalc()
と併用することでより柔軟に値を設定することもできます。
.beard::before {
top: -13px;
transform: rotate(calc(var(--rotate) * 20deg));
}
border-radiusは最大8つの値を指定できる
border-radius
はborder-radius: 50%;
などのように使うことが多いですが、実は最大8つの値をとることができます。 それによりより複雑な円を書くことができ、今回のイラストでは寅の輪郭などに使っています。
理論を説明するよりも実際に触ってみた方が早いと思うので気になる方は以下のサイトで色々触ってみてください。
理論的なことについては以下の記事がわかりやすいと思います。
overflow:hiddenで顔からはみ出ないようにする
左右の模様と口周りの白い部分は親要素であるface
にoverflow:hidden
を設定することで、はみ出た部分を非表示にしています。
overflow:hiddenを指定しなかった場合
ear
がface
の子要素である場合、耳は顔の外にあるものなのでoverflow:hidden
を設定すると非表示になってしまいます。そこでcontainer
という顔と同じサイズの透明な要素を設定し、そこを基準にして耳を生やすようにしました。
translateでパーツを真ん中に持ってくる
鼻を顔の真ん中の位置に持ってこようとしてleft: 50%
をしても真ん中にはならず右にずれてしまいます。これはleft: 50%
が要素の左端を基準としているからです。
親要素の左端から要素の左端までで50%
この状態から要素の半分の長さだけ左に戻してあげることで真ん中にすることができます。それにはtranslateX
を使います。
/* 鼻 */
.nose {
/* 前略 */
left: 50%;
transform: translateX(-50%);
}
ここでいう50%というのは要素のX軸方向の長さの50% なので、つまり要素の半分の長さの分だけ移動するという意味になります。
同様に縦方向の真ん中にパーツを置きたい場合はtranslateY
を使いましょう。
終わりに
この記事ではCSSで寅を描く方法について紹介しました。やっぱりSassを使わないのはしんどいですが、セレクタやカスタムプロパティの勉強になってよかったです。
最後に超即席ですが今回描いた寅を年賀状っぽくしたので貼っておきます。
Discussion