CSSでつくる吹き出し△の形を調整したい
技術推進室の色川です。
普段はISMSの運用や自社サービスの脆弱性診断をしつつ、一方でフロントエンド開発支援などをしています。
今回のお題は、今さら感が強いですが「CSSでつくる吹き出し(△のやつ)」です。
任意の形にしようと思ったら意外と手こずったので、備忘録がてら考え方を残しておきます。
ちなみに、吹き出しの作り方には「極太ボーダーを重ねて作る方法」と「ボックスを変形させて作る方法」がありますが、手こずったのは後者です。
つくるもの
この例では以下の div の上に、高さ 12px、幅(底辺)18px の△をつけていきます。
<style>
.baloon {
position: relative; /* △を position: absolute で位置調整するために必要 */
width: 200px;
height: 120px;
border: 1px solid #ccc;
border-radius: 5px;
}
</style>
<div class="baloon"></div>
極太ボーダーを重ねて作る方法
border
でつくった▲を2つ用意し、枠線色の▲の少し下に背景色の▲を重ねることで線のように見せる方法です。2つの▲は .baloon
の ::before
と ::after
で作ります。
border で▲をつくる
ボックスに太めのボーダーをつけて各辺の色を変えてみると、以下のような構造をしていることが分かります。
コンテンツのサイズを 0 にするとボーダーだけが残ります。
border-bottom
以外の色を transparent
にすれば上向き△のできあがりです。
三角形の高さは border-bottom-width
で、幅は border-right-width
と border-left-width
で調整します。
実装例
▲自体の大きさと形は同じなので、::before
と ::after
にまとめて指定します。
△のX座標(left
)を指定するとき、△の左端ではなく頂点の位置を指定できた方が調整しやすいので(△の大きさが変わっても left
を変更しなくて済む)、transform: translateX(-50%)
で△のサイズの半分だけ左にずらしています。
.baloon::before,
.baloon::after {
position: absolute;
width: 0;
height: 0;
left: 50px;
transform: translateX(-50%);
border: solid transparent;
border-width: 0 9px 12px 9px; /* 左右をそれぞれ 9px にすることで幅 18px を実現 */
content: "";
}
::before
に枠線色、::after
に背景色をつけて、ずれて重なるように位置を指定します。
.baloon::before {
top: -12px;
border-bottom-color: #ccc;
}
.baloon::after {
top: -10.5px; /* ここのずらし具合で線の太さが決まる */
border-bottom-color: #fff;
}
ボックスを変形させて作る方法
::before
で上辺と左辺だけボーダーをつけたボックスを作り、角が真上を向くように回転させることでを△作る方法です。
基本形
以下のようにすれば上向き△は簡単に作れます。角を上に向けるために transform
で rotate(45deg)
しています。
.baloon::before {
position: absolute;
width: 12px;
height: 12px;
border: 1px solid;
border-color: #ccc transparent transparent #ccc;
transform: rotate(45deg);
background-color: #fff;
content: "";
}
これだと角が90°になってしまう上、△の高さも 12px になりません。(辺の長さが 12px になっている。)
これをベースにいろいろと加工していきます。
任意の形になるように変形する
transform
の skew()
でブロックを変形させます。
skew()
に指定するのは下図の青の角度なので、三角形のサイズから赤の角度を求め、そこから算出します。
赤の角度はは三角形の高さ(12px)と底辺(18px)からアークタンジェントで求めることができます。
赤の角度 = Math.atan2(9, 12) * 2 // オレンジの角の2倍
青の角度 = (Math.PI / 2 - 赤の角度) / 2
これを計算すると、青の角度は .142rad
となります。(小数第4位を四捨五入)
.baloon::before {
position: absolute;
width: 12px;
height: 12px;
border: 1px solid;
border-color: #ccc transparent transparent #ccc;
transform: skew(.142rad, .142rad);
background-color: #fff;
content: "";
}
△の高さを変える
今は△の両辺が 12px になっていて、高さは 12px になっていません。高さを直接指定することはできないので、両辺の長さで調整します。これは三平方の定理で求められます。(skew()
でX方向とY方向を同じだけ変形させた場合、辺の長さは変わりません。)
width = height = Math.sqrt(9 ** 2 + 12 ** 2) // ** はべき乗演算子
これを計算すると 15px となります。
角を上に向ける
rotate(45deg)
すればよいのですが、transform
は指定順(適用順)で結果が変わるので注意が必要です。ここでは先に rotate()
を指定します。
.baloon::before {
position: absolute;
width: 15px;
height: 15px;
border: 1px solid;
border-color: #ccc transparent transparent #ccc;
transform: rotate(45deg) skew(.142rad, .142rad);
background-color: #fff;
content: "";
}
位置を調整する
left
極太ボーダーのときと同じく、left
で頂点の位置を指定すべく translateX(-50%)
したいのですが、ここも指定順に注意が必要です。ここでは最初に指定すればうまくいきます。
.baloon::before {
position: absolute;
left: 50px;
width: 15px;
height: 15px;
border: 1px solid;
border-color: #ccc transparent transparent #ccc;
transform: translateX(-50%) rotate(45deg) skew(.142rad, .142rad);
background-color: #fff;
content: "";
}
top
縦の位置が中途半端なのは、rotate()
で回転させているせいです。rotate()
は元のボックスの中心点を軸にするので、以下のようになっているのです。(分かりやすさのため、border
をすべて表示しています。)
ボックスの中心は height
の半分なので、その分だけ top
をマイナスします。
.baloon::before {
position: absolute;
left: 50px;
top: calc(-15px / 2);
width: 15px;
height: 15px;
border: 1px solid;
border-color: #ccc transparent transparent #ccc;
transform: translateX(-50%) rotate(45deg) skew(.142rad, .142rad);
background-color: #fff;
content: "";
}
まとめ
「ボックスを変形させて作る方法」はものすごく複雑なので mixin 化したいところですが、Sass には三角関数がないのでどうしたものかなぁという感じです。
とりあえず考え方を丁寧に残したので、次やるときはきっとラクにできるはず・・・!