문제 상황‘배경’용 border, ‘강조’용 border의 스타일 차이기존 방법layout shift의 원인 분석해결 방법1. transform으로 ‘카운터’치기(보정하기) ❌2. border-width를 일치시키기 ⚠️3. box-shadow로 기본 border 대체하기 ✅발상적용결론box-shadow는 그림자도 되지만 border도 된다.여러 스타일의 border가 필요할 때는 border만으로는 layout shift가 발생할 수 있다.
문제 상황
‘배경’용 border, ‘강조’용 border의 스타일 차이
기존 방법
- 아래와 같이 ‘축구장’ 형태의 레이아웃을 구성하면서, 기본 격자를 border로 처리했습니다.
- 격자를 border로 처리하는 것까지는 좋은 선택이었는데요…
- 이후 ‘선택됨’ 상태도 동일하게 border로 구현하려고 하니 문제가 발생했습니다.
- 강조 표시(선택됨 상태)로 전환 시 layout shift가 발생했습니다.
layout shift의 원인 분석
- border가 right, bottom에서 all로 변하고,
- width가
1px -> 3px
로 border-width가 변하기 떄문입니다.
해결 방법
1. transform
으로 ‘카운터’치기(보정하기) ❌
- 기본 상태와 선택됨 상태의 차이는 눈 대중으로 보았을 때 (x,y) 좌표 차이로 보여서,
translate(x,y)
를 시도했습니다. - CSS:
transform: 'translate(-0.5px, -0.2px)'
translate
로도 layout shift가 거의 없게 근사할 수 있지만 미묘하게 움직이는 느낌은 계속해서 남아 있었고, CSS에 매직 넘버도 추가되어서 좋은 방법은 아니었습니다.
- 코드
// Vanilla Extract / CSS 일부만 발췌 export const commonCellContainer = recipe({ base: { borderRight: `1px solid ${lightThemeVars.color.field.lineLight}`, borderBottom: `1px solid ${lightThemeVars.color.field.lineLight}`, }, variants: { isSelected: { true: { transform: 'translate(-0.5px, -0.2px)', border: `3px dashed ${lightThemeVars.color.white.main}`, }, }, },
- 화면
2. border-width
를 일치시키기 ⚠️
- layout shift는 결국 border의 면적을 항상 일치시키는 것이었습니다.
- 기본 상태의 레이아웃도 선택됨 상태와 마찬가지로
3px
을 유지하니 layout shfit가 사라졌습니다. border: 3px solid transparent
- 단, 이렇게 하면 실제 디자인과 많이 달라지기 때문에 사용할 수는 없었습니다.
- 화면
3. box-shadow
로 기본 border 대체하기 ✅
발상
- 예전에 CSSBattle 등에서 여러 개의 border를 동시에 사용하던 것이 생각났습니다.
- 당시 outline, box-shadow를 활용했던 것이 기억났고, box-shadow로 border처럼 렌더링할 수 있는지 확인해보았습니다.
적용
- 다행히도 box-shadow로 쉽게 가능했어서, 결과적으로는
border: 3px
,로 배치해서 layout shift를 방지하고, 대신 기본 선택됨 색상은transparent
로 설정했습니다. - CSS:
border: '3px solid transparent'
- 이후
box-shadow
로 기본 border 표시를 대체할 수 있었습니다.
// Vanilla Extract / CSS 일부만 발췌 export const commonCellContainer = recipe({ base: { border: '3px solid transparent', boxShadow: `0 0 0 1px ${lightThemeVars.color.field.lineLight}`, }, variants: { isSelected: { true: { border: `3px dashed ${lightThemeVars.color.white.main}`, }, }, }, });
- 화면