🎉

border付きboxが隣接するデザインで地味にハマった

2022/06/05に公開
2

何も考えずに border がついた box を並べると、隣接する border が被って2重になります。それを回避する方法をいくつか検討してみました。もっといい方法あったらぜひ教えてほしい…

検討した方法

方法 概要 個人的おすすめ度
1. flexbox/隣接borderを調整する 隣接するborderを半分ずつにする 1
2. flexbox/マイナスマージン マイナスマージンで打ち消す 2
3. flexbox/boxshadow box-shadowで外側にborderをつける 4
4. flexbox/border + boxshadow borderとbox-shadow半分ずつ 4
5. flexbox/疑似要素 疑似要素で上からborderをかぶせて、ずらす 5
6. table border-collapse: collapse; 2
7. grid 背景をborder色にして、gridでboxを被せる 1

(数字が大きいほどおすすめ)
私は結局疑似要素使いました。

前提

  • box をコンポーネント化して使うイメージ
  • box-sizing: border-box
  • サンプルはわかりやすさのため、基本的に box が 100px 幅、border が 5px
  • 高解像度のディスプレイ(retina くらい…?)を除いて、0.5px というのは表現できないようです。0.5px ずらす、とかもできず、0.5px がどう扱われるかはブラウザによる…?chrome では1以下は1,それ以上は切り捨てられているように見えます。

参考(retina で見た場合と、それ以外のディスプレイで見たときに見え方が違うかと思います)

高解像度ディスプレイ(0.5px 刻みで表示)

普通のディスプレイ(0.5/1/1.5px がすべて同じ幅になっている)@chrome

1. flexbox/隣接 border を調整する

2重になるところのマージンを半分(例だと 2.5px)にして隣接させる方法。(box の内に 5px の border)

  • メリット
    • 幅は全ボックス共通
  • デメリット
    • flex-wrap: wrap で複数行になった場合とか、上下の重なりがちょっとめんどい
    • box をコンポーネントとして作成している場合、子側で border を設定するのが難しいのでイマイチ?
    • 0.5px が表現できないので、ディスプレイによっては 2px ずつになってしまい、隣接時の border が細くなる

2. flexbox/マイナスマージン

box を少し大きめに作って、2重になるところをマイナスマージンで打ち消す方法。(box の内に 5px の border)

  • メリット
    • 0.5px を使わなくていいので、ディスプレイ間の差異がない
  • デメリット
    • 上下の重なりがちょっとめんどい
    • width の計算がめんどい
    • (設定の仕方にもよるが)一番左とそれ以外で box 内部のサイズが違う
      • 一番左:box サイズ - ボーダー分 ✕2(例だと 100px - 10px = 90px)
      • それ以外:box サイズ - ボーダー分(例だと 100px - 5px = 95px)

3. flexbox/box-shadow

box-shadow で box の外側にボーダーをつけています。
隣接する要素は後から来たものが上にかぶさるので、ボーダーが2重になりません。(box の外に 5px の border)

  • メリット
    • 簡単
    • 子側で設定すれば、親側は並べるだけでいい
    • 上下に並んでも問題なし
    • 0.5px 問題を気にしなくていい
  • デメリット
    • 複数行に折り返してすべて埋まらなかったケースが辛い
    • 一番右とそれ以外で box 内部のサイズが違う
      • 一番右:box サイズ(例だと 100px)
      • それ以外:box サイズ - ボーダー分(例だと 100px - 5px = 95px)

例えばボーダーが 1px の場合上記デメリットはほとんど気づかないと思うので、それが許容できるのならこれが一番簡単かなと。

4. flexbox/2.5px ずつ

border を 2.5px、box-shadow を 2.5px。

  • メリット
    • 子側で設定すれば、親側は並べるだけでいい
    • 幅は全ボックス共通
    • 上下に並んでも問題なし
  • デメリット
    • 0.5px 単位が表現できないため、ディスプレイによって border 幅が変わってしまう。0.5px の解釈によって、ちょっと表示が崩れそう。(下記微妙に崩れいている)

border が偶数 px 幅の場合はこれが最強かもしれないです。

5. flexbox/疑似要素

box より少し大きなサイズの疑似要素を作り、その中に border を引き、position: absolute で border の半分の長さ分ずらす方法。(box の内に 2.5px、外に 2.5px の border。実質 box-sizing: border-box にはなっていない)

  • メリット
    • 子側で設定すれば、親側は並べるだけでいい
    • 幅は全ボックス共通
    • 上下に並んでも問題なし
  • デメリット
    • position: absolute 使ったり疑似要素使ったり若干トリッキー?
    • 0.5px 単位でずらせないので、ディスプレイによっては全体が想定より 0.5px ずれている(言い方がややこしいですが、2.5px ずれているはずが、2px しかずれていない、ということが起こるという意味です)

なお、疑似要素を上からかぶせているので、疑似要素にはpointer-events: none;を設定する必要があります。
今回はこれを採用し、mixin を作って適用しています。

@mixin border($border-color: $color-black, $border-radius: 0) {
  &::after {
    position: absolute;
    top: -0.5px;
    left: -0.5px;
    display: block;
    width: calc(100% + 1px);
    height: calc(100% + 1px);
    pointer-events: none;
    content: '';
    border: 1px solid $border-color;
    border-radius: $border-radius;
  }
}
.hoge {
  color: $color-black;
  @include border($border-radius: 2px);
}

6. table

table の border-collapse: collapse; でボーダーを重ならないようにする方法。(box の内に 5px の border)

  • メリット
    • 簡単
    • 幅は全ボックス共通
    • 上下に並んでも問題なし
  • デメリット
    • SP/PC で DOM 構造が変わってしまう(PC では 1 行に 3 個、SP では 1 個とかのときに、別々にマークアップする必要あり)
    • 自動で複数行に折り返すみたいなことはできない、はず。
    • セマンティクス的に…?

7. grid

背景を真っ黒にして grid で配置し、上に要素をかぶせて border っぽくする

  • メリット
    • 幅は全ボックス共通
    • 上下に並んでも問題なし
  • デメリット
    • 幅の調整とかめんどくさい
    • box が埋まりきらなかったときにめんどくさい

まとめ

例えば1px幅のボックスであればそこまで厳密にやる必要はないのかもしれませんが…
そもそも border 付き box が並ぶデザインをおすすめしません。。

Discussion

atiasatias

まとめの一言が全てですね!

kzk4043kzk4043

ですね!笑
まあ避けられない場合は頑張るしかないですね…