border付きboxが隣接するデザインで地味にハマった
何も考えずに 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 の解釈によって、ちょっと表示が崩れそう。(下記微妙に崩れいている)
- 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
まとめの一言が全てですね!
ですね!笑
まあ避けられない場合は頑張るしかないですね…