알기 쉬운 BEM 알아보기(CSS)

block__element_modifier모듈 기반의 방법론 (+mix)

공식 문서(https://en.bem.info)에서는 BEM을 이렇게 설명한다.

BEM(Block, Element, Modifier)는 컴포넌트 기반 접근 방식이다. 기본 아이디어는 사용자 인터페이스(UI)를 독립된 블록으로 나누는 것이다. 이를 통해 복잡한 UI도 쉽고 빠르게 개발할 수 있으며, 기존 코드를 재사용할 수 있는 장점이 있다.

BEM은 모듈을 Block, Element, Modifier 단위로 분해하며, 각 앞 글자를 따서 BEM으로 부른다. BEM은 러시아 Yandex사에서 개발한 방법론으로 실제로 이 방법에 기반한 CSS 설계 기법이 널리 사용되고 있지만 BEM은 CSS에만 국한되지는 않는다.

그렇다면 BEM의 공통 규칙과 모듈의 개별 규칙을 알아보자.

CSS style을 위한 BEM 공통 규칙

– ID 셀렉터와 태그 셀렉터를 사용하지 않는다.
– 네스팅된 셀렉터 사용을 최소화한다.
– CSS 클래스 네이밍 컨벤션을 사용하여 이름 충돌을 피하고 셀렉터의 목적을 명확하게 한다.
– Blocks, Elements, Modifiers를 구분하여 사용한다.
– Blocks를 재사용한다.

BEM의 네이밍 규칙

모두 소문자를 사용하며, 두 단어는 하이픈(-)으로 연결한다.

Blockblock, block-two
Elementblock 이름을 상속받아 언더스코어 두 개(__)로 연결block__element, block-two__element-two
ModifierBlock/Element 이름 상속받아 언더스코어 하나(_)로 연결. 키-값 쌍은 스네이크 케이스(_)를 사용.
block_modifier, block__element_modifier, element_key_value
Modifier
(MindBEMding)
Block/Element 이름 상속받아 하이픈 두개(–)로 연결. 키-값에서 키는 생략가능.block–modifier, element–value

모듈의 개별 규칙

논리적, 기능적으로 독립되어 재사용이 가능한 모듈로 정의한다. 클래스 이름은 ‘형태’가 아니라 ‘목적, 용도’를 나타내야 한다.

EX) head, logo, menu (O)
blue-button, small-title (X)

block은 다른 block 내부에서도 사용할 수 있으며, modifier를 통해 위치나 모양을 변경할 수 있다.

그림 – block의 중첩(출처: en.bem.info)

Element

block을 구성하며 block 내부에서만 사용하는 모듈로 정의한다. 예를 들어 menu__item은 menu라는 block을 구성하며 menu 밖에서는 사용할 수 없으므로 element가 된다.

그림 – block을 구성하는 element(출처:en.bem.info)


element 클래스 이름도 block과 동일하게 ‘형태’가 아니라 ‘용도’를 나타내야 한다.
element는 선택적 요소이므로 사용하지 않아도 상관없지만 element 이름을 중첩하여 사용하지 않는다.

EX) block__element__element, menu__button__icon (X)

Modifier

block/element의 모양이나 동작을 정의하는 모듈이다.
modifier는 선택적 요소이므로 사용하지 않아도 상관없지만 단독으로는 사용할 수 없고 block/element와 함께 사용해야 한다.
같은 block이라도 modifier에 따라 모양이 변할 수 있으며, modifier는 런타임이나 block에 따라 바뀔 수 있다.
modifier 클래스 이름은 형태(크기, 색, 상태, 동작)을 나타내야 한다. 형태로 나누면 boolean, key-value 두 가지가 있으며, 각각의 예는 다음과 같다.

– boolean : ex) active, disabled
– key-value : ex) color_red, line_doubled (스네이크 케이스로 표현)

modifier의 수는 제한이 없으므로 여러 개를 붙일 수 있지만 동일한 스타일은 중복하지 않는다.

MIX

mix는 하나의 요소에 역할이 다른 여러 클래스를 사용하는 기법이다.
장점은 코드를 복사하지 않고 기존의 스타일과 조합해서 새로운 모듈로 사용할 수 있다.
block+element, block+block, element+element 등의 mix를 사용할 수 있으며, 샘플 코드를 통해 확인할 수 있다.

<!-- HTML -->
<div class="header">
  <div class="logo header__logo"></div>
</div>

/* CSS */
.logo {
  width:100px;
  height:50px;
}
.header__logo {
  margin: 12px;
}

다른 요소와 관계된 레이아웃과 관련된 부분은 mix, block 내부의 문제는 modifier를 사용하는 것이 좋다.
mix는 독립성과 재사용성이 높은 상태를 유지하는 장점이 있다.


참고
https://en.bem.info
– 다양한 예제로 배우는 CSS 설계 실전 가이드


CSS 설계를 위한 기본 가이드

변경과 확장에 강하고 안정적인 CSS 구조를 위한 기법

다양한 예제로 배우는 CSS 설계 실전가이드에서 소개하는 설계의 핵심을 알아보자.

CSS의 여러 설계 기법들도 결국에는 다음 여덟 가지 리스트에 속한다고 하니 핵심을 관통하는 포인트라고 볼 수 있다.

1. 특성에 따른 CSS 분류
2. 느슨한 스타일의 결합
3. 적당한 영향 범위
4. 특정 컨텍스트에 대한 적절한 의존도
5. 적절한 명시도(specificity)
6. 영향 범위가 유추 가능한 클래스 네임
7. 형태, 기능, 역할의 유추가 가능한 클래스 네임
8. 확장 용이성

1. 특성에 따른 CSS 분류

첫 번째는 CSS 역할이나 특성에 따라 분류하는 것이다.

베이스 그룹 : 사이트에서 베이스가 되거나 공통으로 적용되는 사항 등
레이아웃 그룹 : 헤더(header), 푸터(footer), 콘텐츠(content) 영역 등
모듈 그룹 : 재사용되는 모듈 등

모듈 자체에는 레이아웃과 관련된 부분은 설정하지 않는 것이 좋다. 모듈은 자신의 레이아웃에는 관여하지 않고 해당 모듈 자체의 역할 및 자녀 요소의 스타일링에만 관심을 가져야 한다. 여기서 레이아웃과 관련된 부분이란 다음과 같다.

-position, z-index, top/right/bottom/left, float, width, margin

/* 베이스 */
body {
  font-family: 'Noto Sans';
}

/* 레이아웃 */
layout_content {
  width: 1000px;
  padding: 10px;
  margin: 0 auto;
}

/* 모듈 */
.bl_media {
  display: flex;
  justify-items: center;
  align-items: center;
}

.bl_media_title {
  margin: 5px;
  font-weight: bold;
}

2. 느슨한 스타일의 결합

정확히는 HTML과 스타일링의 느슨한 결합이다. 느슨한 결합이란 의존하지 않는 상태를 이야기하는데 가장 간단한 방법으로는 요소형 셀렉터를 피하고 클래스 셀렉터를 사용하는 것이다.

p{}, div{}, h1{}과 같이 요소의 스타일을 직접 정의하는 요소형 셀렉터는 요소의 변경이 발생하게 되면 스타일이 중복되거나 누락될 가능성이 크므로 클래스를 사용해서 정의하는 것이 좋다.

3. 적당한 영향 범위

영향 범위를 가능한 좁게 하거나 영향 범위가 넓은 CSS의 스타일을 최소화한다.

영향 범위를 고려하지 않은 채 스타일을 추가하다보면 의도하지 않은 부분에서 오류가 발생하므로 최대한 범위를 줄이기 위해 가장 가까운 부모 요소를 포함시키거나 손자와 자녀 셀렉터의 사용도 함께 검토하는 것이 좋다.

4. 특정 컨텍스트에 대한 적절한 의존도

컨텍스트란 위치 또는 상황을 의미하며, 이 컨텍스트가 변할 때 코드가 동작하지 않을 수 있는 상황을 주의해야 한다. 사용 목적이 명확하다면 문제없지만 그렇지 않은 경우에는 셀렉터를 불필요한 부분까지 구체화하게 되면 해당 문제가 발생하기 쉽다.

/* id인 main을 지정하게 되면 해당 모듈 내부에서만 사용 가능한 .title_top 클래스가 되어 의존도가 높아짐. -> 불필요한 #main 제거 */
#main .title_top{
  display:flex;
  align-items: center;
  color: green;
}

5. 적절한 명시도(specificy)

우선순위를 나타내는 명시도와 관련된 내용은 여기를 클릭하면 확인할 수 있다.

명시도가 높은 CSS의 단점은 다음과 같다.

– 셀렉터 예측이 어려움
– 다른 요소(부모 요소 등)에 대한 의존도 상승
– 덮어쓰기의 어려움
– 유지 보수의 어려움

당연히 !important는 사용하지 않는 것이 기본이며, 셀렉터를 사용할 때는 클래스 셀렉터를 사용한다. id 셀렉터는 우선순위가 매우 높으며 한 페이지 안에서 동일한 값은 한번 밖에 사용하지 못하는 제약도 있으므로 id를 사용하는 이점은 많지 않다.

따라서 기본적으로 클래스 셀렉터를 사용해서 의도하지 않은 우선 순위가 뒤섞이지 않도록 해야 한다.

6. 영향 범위가 유추 가능한 클래스 네임

규모가 커질수록 모듈이나 클래스도 늘어나므로 하나의 클래스 수정이 어떤 범위까지 영향을 미칠지를 이름에서 판단할 수 있도록 해야 한다.

HTML에서 정의된 요소의 클래스 네임만 보고도 CSS의 영향 범위를 유추할 수 있도록 하자.

클래스 네임으로 영향 범위를 쉽게 유추하기 위해서는 자녀 요소에 루트 요소의 클래스 네임을 포함하도록 하는 것이 좋다.

7. 형태, 기능, 역할의 유추가 가능한 클래스 네임

영향 범위와 마찬가지로 형태, 기능, 역할도 유추가 가능하도록 네임을 지정해야 한다.

– title1, title2, title3
– main-title, nav-title, sub-title

두 항목 중 형태나 기능, 역할의 유추가 쉬운 쪽이 어디인지는 분명하다. 각 모듈의 기능이나 역할에 맞춰 이름을 붙이는 것이 매우 중요하다. 특히 작은 규모의 코드라면 title1, title2와 같이 네임을 대충 지정하기 쉽다. 하지만 규모란 언제 어떤 방식으로 커질지 모르므로 항상 습관적으로 이름에서 형태, 기능, 역할을 유추할 수 있고 구체성과 범용성의 균형을 맞추도록 하는 노력이 필요하다.

8. 확장 용이성

확장성이란 결국 기능의 추가나 유지와 연결되는 부분으로, 항상 가능성을 열어두어야 한다.

확장이 쉽도록 클래스를 설계하거나 추가하는 클래스에는 기능, 역할에 따라 적절한 상세도와 영향 범위를 갖도록 한다.

확장이 쉬운 클래스란 멀티 클래스를 사용하는 것으로, 하나의 클래스에는 보편적인 속성, 추가 클래스에는 특정 속성을 추가하여 덮어쓰기를 통해 여러 클래스로 스타일을 지정하는 방식이다.

이를 통해 HTML은 복잡해지지만 CSS는 매우 간단해지는 트레이드오프가 발생하기도 한다. 하지만 멀티 클래스의 이점은 불규칙한 상황에서도 클래스 하나만으로 원하는 작업을 완료할 수 있는 간결함이다.

결국 모든 항목은 별개의 사항이 아니라 다 연결되어 있음을 알 수 있고, 작은 수정 하나가 큰 나비효과를 불러올 수도 있다는 점을 명심해야 한다.

매번 하나하나 체크해가면서 코드를 작성하기보다는 작성 방식이 고민되거나 자율성이 주어졌을 때, 해당 리스트를 떠올리면서 작성하다보면 생각보다도 더 견고한 구조를 만들 수 있을 것이다.


참고 : 다양한 예제로 배우는 CSS 설계 실전가이드(2021, 제이펍)

성능에 중요한 repaint, reflow에 대해 알아보자

reflow와 repaint가 발생하는 요인 분석하기

먼저 페이지가 렌더링되는 과정에서 reflow와 repaint의 단계는 다음과 같다.

(DOM + CSSOM) render tree > layout(reflow) > paint(repaint) > composite

여기서 성능과 관련이 깊은 reflow, repaint 단계에 관해 알아보자.

reflow

reflow(또는 layout)는 render tree 생성 후 요소의 크기와 위치를 계산하는 단계이다. 초기 렌더링 시 최초로 발생하고 요소의 크기나 위치 변경, 요소의 추가나 삭제 등 레이아웃이 변경될 때마다 reflow가 다시 발생한다.

reflow는 자식과 부모 그리고 요소 자신에게도 영향을 미치므로 성능에 영향을 주며, 레이아웃을 계산하는 reflow 후 가시적인 요소를 생성하는 paint 단계가 발생한다.

reflow가 발생하는 구체적인 상황은 다음과 같다.
(https://www.geeksforgeeks.org/what-is-dom-reflow/).

Reflow 발생 원인

– DOM에 요소 삽입, 제거, 업데이트
– 페이지에서 수정 작업 발생(Ex. input 박스 텍스트 수정)
– DOM의 요소 이동
– DOM의 요소 애니메이션 효과
– offsetHeight 또는 getComputedStyle()를 사용한 요소 계산
– CSS 스타일 변경
– 요소의 클래스 이름 변경
– 스타일시트 추가 또는 제거
– 윈도우 리사이징
– 웹 페이지 스크롤
– 폰트 변경
– CSS 슈도 클래스(의사 클래스) 활성화(Ex. :hover)
– 요소에 style 어트리뷰트 설정

reflow 단계에서 많은 연산이 발생하고 repaint, composite 단계도 이어서 발생하므로 성능에 영향을 줄 수 밖에 없다. 따라서 페이지를 구성하는 방식, 성능과 애니메이션의 타협 등을 통해 reflow를 최소화하는 방법을 찾아야 한다.

기본적으로 reflow를 줄일 수 있는 방법은 다음과 같다.

Reflow 줄이는 방법

– 여러 개의 인라인 스타일을 지정하지 않고 개별 스타일 설정을 최소화하기
– 요소에 클래스명을 사용하고 DOM 트리에서 최대한 하부에서 사용하기
– 새로운 스타일을 추가하기 전에 DOM에서 요소를 제거한 뒤 다시 추가하기
– 스타일을 위한 잦은 연산 피하기
– 애니메이션은 fixed 또는 absolute로 지정하기
– 가능한 테이블 레이아웃보다 블록 레이아웃을 사용하기
– 고정된 크기로 테이블 생성하기
– 클래스를 통해 스타일 변경하기
– 테이블 레이아웃을 fixed로 지정하기

REPAINT

repaint(또는 redraw)는 레이아웃이 아닌, 요소의 가시적인 부분이 변경되면 발생하는 단계이다. 요소의 visibility, background, outline 등이 변경될 때마다 repaint가 발생하지만 해당 속성은 레이아웃에 영향을 주지 않으므로 reflow는 발생하지 않는다.

repaint는 render tree를 레이어별로 나누어 처리하므로, repaint 발생 시 해당 레이어만 수정할 수 있는 장점이 있다.

Reflow와 Repaint는 stacking context와도 관련이 있는데, stacking context는 간단하게 3차원인 z축을 의미하는 것이다.

여러 요소가 하나의 레이어로 묶이게 되면 차지하는 메모리를 줄일 수 있지만 묶인 요소 중 하나의 요소에서 변경이 생기면 해당 레이어에 있는 요소를 모두 다시 그려야 하는 상황이 발생한다.

따라서 변경이 생길 가능성이 있는 요소를 하나의 레이어로 만들어서 관리하면 해당 레이어만 다시 그리면 되기 때문에 효율적으로 리렌더링을 할 수 있다.

이렇게 해당 요소를 하나의 레이어로 분리하기 위한 조건은 다음 사이트에서 확인할 수 있다.
stacking context 자세히 알아보기(MDN)

조건 중 ‘opacity < 1’이 있는데, opacity 값이 0~0.99인 상황에서만 레이어가 별도로 생성된다는 의미이다.

opacity가 1일 때는 reflow가 발생할 수 있는데 이는 opacity:1인 경우 다른 요소와 하나의 레이어를 이루기 때문에 발생한다고 볼 수 있다.

Composite

상황에 따라 Reflow, Repaint가 발생하지 않는 속성으로 transform, opacity 등이 있는데 이 속성은 렌더링의 마지막 단계인 composite만 실행된다.

해당 속성은 CPU가 아닌 GPU에서 작업을 수행하는 graphic layer를 사용하기 때문인데 composite 단계에서 layer를 병합한다.

GPU는 CPU에 비해 연산이 빠르고 이미지와 애니메이션의 보정 기능도 가지고 있으므로 이미지나 애니메이션 처리에 좋은 대안이 될 수 있다.

GPU를 사용하는 조건은 다음과 같다.

GPU 사용 조건

– 3D transforms 사용(translate3d, translateZ 등)
– <video>, <canvas>, <iframe> 요소 사용
– transform, opacity를 사용하는 애니메이션
– position:fixed 요소
– will-change 사용
– filter 사용

GPU의 장점은 빠른 연산이지만 메모리를 많이 차지하는 단점도 있으므로 적정선에서 사용해야 쾌적한 환경을 만들 수 있다.

참고
Understanding Repaint and Reflow in Javascript
What is DOM reflow
GPU animation: Doing It Right
Stacking Context

CSS의 em, rem, vh, vw, %의 단위를 알아보자

반응형을 위한 단위의 활용

CSS에서 가장 기본이 되는 크기 단위가 픽셀(px)인데요.

1px은 0.00264cm로 환산되는 길이로, 300px은 약 8cm가 됩니다.

16px은 0.42cm인데요. 이 16px이 바로 CSS에서 기본값입니다.

font-size : 16px은 세로 길이가 16px(0.423cm)인 폰트 사이즈라는 의미로, 출판물에서 기본적으로 사용되는 사이즈입니다.

그럼 이 기본 사이즈 16px을 기반으로 em, rem에 대해 알아보겠습니다.

em

em은 기준 크기에 대한 배수를 의미합니다.

기준은 font-size이며 같은 요소의 사이즈에서 시작해 부모로 차근차근 올라갑니다.

예를 들어 같은 요소에 font-size:16px이 설정된 경우 em의 기준은 16px이 됩니다.

.child-size{
    font-size : 16px;
    width : 2em;   // 2 * 16px = 32px;
}

위 코드에서 child-size 내부에 font-size가 있으므로 해당 요소의 width : 2em는 16의 2배인 32px이 됩니다.

만약 다음과 같이 같이 child-size 내부에 font-size가 정의되어 있지 않으면 해당 요소를 감싸고 있는 부모 클래스에서 기준 사이즈를 가져오게 됩니다.

.parent-size{
    font-size : 20px;
}
.child-size{
    width : 2em;   // 2 * 20px = 40px;
}

최상단까지 이동해도 font-size가 없는 경우에는 위에서 말한 기본값인 16px을 기준으로 지정합니다.

rem

rem은 em과 비슷하지만 r은 root를 의미합니다.

따라서 최상단인 html에 정의된 값을 기준으로 사용합니다.

html{
    font-size : 20px;
}
.child-size{
    font-size : 16px;
    width : 2rem;   // 2 * 20px = 40px;
}

최상단의 크기를 기준하므로 20px이 기준이 되어 2rem은 40px로 계산됩니다.

vw

Viewport Width는 스크롤을 포함한 윈도우의 너비를 의미하며, 반응형을 위해 상대적 크기를 사용할 수 있습니다.

1vw는 뷰포트 너비의 1%에 해당하는 값으로 너비가 1000px인 경우 1vw는 10px, 100vw는 1000px이 됩니다.

브라우저의 사이즈 변경이나 모바일 등에서 전체 비율을 맞추고 싶을 때 효과적으로 사용할 수 있으며, 화면을 가로로 가득 채우고 싶을 때 100vw를 사용할 수 있습니다.

.layout{
    width : 100vw;
}

vh

Viewport Height는 윈도우의 높이를 의미하며 vw와 마찬가지로 뷰포트 높이의 1%를 의미합니다.

화면을 가득 채우고 싶을 때는 다음과 같이 너비와 높이를 지정하면 됩니다.

.layout{
    width : 100vw;
    height: 100vh;
}

%

%는 부모 요소에 따라 크기가 결정됩니다.

높이를 100%로 설정하는 경우는 부모의 높이의 100%를 설정하게 됩니다.

따라서 부모의 높이가 100px인 경우 자식 요소에서 height : 100%를 설정하면 높이는 50가 됩니다.

width : 100%인 경우 스크롤바의 너비를 생략한 크기를 나타냅니다.

font-size : 100%의 경우 부모 요소의 사이즈를 기준으로 사용하게 되므로 기본값은 16px이 됩니다.

CSS selector 우선순위, 명시도(specificity)

요소 선택자(Element Selector) 점수 계산하기

CSS에서는 요소의 선택자에 따라 우선순위가 나뉜다.

우선순위 가중치를 통해 스타일을 적용할 순서를 결정하지만 현재는 가중치 계산 방식이 브라우저별로 다르기 때문에 선택자의 상대 우선순위를 알아두는 것이 더 좋다.

예를 들어 기존에는 가중치가 class:10, id:100으로, class 10개가 1개의 id와 동등한 가중치를 가졌지만 현재는 가중치가 달라져 일부 브라우저에서는 256개의 class가 1개의 id와 동등한 우선 순위를 가진다. (참고:stackoverflow)

선택자는 다음과 같은 우선순위를 가진다.

!important > inline style > id > class (pseud0) > element (pseudo)

따라서 선택자별로 상대적인 가중치를 판단하여 높은 순서에 따라 왼쪽부터 comma로 구분하여 계산하는 것이 좋다.

높음inline style , id , class (pseud0) , element (pseudo) <낮음
Ex1) 0 , 1 , 2 , 1
Ex2) 0 , 0 , 200 , 10
👉 Ex1이 Ex2보다 가중치가 높음

예를 들어 다음과 같이 id, class 선택자가 모두 사용되는 경우 우선순위에 따라id 스타일이 적용된다.

.myMenuClass{
  color:green;
}

#myMenuId{
  color:red;
}

<h1 id="myMenuId" class="myMenuClass">This is it</h1>

그리고 가중치의 합산은 다음과 같다.

#myMenuId{
  color:red;
}

.myMenuClass{
  color:blue;  
}

h1#myMenuId{
  color:orange;  
}

<h1 id="myMenuId" class="myMenuClass">This is it!</h1>

우선 순위를 계산해보면,

#myMenuId : 0,1,0,0 / .myMenuClass : 0,0,1,0 / h1#myMenuId : 0,1,0,1

따라서 마지막 orange color가 적용된다.

!important는 모든 가중치를 무시하고 최우선적으로 적용하는 치트키와 같다. !important가 요소에 중복되는 경우 가장 뒤에 선언된 !important가 적용된다.

하지만 !important의 사용&남용은 평화로운 생태계를 위협하는 존재가 될 수 있으므로 최대한 사용하지 않는 것이 정신 건강과 코드 구성에도 좋다.

마지막으로 예외 사항도 알아보자.

우선순위 없음: *,:where()
가중치 포함하지 않음: >, ~, +
가장 높은 가중치 하나만 선택: :is(), :not()
ex) :is(#menuId, .menuId, span) -> 가중치 가장 높은 #menuId만 카운트

Emotion(Styled), Props 전달하기(Typescript)

Typescript와 함께 동적 CSS 스타일 사용하기
import styled from "@emotion/styled";

const StyledPropsTest = styled.div`
  width: 120px;
  height: 40px;
  font-size: 25px;
  background: ${(props) => (props.testProps ? "blue" : "red")};
  color: white;
`;

const StyledTest = ({testprops}) => {
  return(
    <StyledPropsTest testProps={testprops}>Prop Test</StyledPropsTest>
  )
}

export default StyledTest;

컴포넌트 내부에서 사용하는 변수를 CSS에 전달하여 동적 스타일을 적용하기 위해서는 위와 같이 props를 전달하는 방법이 있습니다.

하지만 Typescipt를 사용하여 custom props를 사용하면 다음과 같은 에러를 만나게 됩니다.

(property) testProps: boolean
Type ‘{ children: string; testProps: boolean; }’ is not assignable to type ‘IntrinsicAttributes & { theme?: Theme | undefined; as?: ElementType<any> | undefined; } & ClassAttributes<HTMLDivElement> & HTMLAttributes<…>’.
Property ‘testProps’ does not exist on type ‘IntrinsicAttributes & { theme?: Theme | undefined; as?: ElementType<any> | undefined; } & ClassAttributes<HTMLDivElement> & HTMLAttributes<…>’.ts(2322)

이는 타입스크립트에서 props의 타입을 체크하기 때문인데요.

다음과 같이 간단하게 <{data:type}>의 타입 단언(Type Assertion)으로 해결할 수 있습니다.

const StyledPropsTest = styled.div<{testProps:boolean}>`
  width: 120px;
  height: 40px;
  font-size: 25px;
  background: ${(props) => (props.testProps ? "blue" : "red")};
  color: white;
`;

다음과 같이 에러가 사라진 것을 확인할 수 있습니다.

CSS, calc()를 사용해 main(body) 높이 자동 설정하기

CSS에서 calc 메서드 사용하기

CSS에서 calc() 메서드를 사용하면 window 창의 사이즈 변경에 따라 자동으로 길이를 계산할 수 있습니다.

header, main, footer의 레이아웃을 나눌 때 main에서 calc()를 사용하게 되면 윈도우 창의 크기가 줄어들어도 main의 내용이 잘릴 염려 없이 가변적으로 창의 크기에 맞게 표현할 수 있습니다.

1. 필요성

예를 들면 다음과 같은 상황에서 유용합니다.

위와 같은 화면이 있을 때 윈도우 창의 사이즈를 줄이면(resizing) 가운데 main의 스크롤을 끝까지 내려도 데이터가 모두 표시되지 않을 때가 있습니다.

스크롤을 끝까지 내렸지만 이렇게 r 까지만 표시되고 아랫부분은 짤려서 표시되지 않습니다.

이런 상황에서 main의 div 높이를 계산하여 자동으로 조절해주면 다음과 같이 윈도우가 리사이징 되더라도 화면이 짤리지 않고 유지되는 것을 볼 수 있습니다.

2. 적용하기

main div의 높이(height)에 calc() 메서드를 사용하여 ‘뷰 높이(vh) – 나머지 공간’을 해주면 됩니다.

다만 전달값의 +, – 앞 뒤에는 반드시 공백을 삽입해야 합니다.

// OK
height: calc(100vh - 300px);

// 에러(공백 없음)
height: calc(100vh-300px);

결과는 다음과 같습니다.

리액트, 리렌더링 시 CSS도 함께 리로드하는 방법(feat.animation)

컴포넌트 리렌더링 시 CSS도 함께 리렌더링하도록 만들기

리액트는 내부 로직에 따라 불필요한 렌더링을 최소화하도록 되어있지만 때로는 이 로직이 의도하지 않는 방식으로 작동할 때가 있습니다.

특히 애니메이션 효과를 줄 때 한 번만 실행되고 마는 것이 아니라 클릭 시마다 애니메이션이 동작하도록 만들고자 할 때 다음 방법을 유용하게 사용할 수 있습니다.

원리는 간단합니다.

리액트 컴포넌트는 state가 변경될 때마다 리렌더링을 실행하므로 클릭 시마다 state 값에 변경을 주면 됩니다.

예를 들어 다음 컴포넌트의 이미지를 확인해 보겠습니다.

const ColorChange = ({ color }) => {
  const DisplayBox = styled.div`
    width: 300px;
    height: 300px;
    display: flex;
    background: ${color};
    animation: change 3s;

    @keyframes change {
      0% {
        transition-timing-function: cubic-bezier(1, 0, 0.2, 0.5);
      }
      0% {
        width: 0;
      }
    }
  `;

  return <DisplayBox></DisplayBox>;
};

export default ColorChange;

---------------------------------------------------------------
컴포넌트 호출
<ColorChange color={color} /> 

red, green을 번갈아가며 누르면 컴포넌트에 전달되는 state가 변경되므로 컴포넌트가 리렌더링되면서 애니메이션이 동작합니다.

하지만 red인 상태에서 다시 red를 한번 더 누르면 애니메이션은 동작하지 않습니다.

그럼 클릭마다 CSS가 리렌더링되어 애니메이션이 작동하도록 하려면 어떻게 해야 할까요?

단순하게 클릭마다 전달되는 state의 값이 변경되도록 해주면 됩니다.

예를 들어 다음과 같이 컴포넌트 key 속성으로 임의의 값을 생성하여 전달합니다.

const [randomData, setRandomData] = useState(Math.random());

//버튼 클릭 시 호출 함수
const changeColor = () => {
     // 색상 변경 작업
     .......
     // 임의의 값 생성
     setRandomData(Math.random());
}

<ColorChange color={color} key={randomData} /> 

전달되는 color 값의 변경을 감지하여 리렌더링이 발생하고 그에 따라 CSS animation도 리로드되지만 계속 같은 버튼을 누르면 동일한 color 값이 전달되기 때문에 변경을 감지하지 못해 리렌더링이 되지 않는 원리입니다.

따라서 클릭 시 color 값은 변경되지 않더라도 key 값을 계속 변경하면 리액트는 컴포넌트 변경을 인식하여 계속 컴포넌트와 CSS를 리렌더링하게 됩니다.


렌더링은 최대한 리액트에게 맡기고 불필요한 렌더링은 최소화하되 위와 같이 필요한 부분에만 부분적으로 적용하도록 해야 합니다. 이를 위해서는 작동 방식의 이해가 필요합니다.

CSS, ::before, ::after 간단하게 알아보기

가상 요소(pseudo element) ::before, ::after의 역할

pseudo(슈도)는 [가짜의, 허위의]라는 뜻을 갖고 있습니다.

그렇다면 이 가상 요소가 존재하는 의미는 무엇일까요?

가장 큰 목적은 HTML에 존재하지 않는 요소를 CSS가 임의로 생성해서 스타일링하는 것입니다.

모양은 ::before 또는 ::after이며, 콜론이 두 개(!)가 붙는 것이 가상 요소, 콜론 하나가 가상 클래스입니다(최신 명세).

before와 after의 의미에서 알 수 있듯이 요소의 앞과 뒤에 가상 요소를 생성하여 HTML에 존재하지 않는 요소를 CSS로 생성합니다.

예를 들면 다음과 같습니다.

<h3> - 오늘 할 일</h3> 

위 태그는 다음과 같은 화면을 렌더링합니다.

텍스트에 ‘-‘가 포함되어 있으므로 마우스로 드래그하면 ‘-‘도 함께 텍스트로 선택이 됩니다.

그러나 가상 요소를 사용하면 다음과 같습니다.

‘-‘는 텍스트가 아닌 가상 요소에서 다루고 있으므로 텍스트로 선택되지 않습니다.

// css
.list::before{
  content:'-'
}


// html
<h3 class="list">오늘 할 일</h3>

‘-‘가 h3 태그에는 텍스트로 존재하지 않지만 ::before를 통해 content로 추가해 주었으므로 외형은 위와 같지만 속은 다른 방식이 됩니다.

이 가상 요소를 사용하면 세부적인 스타일링이 가능해집니다.

// css
.list::before{
  content:'';
  display:inline-block;
  width:20px;
  height:20px;
  background:gold;
}

.list::after{
  content:'😋'
}


// html
<h3 class="list">
 오늘 할 일
</h3>

<h3 class="list">
내일 할 일
</h3>

before로 요소의 앞, after로 요소의 뒤를 설정했으며 content에는 문자 이외에도 이모지나 이미지(url(주소)) 등을 사용할 수도 있습니다.

이외에도 ::first-line, ::selection, ::placeholder 등이 있습니다.

추가로 form과 이미지에는 가상 요소가 적용되지 않습니다.

CSS, 알면 쉬운 display 속성(inline, flex, …others)

inline, block, inline-block, flex, inline-flex, grid, inline-grid, none

display는 요소를 보여주고 숨기거나 위치를 설정하기 위한 옵션으로, display만 잘 알아도 레이아웃의 많은 부분을 해결할 수 있습니다.

이번 포스팅에서 display의 속성인 inline, flex, inline-flex, block, inline-block, grid, inline-grid, none의 구현을 통해 각각의 표시 방식을 알아보도록 하겠습니다.

알아보기 편하도록 배경은 블랙, 요소는 각각 레드, 오렌지, 그린으로 표현하겠습니다.

inline : inline으로 표시(width는 요소 크기)
block : block으로 표시
inline-block : inline + block으로 표시
flex : 자식 요소를 모두 inline-block으로 설정
inline-flex : inline + flex(전체 width는 자식 width의 합)
grid : flex처럼 한 방향이 아닌 가로, 세로로 설정 가능한 레이아웃
inline-grid : inline + grid(전체 width는 자식 width의 합)
none : 표시하지 않음(공간을 차지하지 않음)


줄바꿈 없이 태그를 표시하는 요소를 inline 요소, 태그마다 줄바꿈이 되는 요소를 block 요소라고 합니다.

대표적인 inline 요소는 <span>, <a> 등이 있으며, block 요소는 <div>,<p>,<h1>,<h2> 등이 있습니다.

먼저 아무 속성도 설정하지 않은 div를 확인해 보겠습니다.

.setContainer{
  background-color:black;
}

.setOne{
  background-color: red;
}

.setTwo{
  background-color: orange;
  width:95%;
}

.setThree{
  background-color: green;
  width:90%;
}

<div class='setContainer'>
  <div class='setOne'>
    red
  </div>
  <div class='setTwo'>
    org
  </div>
  <div class='setThree'>
    grn
  </div>
</div>

div의 default 설정은 width 100%입니다.

기본 설정을 확인하기 위해 setTwo와 setThree에 width를 설정한 결과입니다.

먼저 display의 inline 속성을 확인해보죠.


1. inline

위 속성은 말 그대로 요소를 일렬로 늘어선 방식으로 표시하며 줄바꿈을 하지 않습니다.

각 요소마다 설정하며 width는 자동으로 크기에 맞게 변형됩니다.

.setContainer{
  background-color:black;
  display:inline;
}
.setOne{
  background-color: red;
  display:inline;
}
.setTwo{
  background-color: orange;
  display:inline;
}
.setThree{
  background-color: green;
  display:inline;
}

<div class='setContainer'>
  <div class='setOne'>
    red
  </div>
  <div class='setTwo'>
    org
  </div>
  <div class='setThree'>
    grn
  </div>
</div>

inline은 위와 같이 문자열처럼 일렬로 늘어선 상태로 표시됩니다.


2. block

block은 inline 요소를 block으로 표시합니다.

block 요소 div 대신 inline 요소 span을 사용하면 다음과 같은데요.

위에서 div에 display:inline을 설정한 것과 같은 모양입니다.

그러므로 span에 display:block을 설정하면 반대로 div를 사용한 것과 같은 결과가 나오는 것을 알 수 있습니다.

.setContainer{
  background-color:black;
}
.setOne{
  background-color: red;
  display:block;
}
.setTwo{
  background-color: orange;
  display:block;
}
.setThree{
  background-color: green;
  display:block;
}

<div class='setContainer'>
  <span class='setOne'>red</span>
  <span class='setTwo'>org</span>
  <span class='setThree'>grn</span>
</div>

block 속성은 inline 요소를 block 요소처럼 표현하는 것을 알 수 있습니다.


3. inline-block

그렇다면 inline-block은 언제 사용할까요?

display:block이 필요하지만 표시는 inline으로 표시를 하고 싶은 상황입니다.

그러면 그게 그냥 span의 default 아닌가라고 생각할 수도 있지만 span에는 width, height 등 속성이 적용되지 않기 때문에 block으로 설정을 해야 합니다.

위의 코드에서 첫 번째 요소에 width:300px, height:200px를 설정하면 다음 그림과 같은 모습이 됩니다.

위 요소를 inline으로 설정하기 위해 inline-block를 사용할 수 있습니다.

.setContainer{
  background-color:black;
}
.setOne{
  background-color: red;
  display:inline-block;
  width:300px;
  height:200px;
}
.setTwo{
  background-color: orange;
  display:inline-block;
}
.setThree{
  background-color: green;
  display:inline-block;
}

<div class='setContainer'>
  <span class='setOne'>red</span>
  <span class='setTwo'>org</span>
  <span class='setThree'>grn</span>
</div>

div를 사용해 위와 같이 나타내려면 어떻게 해야 할까요?

.setContainer{
  background-color:black;
}
.setOne{
  background-color: red;
  display:inline-block;
  width:300px;
  height:200px;
}
.setTwo{
  background-color: orange;
  display:inline;
}
.setThree{
  background-color: green;
display:inline;
}

<div class='setContainer'>
  <div class='setOne'>
    red
  </div>
  <div class='setTwo'>
    org
  </div>
  <div class='setThree'>
    grn
  </div>
</div>

위와 같이 사용하면 됩니다.

만약 위와 같이 사각형 사이 사이에 빈공간이 들어가는 경우가 있다면 <div> 태그 사이의 띄워쓰기나 줄바꿈을 확인해봐야 합니다.

보기에는 안좋지만 다음과 같이 태그 사이를 모두 붙여쓰면 공백이 발생하는 것을 제거할 수 있습니다.

<div class='setContainer'><div class='setOne'>red</div><div class='setTwo'>org</div><div class='setThree'>grn</div></div>

4. flex

flex는 새로운 속성 중 하나로 부모 요소에서만 설정을 합니다.

display:flex를 설정하면 모든 자식 요소에 display:inline-block을 설정한 것과 같은 효과를 줍니다.

따라서 모든 자식 요소는 display를 설정할 필요 없이 width, height 등의 속성이 사용 가능합니다.

.setContainer{
  background-color:black;
  display:flex;  
}
.setOne{
  background-color: red;
}
.setTwo{
  background-color: orange;
  width:200px;
}
.setThree{
  background-color: green;
}

<div class='setContainer'>
  <div class='setOne'>
    red
  </div>
  <div class='setTwo'>
    org
  </div>
  <div class='setThree'>
    grn
  </div>
</div>

개별 요소에 모두 display를 설정할 필요 없이 부모 요소에서만 편하게 설정이 가능합니다.

만약 자식 요소를 가운데 정렬하고 싶으면 부모 요소에서 justify-content:center;를 사용하면 됩니다.


5. inline-flex

flex는 이미 inline-block의 기능을 갖고 있는데 그럼 inline-flex의 기능 무엇일까요?

바로 inline 특징 중 하나인 자동 크기 조절입니다.

inline-flex는 자식 요소를 합친 크기 만큼만 크기를 설정하므로 width는 100%가 아닌 자식 크기의 합입니다.

flex의 결과와 비교해보면 알 수 있듯이 부모 요소를 의미하는 블랙이 공간을 차지하고 있지 않습니다.


6. grid, inline-grid

이 grid는 무엇일까요?

그리드는 flex처럼 가로나 세로 한 방향으로만 레이아웃을 설정하는 것이 아니라 가로, 세로 두 방향 모두 설정할 수 있는 속성입니다.

grid 역시 부모 요소에서 설정을 하며 자식 요소를 block으로 표현합니다.

다양한 속성을 통해 레이아웃을 구성하는데 효과적이지만 내용이 많아 다른 포스팅을 통해 좀 더 알아봐야 할 필요성이 있을 것 같습니다.

.setContainer{
  background-color:black;
  display:grid;  
  grid-template-columns: 100px 100px 100px;
}
.setOne{
  background-color: red;
}
.setTwo{
  background-color: orange;
}
.setThree{
  background-color: green;
}

<div class='setContainer'>
  <div class='setOne'>
    red
  </div>
  <div class='setTwo'>
    org
  </div>
  <div class='setThree'>
    grn
  </div>
</div>

inline-grid 역시 inline-flex 속성과 마찬가지로 width를 자식 요소에 맞게 조절하는 기능을 합니다.


7. none

none은 해당 요소를 표시하지 않으며 공간도 차지하지 않는 방식입니다.

.setContainer{
  background-color:black;
}

.setOne{
  background-color: red;
  display:none;
}

.setTwo{
  background-color: orange;
}

.setThree{
  background-color: green;
}


<div class='setContainer'>
  <div class='setOne'>
    red
  </div>
  <div class='setTwo'>
    org
  </div>
  <div class='setThree'>
    grn
  </div>
</div>

이렇게 없는 놈 취급을 합니다.

이와 반대되는 속성이 visibility로 이는 공간은 차지하되 표시만 하지 않는 속성입니다.

visibility, display 사용 관련 포스트

display:none을 visibility:hidden으로 변경한 결과입니다.

있는데 없는 척을 합니다.

물론 visibility도 공간만 차지할 뿐 할 수 있는 일은 없습니다.



CSS의 기본이지만 모든 기능을 다 이해하고 사용하기까지는 어느 정도의 시간과 경험이 필요한 것 같습니다. 무작정 외우기보다는 사용하면서 답답함이 쌓였을 떄 한번씩 소화제 느낌으로 설명을 삼켜주면 조금 더 와닿는 설명이 되지 않나 싶습니다..🧔