CSSでつくる透過吹き出し
CSSというより根性で作った。
作成手順
クラスポン付けで<span class="comment"></span>
を吹き出しにすることを目指していきます。
背景をつくる
まずこれを作ります。
角が丸くなってるのが要素本体。その下についてる帯はlinear-gradient
の透過ピクセルをborder-image
にして付けました。角の丸め方は以下の要領です。
パディングエリア(緑)の四つ角が同じになるようにボーダー(黒)を曲げているのが分かるでしょうか。コードとしては例えば
.comment { border-radius: .4em; border-bottom-width: .6em; border-bottom-left-radius: .4em calc(.4em + .6em); border-bottom-right-radius: .4em calc(.4em + .6em); }
のように楕円の縦の半径にボーダーの幅を足すといい感じにニュッと曲がります。
ちなみに黒のボーダーはborder-image
に置き換わるので色はどうでもいいですが見えないだけで場所は取ります。
::beforeで枠をのせる
::beforeのボーダーで枠を作ります。真ん中下のところはclip-path
で切っておきます。clip-path
はパスで囲ったところだけ表示させるプロパティで、ここでは全体をぐるりと囲いつつ消したいところだけ外へ出すような感じです。
::afterでつのを成型する
海苔状の物体は::afterのボーダーです。これをV字に切り抜いて吹き出しのつのにします。
カットして完成
.commentのclip-path
ではみ出している部分を切ったら完成です。
CSSの実際
polygonのストロークをのけると正味30行くらいですからやってることはわりと単純ですがしかし簡単に見えて意外とこのやり方でないとという部分がありますのでいくらか自注をつけたいと思います。
デモ
要素境界を整える
.comment { ... background-clip: padding-box; border-bottom: solid 1em; border-image: linear-gradient(var(--bg-color) 0 1%) 1; -webkit-clip-path: polygon(...); clip-path: padding-box polygon(...); }
background-clip
は背景の範囲を決めるプロパティで初期値はborder-boxです。背景とボーダーはダブるとブレンドされて色が変わってしまうのでpadding-boxを指定して分離します。
困るのがスケーリングが掛かって要素サイズの計算値に1ピクセル未満の端数が出る場合で、そうなるとborder-image
とパディングエリアはぴったりくっつきません。これは仕様によるもの・バグによるものとUAの解釈によるものがあり、微妙に隙間が空いたり重なったりして本当に往生するわけですが「border-width
とborder-image-width
が1:1でborder-image-outset
がゼロ」ならかなりフィットします。つまり何も指定しない時が一番まともです。
border-image
のwidthとoutsetを使う場合単位を付けずに比率で指定し、かつそれぞれ整数倍にしておけば目立つギャップは回避できるようです。
clip-path
の座標計算でも同種の問題にあたりまして単純にボックスの境界でクリップすると削げることがありました。今回の作例ではFFは参照ボックスをpadding-boxにすることでボーダーを食いにくくなりました。webkitは参照ボックスの指定に対応していないのですがもともとの作りとして隙間は空いても削ることはないようです。
続いて::afterでつのの縁取りを作るところです。
.comment::after { box-sizing: content-box; height: calc(100% - var(--border-w)); border-bottom: solid .7em var(--border-color); clip-path: polygon(...); }
つのを枠の内側のラインに合わせるために、枠線一本分低いボックスを作って基準にしています。最初はposition
で位置を合わせようとしたんですが例によって精度が出なかったのでまだマシなheight
を使っています。box-sizing
はどちらでもよいですがborder-boxにしたい場合、箱の外にボーダーを生やすためにborder-image: linear-gradient(var(--border-color) 0 1%) 1 / 1 / 1;
としてアウトセットする必要があるかと思います。
その他の検討事項
最初の思いつきとしてはSVGとborder-image
で楽々と透過吹き出しが作れるのではと思ったんですがそんなことは全くなかった。そこでボーダーと疑似要素をclip-path
で加工する手法を試みましてある程度の成果があったのではないかと思います。実用上の課題としてはpolygonによる切り抜きが面倒なことだと思います。機械的にやれるとよいのですが。それからつのをカーリーにしたい場合ですね。できそうな気はしますが労力を天秤にかけるとそもそも背景画像として用意するかjavascriptで吹き出しのSVGを生成するほうが沼が浅い予感がします。
今回調べてみてサブピクセル絡みのレンダリングギャップは一般化された対処法もなくかなり厄介だなと感じました。私はもう面倒になってしまったのでスケールを基準に考えればむしろ割り切れるピクセル値が異常であり表示位置はズレるほうが逆に自然なのではと思い始めている次第です。
clip-path
はpath()
が使えるのでドローソフトの定義がそのままコピペできる…ことになっているけれども現状では座標がボーダーエリアの左上を原点としたピクセル値で、引数に文字列しか受け付けないためcalc()
も挟み込めないということで実際には適用できるケースのほうが稀ではないかなと思います。Inline SVGの<clipPath>
はややサポートが厚いですが、とは言えCSS側の計算値を反映させる工夫が必要になると簡単ではないと思います。clipやmask用途での使い勝手は数年来あまり変わっていないようでありまして先進的なサイトの素敵なサンプルに化かされて手を出すと肥溜めに肩まで浸かることになるので注意が必要だなと思いました。