CSSでつくる吹き出し△の形を調整したい

2019年8月23日

技術推進室の色川です。
普段は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-widthborder-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 で上辺と左辺だけボーダーをつけたボックスを作り、角が真上を向くように回転させることでを△作る方法です。

基本形

以下のようにすれば上向き△は簡単に作れます。角を上に向けるために transformrotate(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 になっている。)
これをベースにいろいろと加工していきます。

任意の形になるように変形する

transformskew() でブロックを変形させます。

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 には三角関数がないのでどうしたものかなぁという感じです。
とりあえず考え方を丁寧に残したので、次やるときはきっとラクにできるはず・・・!

GMOメディアでは仲間を募集しております

当社は、「For your Smile,with Internet.」という企業理念のもと、多くの笑顔と感動を生み出していくべく、インターネットメディア媒体を多ブランドでサービス展開しています。
募集内容について詳しくはコチラをご覧ください