🎉

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

2022/06/05に公開約4,100字

何も考えずに 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 が埋まりきらなかったときにめんどくさい

まとめ

そもそも border 付き box が並ぶデザインをおすすめしません

Discussion

ログインするとコメントできます