IE10+にも対応したCSSで作る吹き出しの三角形部分に影を適用する

色んなブログで紹介されているCSSだけでできる吹き出しのデザイン。これに box-shadow で影をつけても三角形部分に影は適用されません。実は filter プロパティの drop-shadow を使えば三角形部分にも影をつけることができますが、残念なことにIEには対応していません。そこでSVGを使って対応してみたいと思います。

SVGで box-shadow を表現

まず、三角形に影をつける前に、SVGで影をどのように表現するのか紹介します。


<div class="box-shadow"></div>

.box-shadow {
  width: 100px;
  height: 100px;
  background-color: #fff;
  box-shadow: 0 0 30px #000;
}

CSSで100×100の白い正方形に影をつけた図形をSVGで表現してみます。


<svg width="100" height="100">
  <filter id="shadow" x="-100%" y="-100%" width="300%" height="300%">
    <feGaussianBlur in="SourceAlpha" result="GaussianBlur" stdDeviation="10"/>
    <feMerge>
      <feMergeNode in="GaussianBlur"/>
      <feMergeNode in="SourceGraphic"/>
    </feMerge>
  </filter>
  <polygon points="0,0 100,0 100,100 0,100" fill="#fff" filter="url(#shadow)"/>
</svg>

ガウシアンフィルタで影を表現します。stdDeviation の値はCSSで指定した box-shadow30px3で割った値の 10 を指定します。


svg {
  overflow: visible;
}

Chromeではデフォルトで <svg> 要素に overflow: hidden が指定されていて影が表示されないので visible を指定してください。ちなみにこれで6時間くらいハマりました。

透明度を指定する場合


box-shadow: 0 0 30px rgba(0, 0, 0, .6);

黒で透明度60%の影をつけたいときどうすればよいか。


<svg width="100" height="100">
  <filter id="shadow" x="-100%" y="-100%" width="300%" height="300%">
    <feColorMatrix in="SourceAlpha" result="ChangeAlpha" type="matrix" values="0 0 0 0 0
                                                                               0 0 0 0 0
                                                                               0 0 0 0 0 
                                                                               0 0 0 .6 0"/>
    <feGaussianBlur in="ChangeAlpha" result="GaussianBlur" stdDeviation="10"/>
    <feMerge>
      <feMergeNode in="GaussianBlur"/>
      <feMergeNode in="SourceGraphic"/>
    </feMerge>
  </filter>
  <polygon points="0,0 100,0 100,100 0,100" fill="#fff" filter="url(#shadow)"/>
</svg>

<feColorMatrix>values行列を指定して変換します。.6 は透明度60%のことで、この数値を変えれば透明度を自由に変更することができます。

色を変更する場合


box-shadow: 0 0 30px rgb(220, 151, 51);

たとえばこのようにオレンジ色の影をつけたいときはどうすればよいか。


<svg width="100" height="100">
  <filter id="shadow" x="-100%" y="-100%" width="300%" height="300%" color-interpolation-filters="sRGB">
    <feColorMatrix in="SourceAlpha" result="ChangeAlpha" type="matrix" values="0 0 0 0 .862745
                                                                               0 0 0 0 .592157
                                                                               0 0 0 0 .2
                                                                               0 0 0 1 0"/>
    <feGaussianBlur in="ChangeAlpha" result="GaussianBlur" stdDeviation="10"/>
    <feMerge>
      <feMergeNode in="GaussianBlur"/>
      <feMergeNode in="SourceGraphic"/>
    </feMerge>
  </filter>
  <polygon points="0,0 100,0 100,100 0,100" fill="#fff" filter="url(#shadow)"/>
</svg>

先ほどと同じように <feColorMatrix> を使います。今回の場合は透明度は変更しないので 1 としています。行列に新たに3つの値が設定されています。それらは box-shadow で指定したRGBそれぞれを255で割った値を指定します。例えば、.862745220/255の結果です。これで、box-shadow と同じ色を表現できます。

また、<filter> 要素に color-interpolation-filters="sRGB" を指定します。これを指定しないと正しくRGBA値で変換されません。

SVGで三角形を作成


<svg width="200" height="100" viewBox="0 0 100 50">
  <polygon points="50,50 0,0 100,0" fill="#f1c40f"/>
</svg>

<polygon> 要素で三角形の各点をプロットします。

吹き出しを作成


<div class="balloon">
  吹き出し
  <svg class="balloon-triangle" viewBox="0 0 100 50">
    <polygon points="50,50 0,0 100,0" fill="#f1c40f"/>
  </svg>
</div>

.balloon {
  display: inline-block;
  position: relative;
  padding: .6em 2.4em;
  border-radius: .25em;
  box-shadow: 0 0 9px rgba(0, 0, 0, .55);
  background-color: #f1c40f;
}
.balloon-triangle {
  position: absolute;
  left: 50%;
  bottom: -11px;
  width: 24px;
  height: 12px;
  overflow: visible;
  -webkit-transform: translateX(-50%);
  transform: translateX(-50%);
}

三角形に影を適用


<div class="balloon">
  吹き出し
  <svg class="balloon-triangle" viewBox="0 0 100 50">
    <filter id="shadow" x="-100%" y="-100%" width="300%" height="300%" color-interpolation-filters="sRGB">
      <feColorMatrix in="SourceAlpha" result="ChangeAlpha" type="matrix" values="0 0 0 0 0
                                                                                 0 0 0 0 0
                                                                                 0 0 0 0 0
                                                                                 0 0 0 .55 0"/>
      <feGaussianBlur in="ChangeAlpha" result="GaussianBlur" stdDeviation="9"/>
      <feMerge>
        <feMergeNode in="GaussianBlur"/>
        <feMergeNode in="SourceGraphic"/>
      </feMerge>
    </filter>
    <polygon points="50,50 0,0 100,0" fill="#f1c40f" filter="url(#shadow)"/>
  </svg>
</div>

正方形の場合、stdDeviation は3で割った値にしていましたが、三角形の場合は 9px ならそのまま 9 と指定します。ここらへんの計算方法がよくわかっていません。

また、三角形部分の周り全部に影が表示されてしまっています。上の影は必要ないので修正します。

三角形上部の影を消去


<div class="balloon">
  吹き出し
  <svg class="balloon-triangle" viewBox="0 0 100 50">
    <filter id="shadow" x="-100%" y="0" width="300%" height="300%" color-interpolation-filters="sRGB">
      <feColorMatrix in="SourceAlpha" result="ChangeAlpha" type="matrix" values="0 0 0 0 0
                                                                                 0 0 0 0 0
                                                                                 0 0 0 0 0
                                                                                 0 0 0 .55 0"/>
      <feGaussianBlur in="ChangeAlpha" result="GaussianBlur" stdDeviation="9"/>
      <feMerge>
        <feMergeNode in="GaussianBlur"/>
        <feMergeNode in="SourceGraphic"/>
      </feMerge>
    </filter>
    <polygon points="50,50 0,0 100,0" fill="#f1c40f" filter="url(#shadow)"/>
  </svg>
</div>

<filter> 要素の y0 を指定すれば上の影はカットされます。

もし、インラインSVGが嫌ならData URI化して backgrond に埋め込むと良いです。