😎

隣接する要素のボーダーが重なって太くならないようにbox-shadowでボーダーを表現する

に公開

カレンダーのJSライブラリを月単位で表示することを想像してみてください。横軸は週で最大7つ、縦軸は曜日で最小で4、最大で6です。
そしてすべての項目は1pxのボーダーを指定する必要があるとします。
そのライブラリがtableタグで出力されていたらborder-collapse: collapse;を反映した状態でボーダーを指定すればいいだけです。もしtableタグが使われていなかったとしてもパターンは固定ですからtableプロパティを使用したり、:nth-of-typeプロパティで調整することも難しくありません。

では、次のようなUIを実装するとしたらどうしますか?モバイルでは2カラムだが、タブレットで3カラム、デスクトップで4カラムになるとします。

カラム数が変わるためtableタグやtableプロパティは使えず、border-collapse: collapse;も同様に使えません。

ブレイクポイントごとに:nth-of-typeプロパティでボーダーを調整する方法などが考えられますが、指定がややめんどうになるかもしれません。

完全な代替にはならないのですが、ちょっと便利な方法があります。

borderの代わりにbox-shadowで指定する

指定方法は次のとおりです。

.item {
  box-shadow:
    1px 1px #000,
    inset 1px 1px #000;
}

ここで少しbox-shadowの構文をおさらいします。

/* <inset> | <offset-x> | <offset-y> | <color> */
box-shadow: inset 1px 1px #000;

box-shadow - CSS: カスケーディングスタイルシート | MDN

insetの有無に関わらず<offset-x><offset-y>は同じ挙動です。

  • <offset-x>:水平方向(右)にシャドウが1pxズレる
  • <offset-y>:垂直方向(下)にシャドウが1pxズレる

そしてinsetを省略した場合は要素の外側にシャドウが付きますが、insetを指定すると要素の内側にシャドウが付きます。
たとえば次のように指定すると、

.item {
  border: 1px solid #000;
  box-shadow:
    4px 4px rgb(0 0 255 / 50%), // 青色
    inset 4px 4px rgb(255 0 0 / 50%); // 赤色

このように表示されます。

外側と内側のシャドウが組み合わさることでボーダーのように見えるということです。

問題点

便利なこの方法ですが、実は問題点が2つあります。

右上と左下に1pxずつスキマができている

box-shadowの仕様上、borderと同じ位置に配置することができません。
次のGIFアニメでは黒色をborder、青と赤色をbox-shadowで指定していますが、borderbox-shadowの位置が合っておらず、右上と左下にボーダーがないことがわかります(1pxのスキマなので拡大しないとわからないですが)。

つまり、完全にborderの代わりにはなっていないけれど、簡単な指定でほとんど違和感が出ないくらいの再現ができる方法ということです。

いちばん最初に載せた画像も実は右上と左下にボーダーが1pxずつ足りていませんでした。

ボーダーを2pxにするとスキマも2pxになるので少し目立っていますね。

擬似要素で表示して位置をズラせばborderと同じ配置にはできますが、スキマを埋めることはできません。つまり2px以上の場合に採用することは難しくなります。

角丸にすると線がキタナクなってしまう

たとえば四隅を角丸にしたデザインの場合、角丸部分の線が少し太く表示されてしまいます。
次の画像は左上だけ角丸にしてみました(拡大しています)。

まとめ

  • borderの代わりにbox-shadowを使うとボーダーの重なりを考慮せずに実装ができる
  • ただし2px以上や角丸の場合はアラが目立ってしまう

完璧な方法ではありませんが、問題点だけクリアできれば便利な場面もあるCSSの記述方法でした。

Discussion