9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ZOZOAdvent Calendar 2022

Day 17

角の丸い吹き出しをCSSで作るには

Last updated at Posted at 2022-12-16

角の丸い吹き出しを作る

今日はCSSのみで角の丸い吹き出しを作ります。
一般的にCSSで作成される角が丸い吹き出しというと文字を囲む箱部分の角だけが丸い物が多いのですが、今回はさらに飛び出し部分の角も丸く
なおかつ、ボーダーとシャドウにも対応させてみましょう。

完成図
完成予想図

一般的な吹き出しを作る

吹き出しを作る

まずは飛び出し部分の角が尖っていない一般的な吹き出しを作ってみます。
HTMLは次のとおりです。
このHTMLの balloon 部分を吹き出しにします。

<!DOCTYPE html>
<html>
    <head>
        <title>Hello CSS balloon</title>
            <link rel="stylesheet" href="css.css" />
            <meta charset="UTF-8" />    
        </head>
    <body>
        <h1>Hello! balloon</h1>
        <div class="balloon">
            Hello! CSS balloon.
        </div>
    </body>
</html>

吹き出しの矩形部分は backgroundborder を使用して作ります。
border-radius を使用することで角を丸めます。

body{
    -webkit-text-size-adjust: 100%;
    color: black;
    background-color: white;
}

.balloon{
    background-color: #F0F0D0;
    display: inline-block;
    border-radius: 8px;
    padding: 32px;
    border: 2px solid #666;
    position: absolute;
    box-shadow: 0 4px 4px #000;
}

角の丸い矩形

飛び出し部分を作る

吹き出しの飛び出し部分の三角を取得するためには、after要素を使って要素を作り次のように高さと幅がゼロの要素に太さのある罫線をつけると各辺がそれぞれ二等辺三角形となるので、

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: red green blue orange;
    border-width: 40px;
}

三角形を用意する

3辺を透明(transparent)にして一辺を使用する方法が一般的です。
bottom にborder-widthの倍のサイズを指定することで飛び出しの位置が吹き出しの下に一致します。

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: red transparent transparent transparent;
    border-width: 40px;
    bottom: -80px;

}

3角形の位置を合わせる

この方法の問題点

これで色を調整すれば一般的な吹き出しを作ることができますがこの方法は問題があります。

それは、飛び出し部分を丸めることができないのと、飛び出し部分に影がつかないことです。
試しに飛び出し部分に border-radiusbox-shadow を適用してみましょう。

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: red transparent transparent transparent;
    border-width: 40px;
    bottom: -80px;
    border-radius: 10px;
    box-shadow: 0 4px 4px #000;
}

等角だけまるまってしまう

このように、影があらぬところにつけられてしまい、飛び出しの角もまるまっていません。
よく見ると底角の左右が丸まってしまっています。 border-radiusbox-shadow が元の矩形に対して適用されてその一辺を利用しているために発生する現象です。

飛び出しを丸めた吹き出しを作る

頂角が丸まった二等辺三角形を作る。

ということで、ここからが本題です。先程の一辺を使う方法だと角が中心部分となってしまい飛び出し部分を丸めることができませんでした。
そこで、2辺を使うことで矩形の角を頂角として、頂角がまるまった二等辺三角形を用意します。

そのためにまずは、transparent の位置を変更して右辺と底辺を残すようにします。
また影を右下に落ちるようにX座標にもY座標と同じだけ影を移動します。

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent green blue transparent;
    border-width: 40px;
    bottom: -80px;
    border-radius: 10px;
    box-shadow: 4px 4px 4px #000;
}

頂角がまるまった3角形を得る
これで、頂角が丸まった2等辺三角形を得ることができました。

等辺側に影がついているのも確認できます。
底角は尖っていてほしいので border-radius を右下だけに適用します。

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent green blue transparent;
    border-width: 40px;
    bottom: -80px;
    border-radius: 0px 0px 10px 0px;
    box-shadow: 4px 4px 4px #000;
}

等角は丸めない

これで頂角だけが丸まった二等辺三角形を得ることができました。

めでたしめでたし

いや、めでたくありません

位置を合わせる

まだ吹き出しの位置がおかしく、向きもあらぬ方向を向いています。

これを調整するためにCSSの transform を使用します。
transform を使用することで、要素を変形したり回転させることができます。

今回は45度回転させたいので rotate(45deg); を指定します。

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent green blue transparent;
    border-width: 40px;
    bottom: -80px;
    border-radius: 0 0 10px 0;
    box-shadow: 4px 4px 4px #000;
    transform: rotate(45deg);
}

向きが揃った

位置は上辺と右辺が表示されない分上向きに座標を動かして上げる必要があります。
bottom の数値を border-width 分だけ加算します。

もともと bottom の値は border-width * -2 の値だったのでちょうど boder-width * -1 の値となります。

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent green blue transparent;
    border-width: 40px;
    bottom: -40px;
    border-radius: 0 0 10px 0;
    box-shadow: 4px 4px 4px #000;
    transform: rotate(45deg);
}

位置が揃った

飛び出しを細くする

これで角の丸まった飛び出しを作ることができましたが、飛び出し部分をもっとスリムにしたいこともあると思います。

これも transform で実現可能です。

scaleX() を使用することで左右の幅を調整できます
左右幅を半分にしたければ 0.5 を渡して

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent green blue transparent;
    border-width: 40px;
    bottom: -40px;
    border-radius: 0 0 10px 0;
    box-shadow: 4px 4px 4px #000;
    transform: scaleX(0.5) rotate(45deg);
}

とすると、
細くなった

飛び出し部分を細くすることが出来ます。

飛び出し部分に色を付ける

ここまでは分かりやすいように青と緑の色を付けてきましたが、ちゃんと吹き出しの一部になるように色を付けます。
矩形側の background と同じ色を 飛び出しの border-color のうち右と下につけるので

.balloon {
    background-color: #F0F0D0;
    display: inline-block;
    border-radius: 8px;
    padding: 32px;
    border: 2px solid #666;
    position: absolute;
    box-shadow: 0 4px 4px #000;
}

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent #F0F0D0 #F0F0D0 transparent;
    border-width: 40px;
    bottom: -40px;
    border-radius: 0 0 10px 0;
    box-shadow: 4px 4px 4px #000;
    transform: scaleX(0.5) rotate(45deg);
}

としてもいいのですが、この方法だと #F0F0D0 を3箇所に記載する必要があり、吹き出しの色を変えたくなったときに面倒です。
色が揃った

飛び出し部分に色を付けた。 うっすらと線が残っている部分はこの後で消えます。

css ではvar関数を使うことで変数のように値に別名をつけることができるので、それを利用します。

--background-color:  #F0F0D0; /* 吹き出しの色 */

のように指定するとその他の場所ではvar関数を使って

background-color: var(--background-color);

のように値を取得することが出来ます。

.balloon {
    --background-color:  #F0F0D0; /* 吹き出しの色 */
    background-color: var(--background-color);
    display: inline-block;
    border-radius: 8px;
    padding: 32px;
    border: 2px solid #666;
    position: absolute;
    box-shadow: 0 4px 4px #000;
}

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--background-color) var(--background-color) transparent;
    border-width: 40px;
    bottom: -40px;
    border-radius: 0 0 10px 0;
    box-shadow: 4px 4px 4px #000;
    transform: scaleX(0.5) rotate(45deg);
}

飛び出しのサイズも指定しておきます

--pick-size: 40px; /* 飛び出しのサイズ */

同様に、同じような指定をしている場所を見ていくと、影の色と大きさも同じなので、それぞれ次のように指定します。

--shadow-size:4px; /* 影の長さ */
--shadow-color: #000; /* 影の色 */

飛び出しの border-widthbottomも正負が逆なだけの同じ値です。
このような場合には calc() 関数を使用して calc(var(--pick-size)*-1); とすることで値を計算することが出来ます。

.balloon {
    --background-color:  #F0F0D0; /* 吹き出しの色 */
    --shadow-size:4px; /* 影の長さ */
    --shadow-color: #000; /* 影の色 */
    --pick-size: 40px; /* 飛び出しのサイズ */
    background-color: var(--background-color);
    display: inline-block;
    border-radius: 8px;
    padding: 32px;
    border: 2px solid #666;
    position: absolute;
    box-shadow: 0 var(--shadow-size) var(--shadow-size) var(--shadow-color);
}

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--background-color) var(--background-color) transparent;
    bottom: calc(var(--pick-size)*-1);
    border-width: var(--pick-size);
    border-radius: 0 0 10px 0;
    box-shadow: var(--shadow-size) var(--shadow-size) var(--shadow-size) var(--shadow-color);
    transform: scaleX(0.5) rotate(45deg);
}

飛び出し部分にボーダーをつける

今回では吹き出しにボーダーがついていますが、飛び出し部分にはボーダーがついていません。
分かりやすいようにボーダーの色を赤にしてみます。

ボーダーの色は飛び出し部分でも使うためこちらも
--border-color--border-width として値を設定しておきます。

.balloon {
    --background-color:  #F0F0D0; /* 吹き出しの色 */
    --shadow-size: 4px; /* 影の長さ */
    --shadow-color: #000; /* 影の色 */
    --border-width: 2px; /*線の太さ */
    --border-color: red; /*線の色 */
    --pick-size: 40px; /* 飛び出しのサイズ */
    background-color: var(--background-color);
    display: inline-block;
    border-radius: 8px;
    padding: 32px;
    border: var(--border-width) solid var(--border-color);
    position: absolute;
    box-shadow: 0 var(--shadow-size) var(--shadow-size) var(--shadow-color);
}
.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--background-color) var(--background-color) transparent;
    bottom: calc(var(--pick-size)*-1);
    border-width: var(--pick-size);
    border-radius: 0 0 10px 0;
    box-shadow: var(--shadow-size) var(--shadow-size) var(--shadow-size) var(--shadow-color);
    transform: scaleX(0.5) rotate(45deg);
}

ボーダーがつかない

飛び出し部分にボーダーがついていないですし、よく見るとなんだか薄っすらと上方向にも影が漏れてしまっていますね
これも合わせて修正します。

CSSでボーダーをつけるにはborder を使うのですが、こんかいはそもそも飛び出し部分自体が border で実装されているためこれにさらなる border を追加することが出来ません。

そこで、また疑似要素をもう一つ用意します。
::after をコピーして ::before を作り、こちらで罫線と影の処理を行います。

.balloon::after {
    --pick-size: 40px; /* 飛び出しのサイズ */
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--background-color) var(--background-color) transparent;
    bottom: calc(var(--pick-size)*-1);
    border-width: var(--pick-size);
    border-radius: 0 0 10px 0;
    box-shadow: var(--shadow-size) var(--shadow-size) var(--shadow-size) var(--shadow-color);
    transform: scaleX(0.5) rotate(45deg);
}
.balloon::before{
    --pick-size: 40px; /* 飛び出しのサイズ */
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--background-color) var(--background-color) transparent;
    bottom: calc(var(--pick-size)*-1);
    border-width: var(--pick-size);
    border-radius: 0 0 10px 0;
    box-shadow: var(--shadow-size) var(--shadow-size) var(--shadow-size) var(--shadow-color);
    transform: scaleX(0.5) rotate(45deg);
}

重複している部分のうち、::before--pick-size 指定は削除します。
また、影は罫線がついたサイズに対して当てたいため ::after 側の box-shadow も削除します

飛び出し部分の丸めと幅はbeforeとafterでそれぞれ記載したくないため、こちらも var()pick-radiuspkck-thin としてまとめます
すると次のように出来ます。

.balloon {
    --background-color: #F0F0D0; /* 吹き出しの色 */
    --shadow-size: 4px; /* 影の長さ */
    --shadow-color: #000; /* 影の色 */
    --border-width: 2px; /*線の太さ */
    --border-color: red; /*線の色 */
    --pick-size: 40px; /* 飛び出しのサイズ */
    --pick-radius: 10px; /* 飛び出しの丸め */
    --pick-thin: 0.5; /* 飛び出しの幅 */
    background-color: var(--background-color);
    display: inline-block;
    border-radius: 8px;
    padding: 32px;
    border: var(--border-width) solid var(--border-color);
    position: absolute;
    box-shadow: 0 var(--shadow-size) var(--shadow-size) var(--shadow-color);
}

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--background-color) var(--background-color) transparent;
    bottom: calc(var(--pick-size)*-1);
    border-width: var(--pick-size);
    border-radius: 0 0 var(--pick-radius) 0;
    box-shadow: var(--shadow-size) var(--shadow-size) var(--shadow-size) var(--shadow-color);
    transform: scaleX(var(--pick-thin)) rotate(45deg);
}

.balloon::before {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--background-color) var(--background-color) transparent;
    bottom: calc(var(--pick-size)*-1);
    border-width: var(--pick-size);
    border-radius: 0 0 var(--pick-radius) 0;
    box-shadow: var(--shadow-size) var(--shadow-size) var(--shadow-size) var(--shadow-color);
    transform: scaleX(var(--pick-thin)) rotate(45deg);
}

before の色をボーダーの色に合わせて影を作ります。

.balloon::before {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--border-color) var(--border-color) transparent;
    bottom: calc(var(--pick-size)*-1);
    border-width: var(--pick-size);
    border-radius: 0 0 var(--pick-radius) 0;
    box-shadow: var(--shadow-size) var(--shadow-size) var(--shadow-size) var(--shadow-color);
    transform: scaleX(var(--pick-thin)) rotate(45deg);
}

::before のサイズを border-width の2倍分大きくします

.balloon::before {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--border-color) var(--border-color) transparent;
    bottom: calc(var(--pick-size)*-1);
    border-width: calc(var(--pick-size) + var(--border-width) * 2);
    border-radius: 0 0 var(--pick-radius) 0;
    box-shadow: var(--shadow-size) var(--shadow-size) var(--shadow-size) var(--shadow-color);
    transform: scaleX(var(--pick-thin)) rotate(45deg);
}

ボーダーの付く場所がずれてる

ボーダーの位置が右上に発生してしまっているため ::border を真下に来るように leftbottom の位置を調整します。
left については飛び出しの位置を調整できるようにこれも var()pick-left として定義します。

.balloon {
    --pick-left: 10px; /* 飛び出しの位置 */
}

.balloon::after {
    left: var(--pick-left);
}

.balloon::before {
    bottom: calc( (var(--pick-size) + var(--border-width) * 2) * -1);
    left: calc(var(--pick-left) - var(--border-width)*2);
}

ボーダーが正しくついた

細部を整える

これでひとまず、飛び出しにボーダーをつけることが出来ましたが、
よく見ると飛び出し部分の影が矩形部分より存在感が薄くなっています。
また、飛び出しの矩形から少し上側にも影が漏れていたり、飛び出しの先端でボーダーが太くなっていたりして不格好です。

影は、斜めに展開しているため、ルート2倍に影が伸びたのに対して横幅を半分にしているため、おおよそ1.4倍影の位置を下げます。

.balloon::before{
  box-shadow: calc(var(--shadow-size) * 1.4) calc(var(--shadow-size) * 1.4) var(--shadow-size) var(--shadow-color);
}

角のトガリを抑えるには .balloon::beforeborder-radius を大きくしてあげればOKです。

.balloon::before{
  border-radius: 0px 0px calc(var(--pick-radius)*1.5) 0px;

これで角を自然に丸めることができるようになりました。

影の位置がずらしたことで上への影漏れも目立たなくなっています。
ひとまず完成

完成

最後に細かな色や影を整えて出来上がり

body {
    -webkit-text-size-adjust: 100%;
    color: black;
    background-color: white;
}

.balloon {
    --background-color: #F0F0D0; /* 吹き出しの色 */
    --shadow-size: 2px; /* 影の長さ */
    --shadow-color: rgba(0, 0, 0, 0.3); /* 影の色 */
    --border-width: 2px; /*線の太さ */
    --border-color: #808040; /*線の色 */
    --pick-size: 40px; /* 飛び出しのサイズ */
    --pick-radius: 10px; /* 飛び出しの丸め */
    --pick-thin: 0.5; /* 飛び出しの幅 */
    --pick-left: 10px; /* 飛び出しの位置 */
    background-color: var(--background-color);
    display: inline-block;
    border-radius: 8px;
    padding: 32px;
    border: var(--border-width) solid var(--border-color);
    position: absolute;
    box-shadow: 0 var(--shadow-size) var(--shadow-size) var(--shadow-color);
}

.balloon::after {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--background-color) var(--background-color) transparent;
    left: var(--pick-left);
    bottom: calc(var(--pick-size)*-1);
    border-width: var(--pick-size);
    border-radius: 0 0 var(--pick-radius) 0;
    transform: scaleX(var(--pick-thin)) rotate(45deg);
}

.balloon::before {
    content: '';
    position: absolute;
    width: 0;
    height: 0;
    display: block;
    border-style: solid;
    border-color: transparent var(--border-color) var(--border-color) transparent;
    bottom: calc( (var(--pick-size) + var(--border-width) * 2) * -1);
    left: calc(var(--pick-left) - var(--border-width)*2);
    border-width: calc(var(--pick-size) + var(--border-width) * 2);
    border-radius: 0px 0px calc(var(--pick-radius)*1.5) 0px;
    box-shadow: calc(var(--shadow-size) * 1.4) calc(var(--shadow-size) * 1.4) var(--shadow-size) var(--shadow-color);
    transform: scaleX(var(--pick-thin)) rotate(45deg);
}

完成

ということで、今回はCSSを使って吹き出しを作ってみました。

本来のCSSの使い方とは異なるため程々に楽しむのがいいかなと思います。

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?