브라우저 환경에서 UI 마크업을 하다 보면 가장 헷갈리는 지점 중 하나가 바로 줄바꿈이다. 언어 별로 줄바꿈이 동작하는 방식도 다르고, 공백 문자가 줄바꿈에 적용되게 하기 위한 방식도 가지각색이다. 줄바꿈에 대한 요구사항이 들어올 때마다 매번 대충 구글링해서 쓰던 white-space: pre 뭐시기 속성, word-break, <br> 태그 등… 이번에는 과연 이 속성들이 어떻게 동작하는 것이고, 어떤 상황에서 어떤 줄바꿈 전략을 사용해야 하는지 알아보고자 한다.
줄바꿈을 적용하는 방법을 알아보기 위해, 먼저 줄바꿈에는 어떤 종류의 것들이 있고, User Agent에 의한 콘텐츠 래핑은 어떻게 발생하는지 짚고 넘어가보자.
줄바꿈은 어떻게 발생할까
인라인 성격의 텍스트가 줄로 배치되는 경우, 박스 영역을 기준으로 컨텐츠의 줄이 바뀌게 된다. 이걸 줄바꿈이라고 한다. 이때 명시적인 제어로 인해 발생하는 줄바꿈을 강제 줄바꿈(forced line break), 컨텐츠 래핑으로 인해 자연스럽게 발생하는 줄바꿈을 소프트 줄바꿈(soft wrap break)이라고 한다.
- 소프트 줄바꿈(soft wrap break): 소프트 랩 기회로 발생하는 콘텐츠 래핑 때문에 줄이 바뀌는 것. 즉 User-Agent가 콘텐츠에 알맞게 맞추기 위해 자연스러운 줄바꿈을 만드는 것.
- 강제 줄바꿈(force line break): 컨텐츠가 <br> 태그 등 명시적인 제어로 줄바꿈이 되거나, 블록의 시작 또는 끝 때문에 줄이 바뀌는 것.
컨텐츠의 영역에 맞게 자연스럽게 줄바꿈이 실행되는 케이스는 어떤 것일까?
우선, 대부분의 언어에서는 단어 단위로 소프트 줄바꿈이 발생할 수 있게 된다. 영어, 스페인어 등 라틴어 계열의 문자 체계가 대표적이다. 이런 언어에서는 단어 중간에 줄바꿈 등으로 끊김이 발생하는 경우 읽기가 굉장히 힘들어진다. (da \n ybreak 라는 텍스트를 단번에 이해하거나 발음할 수 있을지 생각해보면 알 수 있다. 정답은 새벽을 뜻하는 daybreak)
그러나 한국어, 중국어, 일본어와 같은 언어의 경우, 각각의 음절/문자가 하나의 활자 단위에 해당한다. 한자 베이스의 이 세 언어를 묶어서 CJK 언어라고 하는데, 이 언어들은 띄어쓰기 등 공백 문자가 존재하지 않더라도 줄이 끊어질 수 있다. 하나의 문자 단위마다 의미를 가지고 발음할 수 있기 때문에 줄이 바뀌더라도 그 부자연스러움이 영어보다 덜하다는 것을 느낄 수 있다. (글로벌 폰트를 일반 폰트와 CJK 폰트를 나눠서 배포하는 것을 본 적이 있을 것이다)
이렇듯 소프트 줄바꿈은 상당히 복잡한 기준들을 가지고 있다. 이걸 HTML 콘텐츠로 제작할 때, 좀더 디테일하게 줄바꿈의 동작을 조절하여 디자인적 일관성을 지킬 순 없을까? 다음과 같은 CSS 프로퍼티로 어느 정도 조절이 가능하다. 이 속성들은 언어 분류에 따라 다르게 동작할 수 있기 때문에, 다국어를 처리할 필요가 있다면 언어 별로 어떻게 동작하는지에 대한 이해와 주의도 필요하다.
- line-break CJK 언어의 텍스트 줄을 어디서 바꿀지 지정
- word-break 텍스트가 자신의 콘텐츠 박스 밖으로 오버플로 할 때 줄을 바꿀 것인지 지정
- hyphens 여러 줄에 걸치는 텍스트에서 단어에 붙임표를 추가하는 방식
- overflow-wrap (word-wrap) 단어 마디 안에서 줄을 바꿀 것인지 조정
한편 줄바꿈은 언어를 불문하고 공백 문자에서도 발생한다. 텍스트가 정해진 영역보다 길어져서 자동으로 발생하는 줄바꿈과 달리, 하나의 텍스트 노드 내에서 공백, 줄바꿈 등의 공백 문자를 처리하기 위해서도 줄바꿈이 발생하게 되는 것이다. 이를 어떻게 처리할지 결정하는 주요 속성으로는 white-space 가 있다. 이처럼 다양한 줄바꿈 방식이 존재하는데, 이번에는 줄바꿈을 제어하는 주요 속성들 중 세 가지만 톺아보고자 한다.
주요 CSS 줄바꿈 속성
word-break
소프트 줄바꿈 기회를 어떻게 결정할지를 정의하는 속성이다. 까다로운 점은, 언어의 특성마다 글자 또는 어절을 구분하는 방식이 다르다는 것이다. CJK 언어(한중일)는 음절 단위를 중단점으로 처리하고, Non-CJK 언어는 공백을 기준으로 중단점을 판단한다.
예를 들어 CJK 언어인 한국어의 경우, 음절 단위와 단어 단위, 두 가지의 줄바꿈이 모두 가능하다. 다음 html을 word-break 속성을 통해 제어한다고 해보자.
<div class="cjk">
동해물과백두산이마르고닳도록 동해물과백두산이마르고닳도록 동해물과백두산이마르고닳도록
</div>
위 텍스트를 기본인 normal 속성으로 두면 다음과 같이 음절이 나뉘는 어떤 곳이든지 소프트 줄바꿈이 가능하다. break-all, break-word로 속성을 정의하는 경우에도 동일하다.
한국어에서 띄어쓰기를 기준으로 자동 줄바꿈을 적용시키고 싶다면, (즉 단어가 띄어쓰기로 끊어질 때에만 줄바꿈이 되도록 처리하고 싶다면), word-break 속성을 반드시 keep-all로 주어야 한다. 이 경우 CJK 텍스트가 Non-CJK 텍스트처럼 줄바꿈이 된다.
영문이라면 어떨까? 우선 영문과 같은 Non-CJK 언어에서는 소프트 줄바꿈이 발생하는 단위를 하나의 캐릭터셋이 아닌, 띄어쓰기로 나뉘어지는 단어를 기준으로 본다. 따라서 단어를 끊지 않게 처리하는 keep-all 속성은 사실 기본값인 normal과 동일하다. CJK 텍스트처럼 어떠한 문자 사이에서도 강제로 줄바꿈이 가능하도록 설정하려면, break-all 을 주는 수밖에 없다.
정리해보자면, 기본값은 CJK와 Non-CJK 언어의 동작 방식이 다르다. 따라서 두 가지 이상 다국어 텍스트의 줄바꿈 정책을 일관되게 쓰고 싶다면, break-all(어떠한 문자 사이에서도 줄바꿈 가능) 또는 keep-all(단어 내 줄바꿈 불가) 중 하나를 사용하면 된다.
overflow-wrap
컨텐츠가 블록 영역 밖으로 넘치는 경우, 허용된 중단점이 아님에도 불구하고 임의의 지점에서 줄을 바꿀 수 있게 할 것인지를 결정하는 속성이다. 참고로 word-wrap과 동의어다.
기본값인 normal과 달리, 이 속성을 break-word로 줄 경우 줄을 바꿀 만한 중단점이 없음에도 임의의 지점에서 줄바꿈이 발생할 수 있게 된다. Non-CJK 언어의 예시는 다음과 같다.
그러나 CJK 언어의 경우 기본적으로 음절 단위로 줄바꿈이 가능한지라, break-word 속성을 주는 것이 사실 의미가 없다. 기본값과 동일하게 동작하기 때문이다.
따라서, 언어 불문 텍스트 오버플로를 무슨 일이 있어도 방지하고 싶다면 break-word 속성으로 해결할 수 있다. 중단점인 공백 등이 없어도 텍스트가 넘치는 지점이라면 줄바꿈을 허용하여 보다 자연스러운 컨텐츠 래핑이 가능해진다.
white-space
공백 문자를 처리하는 방법을 결정하는 속성이다. 여기서 공백 문자란 스페이스(U+0020), 탭(U+0009), 줄바꿈 문자(U+000A) 등을 의미한다. 보통 줄바꿈을 명시적으로 주고 싶을 때, \\n과 같은 개행 문자를 사용해본 적이 있을 것이다. 다만 단순히 개행 문자를 넣는다고 UA가 자동으로 줄바꿈을 시켜주는 것은 아니다. white-space 속성을 통해 이런 류의 공백 문자를 핸들링할 수 있는데, 키워드별로 살펴보자면 다음과 같다.
기본 동작(normal)은 연속된 공백은 하나로 합치는 것이다. 한 줄이 너무 길어서 넘칠 경우, 자동으로 줄을 바꾸게 된다. 여기서 줄바꿈이 발생할 수 있는 요소가 키워드 별로 달라진다.
- nowrap: 줄바꿈은 강제 줄바꿈인 <br> 요소에서만 발생
- pre: 줄바꿈은 개행 문자(줄바꿈 문자)와 <br> 요소에서만 발생.
- pre-wrap: 연속된 공백 문자와 개행 문자를 모두 유지. <br> 뿐 아니라 소프트 줄바꿈 기회에서 줄바꿈도 가능하다.
- pre-line: pre-wrap과 동일하기 동작하지만, 연속된 공백 문자를 하나로 합친다는 차이가 있다.
- break-spaces: pre-wrap과 동일하게 동작하지만, 연속 공백을 처리하는 방식에서 다음과 같은 차이가 있다.
- 연속 공백이 줄의 끝에 위치하더라도 공간을 차지함
- 연속 공백의 중간과 끝에서도 자동으로 줄바꿈 가능
- 유지한 연속 공백은 절대 요소 바깥으로 넘치지 않음 (공간을 차지하기 때문)
다음과 같이 배치되어 있는 HTML 컨텐츠가 있다고 해보자. 코드 내부에 들어있는 태그 사이의 인덴트(indent) 등이 공백 문자로 들어가 있다.
<div>
<div>
normal
</div>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</div>
이때 기본값(normal)과 nowrap을 적용해보면 다음과 같은 차이를 보인다.
즉, 소프트 줄바꿈 기회에서 줄바꿈을 하느냐(normal), 강제 줄바꿈 기회에서만 줄바꿈을 하느냐(nowrap)의 차이가 있어 보인다.
그렇다면 이름이 비슷해보이는 pre, pre-wrap, pre-line은 어떻게 다를까?
우선 pre, pre-wrap은 연속된 공백들을 쓰여진 그대로 유지하고 있다는 것을 알 수 있다. 반번 pre-line은 연속된 공백을 하나로 합쳐 보다 자연스러운 텍스트 흐름을 나타낼 수 있게 도와준다. 또한 pre 의 경우 <br>과 같은 강제 줄바꿈이 아니라면 줄바꿈 처리를 하지 않지만, pre-wrap/pre-line의 경우 소프트 줄바꿈 기회에서 줄바꿈이 가능하다. 결과적으로는, 연속된 공백 문자를 하나로 합치고, 소프트 줄바꿈도 가능한 pre-line이 가장 자연스럽고 범용적으로 쓸 수 있을 듯하다.
여기서 주의해야 할 태그: <pre>
대부분의 태그에서 white-space 속성의 기본값은 normal이다. 그러나 <pre> 태그를 사용하는 경우, white-space 속성의 기본값은 이름처럼 pre로 변해버린다. 즉, 연속된 공백을 유지하고, 강제 줄바꿈 기회에서만 줄바꿈처리를 한다는 굉장히 특이한 속성을 기본값으로 가지게 되므로 마크업 시 사용에 주의해야 한다.
Use Case
모든 다국어 케이스에서 단어마다 줄바꿈이 되어야 할 때
상세 정보를 설명하는 디스크립션 텍스트 등은 단어 단위로 줄바꿈처리를 해주는 것이 가독성에 좋을 때가 있다. 따라서 Non-CJK, CJK 모든 언어에서 단어 단위로 소프트랩되는 것을 원한다면 word-break: keep-all 을 사용해볼 수 있겠다. 이 경우 한글 등 CJK 텍스트를 영문 텍스트처럼 줄바꿈하는 것이 가능해진다.
언어 별 줄바꿈 포인트를 강제로 다르게 가져가야 하는 경우
고정적인 메타데이터 문구를 보여줘야 하는 푸터 텍스트 등에서는 언어 별로 텍스트의 길이 차이가 심할 경우, 언어별로 강제 줄바꿈을 해줘야 하는 경우가 있다. 이때 가장 쉽게 접근할 수 있는 방법은, <br> 태그를 통한 강제 줄바꿈이다. 다국어 별로 <br> 태그를 적절한 곳에 삽입하여, HTML에서 출력해주기만 하면 된다.
또는 소프트 줄바꿈과 같이 중단점이 허용되는 위치를 나타내기만 해주는 <wbr> 태그를 사용해볼 수도 있다. <br> 처럼 반드시 줄바꿈이 발생하는 것은 아니지만, 현재 요소의 줄바꿈 규칙을 무시하고 브라우저가 줄바꿈을 할 수 있는 지점을 명시할 수 있어, 보다 자연스러운 케이스별 줄바꿈 처리가 가능하다.
URL을 출력할 때
URL에는 띄어쓰기, 공백문자가 없다. 따라서 URL을 레이아웃에 맞춰 깔끔하게 출력하고자 한다면 word-break: break-all 을 주어 어떠한 문자 사이에서라도 소프트 줄바꿈이 가능하도록 해두는 것이 좋겠다.
또는 overflow-wrap: break-word 을 통해 중단점을 주지 않더라도 텍스트 오버플로를 막을 수 있다.
Reference
https://www.w3.org/TR/css-text-3/#line-breaking
https://smartstudio.tech/deepdive-linebreak-css-about-white-space/
https://developer.mozilla.org/ko/docs/Web/CSS/white-space
'Web' 카테고리의 다른 글
서로 다른 윈도우 간 안전하게 통신하는 방법 (0) | 2024.03.17 |
---|---|
Axios 인터셉터로 JWT 토큰 로테이션 구현하기 (0) | 2024.02.18 |
JavaScript로 이미지 파일 데이터 다루기 (0) | 2023.08.31 |
[Web] DOM API를 효과적으로 사용해보자 (0) | 2022.04.16 |
브라우저 렌더링 (1) - 클라이언트 사이트 렌더링 (0) | 2022.01.26 |