예제와 함께 알아보는 CSS flex 와 Flexbox — 코딩크리처
제목 : 예제와 함께 알아보는 flex 와 Flexbox
이 글을 작성하는 이유 :
다양한 크기의 디바이스가 웹에 접속하는 시대이기 때문에,
디바이스 크기에 따른 유연한 확장과 축소가 필수인 시대가 되었다.
핸드폰부터 노트북, 커다란 모니터까지 호환이 가능한 스타일링을 추구하는 시대,
즉, "반응형 웹 디자인" 이 필수적인 시대가 되었다.
2000년대 초중반, HTML 태그를 통해 일방적인 정보의 소통 (Server --> Visitor)
이 이루어지며, 우리가 하얀 바탕에 기본적인 태그를 작성했을 때의 스타일이 주를 이루었다.
table, ul, ol, li, iframe 과 같은 간단한 태그를 이용하여
정보의 가독성을 확보하고자 하였다.
그리고 시간이 흘러 2020년 중반이 되었다.
제각각의 규칙을 가지고 있었던 사이트들이, 이제는 서로에게 약간의 변화가 있을 뿐,
대부분의 경우 공통된 컨벤션을 따라 웹이 제작되고 있다.
다른 것이 있다면, 웹을 실행하는 디바이스 성능의 향상에 따라,
웹을 제작하는 개발자의 입맛대로 기능적 컴포넌트의 Customization 이 가능해졌다는 것이다.
이제는 엘리먼트가 각자 가지고 있는 "Box-Model" 을 스스로 스타일링하며,
블럭 스타일 display : block 에서 나타낼 수 있는 스타일의 한계를 풀어 준 것이다.
이 과정에서 가장 많이 사용된다고도 말할 수 있는 표시 형식은, display : flex 이다.
Horizontal, Vertical, 이 둘 중 한 가지 모드로,
원하는 방향으로 출력 할 수 있으며,
엘리먼트의 정렬 형식을 Direction 에 따라 커스텀 할 수 있다는 것이 크다.
뿐만 아니라, 모든 디바이스의 출력에 알맞게 표시하기 위한 가장 중요한 요소 중 하나이다.
가로로 펼쳐진 옵션 8 개가 모니터 상에서 알맞게 표시되었을 때,
핸드폰에서는 8 개의 옵션을 알맞는 형식으로 줄넘김 해 주는 옵션, flex-wrap : true
와 같은 성질을 display : flex 가 충족 해 주기 때문이다.
그리고 특히, 웹을 제작하며 가장 많이 사용하게 될 Property : Value 중 하나인 flex 를,
제대로 공부하고 넘어가지 않는다면, 이는 기술적 부채로 이어질 것이 분명하다는 생각이 들었다.
이를 예방하기 위해, Flex, 그리고 Flexbox 를 제대로 공부하고 기록한다.
display 와 Flex 에 대해서 간단히 짚고 넘어가자.
display 라는 css 속성에는 정말 다양한 값이 들어 갈 수 있다.
이 display 라는 속성에 대해서 적어놓은 글이 있으니,
만약에 display 가 뭔지 정확히 모른다면,
CSS 레이아웃, display 에 대해서 알아놓자 를 보고 오는 것이 좋다.
display 라는 값은 "이 엘리먼트의 출력 형식" 을 정해주는 것이다.
이 display 라는 것을 정확히 따져본다면, 아주 추상적으로 이런 것을 정한다.
부모나 형제에게 있어, 자신은 어떤 형태로 인식되는가?
자신에게 속한 자식 혹은 손자 엘리먼트들은 어떻게 표시되는가?
브라우저 자체에게 있어 자신은 어떤 형태로 인식되는가?
이를 정하는 것이 display 라고 간략하게 말할 수 있다.
만약에 inline xxx or inline-xxx 라고 붙여진 속성이라면,
위의 계층에서 나를 바라보았을 때, "글자" 처럼 취급된다.
만약에 그렇지 않고, block, flex, grid, table, .... 등등
단순 Property 로 구성되었을 경우, 이는 상위 계층 혹은 동일 계층에서 있어
인식되었을 때, "영역" 으로 인식된다.
만약에 inline-flex or inline flex 라면?
display : inline-flex or inline flex 라고 정해졌을 경우,
이 엘리먼트를 감싸는 개체는 이를 텍스트처럼 배치한다.
display : flex 라면, 외부에서 바라보았을 때는 display : block 처럼 영역을 가진다.
둘의 차이점은, 외부에서 바라보는 "배치의 차이점" 인 것이다.
공통점은, 내부 엘리먼트를 "어떻게 배치 할 것인가?" 인 것이다.
flex 에 대한 이론적인 기초
flex 라는 속성은, 내부의 엘리먼트를 1차원 배열 형태로 나열시키는 속성이다.
단, 이 속성에 대한 기능적인 부분이 굉장히 세세하여 따로 분야가 나뉠 정도이다.
기본적으로 display : flex 만 선언 될 경우,
내부 엘리먼트들은 Horizontal(수평) 방향으로 왼쪽에서 오른쪽으로 배치된다.
display : flex 가 선언되어야, flex 와 직접 관련된 속성들이 효과를 발휘한다.
또한, display : flex 속성은, 직속 자식 엘리먼트들을 flex-item 이라는 관점으로 보게 만든다.
display : flex 가 선언된다 해도, 하위 요소의 display 속성 값은 변하지 않는다.
본격적으로 시작하기 전에, 이 블로그에 사용될 CSS 예시의 형태
이를 작성하는 이유는, 이 글을 읽으시는 독자분들의 디바이스나 브라우저에 따라,
혹은 설정한 커스텀 속성에 따라 iframe 이 보이지 않을 수 있기 때문이다.
<iframe
width="70%"
max-height="20rem"
srcdoc='
<style>
body {
background : white;
}
</style>
<body>
기본 상태는 이러합니당
</body>
'
>
<p>이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
</iframe>
Preview :
'
>
이 문구가 보인다면, iframe 이 막혀 CSS 예시를 볼 수 없습니다.
end
보통은 이 iframe 을 외부 리소스를 끌어오는 용도로 사용하고,
나처럼 이렇게 사용하는 것은 일반적인 행동은 아니라는 것을 알아주었으면 좋겠다.
보통은, src : ..... 로 페이지를 보여준다.
일단 잠깐 보고 시작하는 block 과 flex 의 차이
block 은 어떻게 표시될까?
block 은, 기본적으로 "하나의 영역에 하나의 줄" 규칙이 적용된다.
따라서, article, div, section 과 같은 기본 영역 구분자들이 하나의 줄에 하나씩 배치된다.
어짜피 3 개 모두 같은 CSS 속성을 Default 로 가지고 있어서,
div 내부의 section 3 개가 어떻게 배치되는지 집중해서 보도록 하자.
div {
/* Default 속성 */
display : block;
}
section {
border : 1px solid black;
border-radius : 4px;
}
<body>
<div>
<section>Section 1</section>
<section>Section 2</section>
<section>Section 3</section>
<section>Section 4</section>
</div>
</body>
Preview :
end
위의 예제를 본다면, 기본 배치의 경우,
하나의 블록에 하나의 Line 이 할당되는 것을 볼 수 있다.
이제, div 가 flex 가 되면 어떻게 될까?
flex 는 어떻게 표시될까?
위에서 말했듯, flex 는 내부 엘리먼트들을 1차원 형태로 표현한다.
위의 예시를 그대로, flex 로만 변경한다.
div {
/* 자식 엘리먼트 flex 화 */
display : flex;
}
section {
border : 1px solid black;
border-radius : 4px;
}
<body>
<div>
<section>Section 1</section>
<section>Section 2</section>
<section>Section 3</section>
<section>Section 4</section>
</div>
</body>
Preview :
End
위의 예제를 보면, 상위 요소인 div 의 display 가 flex 가 됨에 따라,
하위 요소인 section 들이 "가로"(Horizontal or row) 로 정렬 된 것을 볼 수 있다.
즉, block 과 flex 는 이러한 차이점을 가지고 있다.
Flexbox 는 무엇일까?
web.dev 에서 Flexbox 에 대해서 알려주는 이론적인 문장은,
"Flexbox 는 1차원 콘텐츠용으로 설계된 레이아웃 모델이다."
"크기가 다른 여러 항목을 가져와서, 해당 항목에 가장 적합한 레이아웃을 반환하는 데 탁월하다"
라고 한다.
만약에 이 글을 실제로 읽고 있는 독자가 있다가 잠깐 보고 오기를 권장하는데,
하이라이트 링크 주소는 : 플렉스 레이아웃으로 무엇을 할 수 있나요?
나는 "플렉스 박스란 무엇인가?" 를 보면서, 오히려 이런 생각이 들었다.
"왜 플렉스 박스란 단어가 탄생했는가?"
기본으로 설정되던 display : block 과 달리,
display : flex 를 선언하면, 내부의 엘리먼트들은 이 선언으로 인해
시각적으로 정말 다양한 요청을 수행할 수 있다.
Detail 한 정렬
이 블록 내부의 컨텐츠가 너비를 넘어섰을 때
작성 순서 (x, y 축에 대해 반대로 가능)
이것은 순전히 나의 생각이고 판단인데,
1차원 DOM 나열 형태에 있어,
기본 display 에 비해 풍부한 기능과 생태계로 인해,
이를 직관적으로 설명하기 위해 Flexbox 라는 단어를 사용하여 flex 를 설명하지 않나 싶다.
flex-direction 이란? - (작성 방향에 대해서)
flex 는 1차원 엘리먼트 나열 방향에 대해 다양한 선택지를 제공한다.
row --> Default
row-reverse
column
column-reverse
한번, 예제로 알아보자.
각 flex-direction 에 따른 예제.
한번 간단한 DOM 예제에, flex-direction 만 추가되고 변형 된 상태를 각각 살펴보자.
flex-direction : row 의 경우 :
div {
display : flex;
flex-direction : row;
}
/* 스타일 가독성을 위해 넣은 속성. */
section {
margin : 10px;
padding : 10px;
border : 2px solid black;
border-radius : 3px;
}
공통 HTML :
<body>
<div>
<section>섹션 1</section>
<section>섹션 2</section>
<section>섹션 3</section>
<section>섹션 4</section>
</div>
</body>
Preview :
end
flex-direction : row-reverse 의 경우 :
div {
display : flex;
flex-direction : row-reverse;
}
/* 스타일 가독성을 위해 넣은 속성. */
section {
margin : 10px;
padding : 10px;
border : 2px solid black;
border-radius : 3px;
}
Preview :
end
flex-direction : column 의 경우 :
div {
display : flex;
flex-direction : column;
}
/* 스타일 가독성을 위해 넣은 속성. */
section {
margin : 10px;
padding : 10px;
border : 2px solid black;
border-radius : 3px;
}
Preview :
end
flex-direction : column-reverse 의 경우 :
div {
display : flex;
flex-direction : column-reverse;
}
/* 스타일 가독성을 위해 넣은 속성. */
section {
margin : 10px;
padding : 10px;
border : 2px solid black;
border-radius : 3px;
}
Preview :
end
flex-wrap 이란? - 오버플로를 막아주는 기능
나는 이 기능이 각종 디바이스의 크기에 따라 자신의 스타일 형식을 유지 해 주는
중요한 기능이라고 생각한다.
display : flex && flex-wrap : wrap 를 선언하면,
1차원 나열 형태로 선언된 박스가, 상위 엘리먼트의 크기를 고려하여,
내부 엘리먼트가 Overflow 되지 않도록 만들어 준다.
상위 엘리먼트의 크기는 고정되어 있을 수도, 혹은 사용자의 디바이스에 따라 증감할 수도 있다.
flex-wrap : wrap 선언은, 반응형 웹 디자인에 가장 걸맞는 속성이지 않을까 생각된다.
한번 예시를 보자.
flex-wrap 이 없어 오버플로가 되는 상황 :
div {
display : flex;
/* Elem 을 담는 컨테이너 최대 너비를 300px 로 고정. */
max-width : 300px;
border : 3px solid dodgerblue;
gap : 1rem;
}
section {
/* 애매하게 2 개 까지는 오버플로가 발생하지 않게 설계 */
width : 120px;
border : 2px solid black;
/* section 은 무조건 정해진 너비를 지킬 수 있게 설정 */
/* 아니라면, 120px 너비를 유지하지 못하고, 좁은 div 에 맞춰 너비가 줄어든다. */
flex-shrink : 0;
}
<body>
<div>
<section>120px 크기의 섹션 1</section>
<section>120px 크기의 섹션 2</section>
<section>120px 크기의 섹션 3</section>
</div>
</body>
Preview :
end
위의 예제를 보면, 고정된 section 태그의 120px 로 인해,
자신을 감싸고 있는 div 의 영역까지 넘어가는 것을 볼 수 있다.
flex-wrap 이 선언되어 오버플로를 막는 상황 :
div {
display : flex;
flex-wrap : wrap;
/* Elem 을 담는 컨테이너 최대 너비를 300px 로 고정. */
max-width : 300px;
border : 3px solid dodgerblue;
gap : 1rem;
}
section {
/* 애매하게 2 개 까지는 오버플로가 발생하지 않게 설계 */
width : 120px;
border : 2px solid black;
/* section 은 무조건 정해진 너비를 지킬 수 있게 설정 */
/* 아니라면, 120px 너비를 유지하지 못하고, 좁은 div 에 맞춰 너비가 줄어든다. */
flex-shrink : 0;
}
Preview :
end
이번에는 flex-wrap : wrap 이 선언되어,
상위 엘리먼트의 최대 크기인 300px 을 오버플로 하지 않고,
다음 줄로 줄넘김이 된 것을 볼 수 있다.
이번 flex-wrap 예제에서 주의깊게 보아야 할 것은 무엇일까?
나는 오히려 "Overflow 를 유발" 하기 위해 하위 자식인 section에,
flex-shrink : 0 을 선언하여, 고정된 width 120px 를 유지할 수 있게 만들었다.
flex 선언시 기본적으로 flex-shrink : 1; 의 값을 가진다.
따라서, 하위 값은 고유의 너비가 있음에도 불구하고,
오버플로를 막기 위해 고유의 너비보다 더 작은 너비로 세팅된다.
즉,
flex-shrink : 1 : 상위 요소에 따라 너비가 줄어들 수 있다.
flex-shrink : 0 : 상위 요소의 크기가 어떻든 자신의 크기를 유지한다.
flex 요소의 내부 공간 제어하기
display : flex 선언 시, 직속 항목들은 Flex 와 관련된 일련의 속성을 선언 할 수 있는데,
이는 flex 에 의해 형성되는 공통 디자인에서 하위 요소가 가질 수 있는 개별 속성을 지정하는 것이다.
바로 직전의 예시에서, 나는 Overflow 를 유발하기 위해,
하위 항목인 section 태그에 flex-shrink : 0 을 선언했다.
이 속성이 적용될 수 있는 이유는, 바로 위의 요소인 div 가 display : flex 선언했기 때문이다.
div 태그에서 display : flex 가 선언되었을 때,
직속 항목들의 Flex 관련 요소 커스텀을 위해, section 은 이 3 가지 항목을 조정 할 수 있다.
Default Value :
flex-grow : 0 --> 상위 컨텐츠에 따라 해당 컨텐츠를 늘릴 수 있는가?
flex-shrink : 1 --> 상위 컨텐츠에 따라 해당 컨텐츠를 줄일 수 있는가?
flex-basis : auto --> 컨텐츠의 비율의 중심이 될 요소는 어느정도인가?
즉, flex-grow, flex-shrink, flex-basis 를 조정할 수 있다.
여기서 flex-grow, flex-shrink 는 서로 반대의 개념이지만, 동일한 단위를 사용하며,
flex-basis 는 위의 2 개의 속성에서 "기준이 되는" 단위를 말한다.
%, px, em, rem, ... 등등이 사용되며,
auto 로 표시 할 경우, 컨텐츠의 크기 자체를 기준으로 사용한다.
flex-grow 란 무엇인가?
이는 flex 가 선언된 엘리먼트의 직속 하위 요소, flex-item 에 사용할 수 있는 속성이다.
-이 속성의 값이 0 인 경우-
이 flex-item 은 상위 컨텐츠의 크기가 변동해도 "증가 하지 않는다".
-이 속성의 값이 1 인 경우-
이 flex-item 은 상위 컨텐츠의 크기가 변동 할 시, "증가 할 수 있다."
-이 속성의 값이 1 보다 큰 경우
이 상황은 flex-basis : auto 라고 가정한다.
이 경우, flex 선언된 블록 내부에 직속 자식으로 여러개가 있는 상황에 주로 사용되는데,
1 번째 : flex-grow : 1
2 번째 : flex-grow : 2
3 번째 : flex-grow : 3
으로 선언되었을 경우,
flex 선언된 블록의 컨텐츠 크기에 따라서,
"1 : 2 : 3"
크기로 나뉘어진 영역을 볼 수 있다.
만약에 모든 엘리먼트에서 flex-grow : 1 이 선언되었을 경우,
모든 엘리먼트는 flex 선언된 영역의 크기를 정확히 동일하게 나누어 가진다.
그러나, 여러 엘리먼트 중 flex-grow : 2 인 요소가 존재한다면,
해당 요소는 flex 영역 중 남는 영역이 존재한다면,
그 중 (2 / 모든 flex-grow 합) + flex-basis 가 최종 영역이 된다.
어떻게 보면, 0 은 경쟁에 참여하지 않음으로 볼 수 있고,
1 이상은 남는 영역을 가져가는 경쟁에 참여하는 엘리먼트라고 볼 수 있다.
즉, flex-grow 를 요약하자면,
0 : 이 컨텐츠는 상위 요소의 크기에 관계없이 이 크기를 유지 할 것이다.
1 : 이 컨텐츠는 상위 요소의 크기에 맞게 커질 수 있다.
1 보다 클 경우 : 주로 여러개의 엘리먼트가 있을 경우 사용되며, 비율로 사용된다.
0, 1 의 값 또한 여러개의 엘리먼트가 존재하는 경우에서 사용될 수 있으며,
flex-basis 와 조합되어 더 다양한 상황을 연출할 수 있다.
flex-grow 예시
이전에 HTML 모든 태그 다루기 Series 에서 모든 것을 다루었다고 생각했는데,
내용이 장편으로 6 편까지 다루는 과정에서 예시 보조로 사용할 수 있는 HTML 을 많이 까먹었다는 생각이 든다.
이번에는 직간접적으로 체험할 수 있는 인터랙션 input 을 넣을 것이다.
article {
display : flex;
width : 200px;
}
article div {
flex-grow : 1;
flex-shrink : 0;
flex-basis : 50px;
border : 2px solid black;
}
<body>
초기 이 영역의 총 크기는 <span>200px</span> 입니다.
<br/>
<input type="range" id="width-range" min="100" max="400" value="200" step="50"/><br/>
현재 이 영역의 총 크기는 <span id="width-num"></span>px 입니다.
<article id="article-dom">
<div style="background : dodgerblue;">
a
</div>
<div style="background : gray;">
b
</div>
<div style="background : green;">
c
</div>
<div style="background : lightblue;">
d
</div>
</article>
<script>
const lever = document.getElementById("width-range");
const widthText = document.getElementById("width-num");
const articleWidth = document.getElementById("article-dom");
widthText.textContent = lever.value;
lever.addEventListener("input", function() {
widthText.textContent = lever.value;
articleWidth.style.width = lever.value + "px";
})
</script>
</body>
Preview :
end
위의 예시에서 "의도한 바" 는,
flow-grow : 1 선언과, flow-shrink : 0 선언에 의해,
컨텐츠가 본래보다 늘어났을 때는 대응하나,
컨텐츠가 자신의 실제 크기보다 줄어들어야 할 상황에는 "줄어들지 않는" 상황을 연출했다.
여기서 레버에 해당하는 HTML 태그를 찾느라 약간 헤메고 있었는데,
input 태그의 type="range" 로 설정 할 수 있었다.
flex-shrink 란 무엇인가?
이 또한 flex 가 선언된 엘리먼트의 직속 하위 요소, flex-item 에 사용 할 수 있는 속성이다.
shrink 라는 용어는, "수축하다" 라는 의미를 담고 있다.
-이 속성의 값이 0인 경우-
해당 flex-item 은 상위 컨텐츠의 크기가 변동해도 "감소하지 않는다."
-이 속성의 값이 1인 경우-
해당 flex-item 은 상위 컨텐츠의 크기가 변동 할 시, "감소 할 수 있다."
-이 속성의 값이 1 이상 인 경우-
이전의 예시에서 flex-grow 가 늘어 날 수록, 크기가 더 커질 수 있는 반면에,
flex-shrink 는 늘어 날 수록, 크기가 줄어 들 수 있다.
article {
display : flex;
padding : 1rem;
background : gray;
width : 200px;
}
article div {
flex-grow : 0;
flex-shrink : 1;
flex-basis : 50px;
}
<body>
처음 이 영역의 총 크기는 200px 입니다.
<br/>
<input type="range" id="width-lever" min="100" max="300" value="200" step="50"/>
<br/>
현재 이 영역의 총 크기는 <span id="width-text"></span> px 입니다.
<br/>
<article id="article-dom">
<div style="background : dodgerblue;">
a
</div>
<div style="background : white;">
b
</div>
<div style="background : green;">
c
</div>
<div style="background : lightblue;">
d
</div>
</article>
<script>
const lever = document.getElementById("width-lever");
const text = document.getElementById("width-text");
text.textContent = lever.value;
const article = document.getElementById("article-dom");
lever.addEventListener("input", function () {
text.textContent = lever.value;
article.style.width = lever.value + "px";
})
</script>
</body>
Preview :
end
위의 레버를 한번 앞뒤로 움직여 보면 알겠지만,
Flex 된 컨텐츠가 커진다고 하여, 하위 요소는 반응하지 않고, 자신의 스타일(너비) 를 유지한다.
그러나, Flex 된 컨텐츠가 자신들을 온전히 담을 수 없을 때,
각자 수축하여 컨텐츠에 들어가도록 맞추는 것을 볼 수 있다.
위의 3 가지 속성을 단축하여 한 번에 선언하기 - flex
지금 설명하려는 것 또한, display : flex 가 선언된 컨테이너 내부의,
flex-item 에 해당하는 직속 DOM 에서 선언할 수 있는 속성이다.
즉, flex : 1, flex : 1 1 auto 식으로 사용한다.
그런데, 단일 값이 아니라, 다중 값을 연결하여 사용하고 있는 것을 볼 수 있다.
이는 각 순서가 가지는 일련의 값이 매칭되기 때문이다.
flex-grow --> 컨테이너의 크기가 남을 때 더 늘릴 것인가
flex-shrink --> 컨테이너의 크기가 작을 때 자신도 따라 줄일 것인가
flex-basis --> flex 에 의해 handle 될 때, 이 DOM 의 기준 너비를 정한다. (width)
따라서, 만약에 flex-item 에서 flex : 1 0 50px 로 선언했다면,
flex-grow : 1
flex-shrink : 0
flex-basis : 50px
형식을 가진 것이다.
그런데, 보통 flex-basis 의 기본 값은 보통 auto 로 설정된다.
그렇다면, auto 의 너비, flex-basis 는 얼마일까?
정답은 바로 0 이다.
만약에 auto == 0 으로 설정하게 된다면, flex 와 관련된 속성으로,
컨테이너 영역을 얼마나 차지 할 것인지, 마치 비율로 나눌 수 있다는 것이다.
예를 들어서, 1 : 3 : 2 의 비율을 가진 컨테이너를 표시하고 싶다고 가정하자.
그렇다면, flex 라는 아주 간단한 단축 기능의 속성을 통해 바로 표현 할 수 있다.
article {
display : flex;
background : lightgray;
border : 2px solid black;
width : 180px;
}
article div:nth-of-type(1) {
flex : 1;
background : dodgerblue;
}
article div:nth-of-type(2) {
flex : 3;
background : pink;
}
article div:nth-of-type(3) {
flex : 2;
background : gray;
}
<body>
<input type="range" id="width-lever" min="100" max="500" value="180" step="20"> <br/>
현재 컨테이너의 총 길이는, <span id="width-text"></span> px 입니다.
<article id="article-dom">
<div>
</div>
<div>
</div>
<div>
</div>
</article>
<script>
function changeTexts() {
let blocks = document.querySelectorAll("article div");
for(let i = 0; i < blocks.length; i++) {
const tempStyleObj = window.getComputedStyle(blocks[i]);
blocks[i].innerText = tempStyleObj.width;
}
}
changeTexts();
const lever = document.getElementById("width-lever");
const text = document.getElementById("width-text");
text.textContent = lever.value;
const article = document.getElementById("article-dom");
lever.addEventListener("input", function () {
text.textContent = lever.value;
article.style.width = lever.value + "px";
changeTexts();
})
</script>
</body>
Preview
end
위의 예제와 같이, flex-item 에 해당하는 요소가 선언할 수 있는
여러 개의 속성 중, 단축어의 기능을 하는 flex 를 이용하여, 비율로 나누는 예제를 선보였다.
즉, flex 는 flex-grow, flex-shrink, flex-basis 의 단축을 도와준다.
Flexbox 의 꽃, 정렬에 대해서 알아보자.
만약에, CSS 스타일링을 해 본 사람이 있다면,
justify-content, align-items 라는 속성에 대해서 들어 보거나, 작성 해 본적이 있을 것이다.
이게 정확히 무엇을 의미하는지 알고 있는가?
"사실 나도 모른다."
나는 이러한 속성을 작성 할 때, "중앙 정렬이 필요해" 생각하며,
단순히 두 속성의 값을 변동 해 가며 맞춰본 기억이 있다.
즉, 이에 대해 이해하고 있지도 않으면서, 비효율적으로 조합을 맞춰본 꼴이 된 것이다.
드디어, Flexbox 의 정렬에 대한 정확한 이해와 사용을 시작 할 때가 된 것 같다.
그 전에 알아 두어야 할 "주축", 과 "교차 축"
원활한 글 작성을 위해 먼저 참조자료들을 읽고 작성하는 편인데,
Flexbox, 즉, display : flex 라고 선언된 컨테이너는,
4 가지의 "방향" 즉, Direction 을 가질 수 있다.
이 4 가지의 방향에 따라서, 우리가 선언할 "정렬" 에 관한 성질들은 전부 달라 질 수 있다.
정렬의 선언은 동일해도, 이 "주축", "교차 축" 을 정하는 flex-direction 에 따라
스타일링은 현저히 달라질 수 있다.
그 이유가, flex-direction 은 주축의 방향과 교차 축의 방향을 정한다.
justify-xxx, align-xxx 은 각각 주축, 교차축의 정렬을 도맡는다.
주 축과 교차 축, 그리고 그 방향에 대해서 이해해 보자.
flex-direction 은 기본적으로 row 이다.
즉, 주 축은 Left to Right, (수평) 이며, (Main Axis)
교차 축은 Up to Down, (수직) 이다. (Cross Axis)
flex-direction 이 column 일 경우,
주 축은 UP to Down, (수직) 이며, (Main Axis)
교차 축은 Left to Right, (수평) 이다. (Cross Axis)
여기서 확실히 해야 할 점이 몇 가지 존재한다.
flex-direction 이 row, 혹은 row-reverse 일 경우,
주 축은 여전히 수평이지만, 배치 방향은 반대이다.
그러나, 교차 축인 수직에서는 배치 방향이 변하지 않는다.
flex-direction 이 column, 혹은 column-reverse 일 경우,
주 축은 수직으로 동일하지만, 배치 방향은 서로 반대이다.
그러나, 교차 축은 수평에서는 배치 방향이 변하지 않는다.
즉, flex-direction 의 값에 따라 여러 상황이 변할 수 있지만,
기본적인 "교차 축" 은 변하지 않는다는 것이 중요하다.
교차 축은 수평, 수직이 될 수 있으며, 항상 Left To Right, 혹은 Up to Down 이다.
이에 대해 각종 옵션을 선택 할 수 있는 예제를 만들어 보기로 결정했다.
단, flex-direction 의 방향을 바꿔가며, 주축과 교차축을 이해하여
여러 엘리먼트가 어떻게 작성되는지만 보도록 하자.
section {
display : flex;
flex-direction : row;
width : 10rem;
height : 10rem;
border : 2px solid lightgray;
}
section div {
text-align : center;
width : 2rem;
height : 2rem;
padding : 0.25rem;
&:nth-of-type(1) {
background : dodgerblue;
}
&:nth-of-type(2) {
background : lightgray;
}
&:nth-of-type(3) {
background : green;
}
}
<body>
<select id="direction-value">
<option value="row">row</option>
<option value="row-reverse">row-reverse</option>
<option value="column">column</option>
<option value="column-reverse">column-reverse</option>
</select>
<br/>
현재 방향은 <span id="direction-text">row</span> 입니다.
<section id="container">
<div>
1
</div>
<div>
2
</div>
<div>
3
</div>
</section>
<script>
const select = document.getElementById("direction-value");
const text = document.getElementById("direction-text");
const container = document.getElementById("container");
select.addEventListener("change", function () {
container.style["flex-direction"] = select.value;
text.textContent = select.value;
})
</script>
</body>
Preview :
end
위의 예제를 보면, flex-direction 의 변화로 인해,
작성 방향이 달라지고 있다.
그에 반해, 주축이 아닌, 결정된 교차 축의 방향은 변하지 않는 것을 볼 수 있다.
justify 와 align 을 알아보자.
먼저 추상적으로 보자면,
justify : 주 축을 관리
align : 교차 축을 관리
이러하다.
위에서 언급했듯, flex-direction 에 의해, justify, align 을 시각적으로 관리하는 것은 다르다.
그러니까, 주 축의 시작점을 flex-direction, justify-.. 로 금방 알아 낼 수 있다면,
이 컨테이너가 엘리먼트를 "어떤 형식으로, 어떤 방향으로" 나열 할 것인지 알 수 있다는 것이다.
여기서 드디어 나의 또 다른 의문점을 해소 할 단계가 왔다.
의문점 리스트
justify-content
align-content
align-items
align-self
이 4 개이다.
justify-content 는 "주 축"(Main Axis) 를 기점으로
"앞, 중앙, 뒤 중 무엇부터 채울 것인가?" 를 의미하지만,
상황에 따라 align-items, 혹은 align-content 를 덧붙여 사용했다.
이는 도대체 무엇인가? 둘 다 컨텐츠를 의미하는 것 같은데, 하나는 아이템이고, 하나는 컨텐츠이다.
위에서 제시한 4 가지의 의문점을 차차 소제목으로 소개하며 알아가는 시간을 가져보자.
justify-content 를 알아보자
justify-content 는 display : flex 와 같이 사용되며,
flex-direction 에 따라 전혀 다른 시각적 효과를 줄 수 있다.
이 속성은, 주 축인 Main Axis 방향의 공간에서, 내부 요소들을 어떻게 분배 할 것인지 결정한다.
flex-direction : row; 가 기본 값이기 때문에,
Default 에서는 수평선 영역에 어떻게 영역을 분배 할 것인지 결정한다.
justify-content 는 밑과 같은 값으로 설정 할 수 있다.
flex-start : 시작부분에 배치
flex-end : 마지막 부분에 배치
center : 중앙에 모이도록 배치
space-between : 내부 요소들끼리 최대한 떨어지도록 배치
space-around : 컨테이너의 border 와, 내부 요소까지 합쳐 최대한 떨어지도록 배치
space-evenly : 규칙적으로 떨어지도록 배치
중요한 것은, 영역을 분배하는 것이기 때문에,
내부 요소보다 더 많은 영역을 가지고 있어야 작동 여부를 확실히 볼 수 있다.
기본 상태인 flex-direction : row 의 예제를 살펴보자.
.container {
display : flex;
flex-direction : row;
width : 15rem;
height : 15rem;
background : lightgray;
}
.container div {
width : 3rem;
height : 3rem;
}
<body>
justify-content : <br/>
<select id="justify-value">
<option value="flex-start">flex-start</option>
<option value="flex-end">flex-end</option>
<option value="center">center</option>
<option value="space-between">space-between</option>
<option value="space-around">space-around</option>
<option value="space-evenly">space-evenly</option>
</select>
<br/>
<section class="container" id="container">
<div style="background : dodgerblue;">
1
</div>
<div style="background : green;">
2
</div>
<div style="background : pink;">
3
</div>
</section>
<script>
const justify = document.getElementById("justify-value");
const container = document.getElementById("container");
justify.addEventListener("change", function () {
container.style["justify-content"] = justify.value;
});
</script>
</body>
Preview :
end
만약 flex-direction 이 column 이라면,
.container {
display : flex;
flex-direction : column;
width : 15rem;
height : 15rem;
background : lightgray;
}
.container div {
width : 3rem;
height : 3rem;
}
Preview :
end
위의 2 가지 예제를 이어서 보여줬는데,
justify-content 에 올 수 있는 6 가지 값의 차이와,
주 축의 경로를 변화시킬 수 있는 flex-direction 이
시각적으로 얼마나 큰 연관관계를 가졌는지 이해 할 수 있을 것이다.
align-xxx 속성 3 개에 대해서 알아보자.
align 이 붙은 속성은 총 3개이다.
align-content - 컨테이너에서 선언
align-items - 컨테이너에서 선언
align-self - 컨테이너에 속한 flex-item 이 선언
각 공식문서에서는 이 align 에 대한 개별적이며 구체적인 설명이 없기 때문에,
따로 Gemini-2.5 에 물어보아 그 답을 알 수 있었다.
전반부에서 말했듯이, display : flex 는 1 차원 나열 표시 형식을 의미하는데,
flex-wrap : wrap 이 같이 선언 되어 있다면,
flex-item 이 가득차기 전에, 이 flex-item 이 마치 글자처럼 "줄넘김" 된다.
그렇지 않다면, flex-item 은 고유의 너비를 스스로 줄여 container 에 맞춘다.
여기서, align-content, align-items 의 차이가 드러난다.
align-content 는, 이 "줄넘김" 되었을 때를 가정하여,
"각 row group" 이 어떻게 떨어질지를 결정한다. 즉, 줄 그룹에 대한 명령이다.
align-items 는, 형성된 "각 줄" 에서 자신이 어디에 정렬 될 것인지,
모든 하위 엘리먼트, flex-item 들에 명령하는 것이다.
align-self 는, flex-item 에서 선언하는 속성인데,
자신은 컨테이너에서 선언한 align-items 와 달리,
자신은 스스로 정렬을 수행하겠다는 것이, align-self 이다.
2 가지 예제로 알아보는 align
align-content, align-items 를 조합하는 예제,
align-self 를 통해 개별 정렬을 수행하는 예제를 살펴보자.
align-content, align-items 를 조합하는 예제 :
.container {
display : flex;
flex-direction : row;
flex-wrap : wrap;
width : 20rem;
height : 20rem;
background : lightgray;
gap : 1rem;
}
.container div {
width : 3.5rem;
border : 1px solid green;
}
<body>
align-content : <select id="align-content-val">
<option value="stretch">stretch</option>
<option value="center">center</option>
<option value="flex-start">flex-start</option>
<option value="flex-end">flex-end</option>
<option value="space-between">space-between</option>
<option value="space-around">space-around</option>
<option value="space-evenly">space-evenly</option>
</select>
<br/>
align-items : <select id="align-items-val">
<option value="stretch">stretch</option>
<option value="baseline">baseline</option>
<option value="center">center</option>
<option value="flex-start">flex-start</option>
<option value="flex-end">flex-end</option>
</select>
<br/>
<section class="container" id="container">
<div>
1
</div>
<div>
2
</div>
<div>
3
</div>
<div>
4
</div>
<div>
5
</div>
<div>
6
</div>
<div>
7
</div>
</section>
<script>
const alignContent = document.getElementById("align-content-val");
const alignItems = document.getElementById("align-items-val");
const container = document.getElementById("container");
alignContent.addEventListener("change", function () {
container.style["align-content"] = alignContent.value;
})
alignItems.addEventListener("change", function () {
container.style["align-items"] = alignItems.value;
})
</script>
</body>
Preview :
end
align-content, align-items, align-self 모두,
기본 값은 stretch 이다.
위의 예제는, align-content, align-items 의 조합을 변경하며
각각 어떤 역할을 하는지 직접 볼 수 있게 만들었다.
여기 baseline 이라는 기능이 있는데, 그다지 많이 사용되지는 않는다.
위에 붙어있어야 하는지, 혹은 정해진 영역을 나누어서 사용해야 하는지에 따라
크게 flex-start, or space-xxx 로 사용 할 것 같다.
중요 한 것은, "줄" 에 내리는 명령과, "엘리먼트" 에 내리는 명령이
align-content, align-items 로 다르다는 것이다.
align-self 를 통해 개별 교차 축 정렬을 수행하는 예제 :
.container {
display : flex;
flex-direction : row;
flex-wrap : wrap;
width : 15rem;
height : 10rem;
background : lightgray;
gap : 1rem;
}
.container div {
width : 3.5rem;
border : 1px solid green;
}
<body>
align-self : <select id="align-self-val">
<option value="stretch">stretch</option>
<option value="baseline">baseline</option>
<option value="center">center</option>
<option value="flex-start">flex-start</option>
<option value="flex-end">flex-end</option>
</select>
<br/>
<br/>
<section class="container">
<div id="div-item" style="background : skyblue;">
변환하는 곳
</div>
<div style="background : white;">
1
</div>
<div style="background : #222; color : white;">
2
</div>
</section>
<script>
const select = document.getElementById("align-self-val");
const divItem = document.getElementById("div-item");
select.addEventListener("change", function () {
divItem.style["align-self"] = select.value;
});
</script>
</body>
Preview :
end
FlexBox 내부의 flex-item 에,
align-self 를 직접 선언하여 자신의 줄에서 "어디에 정렬될지" 선택 할 수 있다.
마무리
이번 글은 웹, 혹은 타 어플리케이션에 있어서의 중요한 레이아웃 방식을 배웠다고 생각한다.
즉, 이번 글은 Flexbox 라는 레이아웃을 알게 된 것이다.
이번에 display : flex 로 생성되는 FlexBox 컨테이너와, 다루는 법을 알아보았다.
나는 이번에 자세히 공부 해 본 것이라 당연히 숙련되었다고 말할 수는 없지만,
위에서 많은 예시를 직접 코드로 작성하며 익혔다.
이번 학습으로 인하여 풀린 의문점은 무엇인가?
나의 의문점은 CSS 스타일링의 핵심 기술인 Flexbox 에 대한 자세한 설명 없이
컴포넌트 제작과 웹의 데이터 흐름에만 집중하여 CSS 스타일링을 복사하거나,
혹은 이를 따라치는 과정에서 발생하는 의문점을 해소 할 수 없어 이번 글을 작성하게 되었다.
이 글을 읽고 의문점이 있다면,
저는 타인이 특정 기술에 대해 질문하는 상황을 매우 환영합니다!
혹시라도 제 글을 읽고 궁금증이나 특정 질문이 있으시다면,
Email : rhdwhdals8765@gmail.com
으로 메일을 보내주시면 좋습니다.
제 글을 읽어주시는 분들께 항상 감사하다는 말씀을 드리고 싶습니다.
참조 사이트들
web.dev - (구글 관계자 팀이 만든 css 블로그)
https://web.dev/learn/css/flexbox?hl=ko
W3Schools - CSS with Flex
https://www.w3schools.com/cssref/css3_pr_flex.php
MDN 공식 문서 - (flexbox)
https://developer.mozilla.org/ko/docs/Web/CSS/CSS_flexible_box_layout/Basic_concepts_of_flexbox
예제와 함께 알아보는 CSS flex 와 Flexbox