CSS는 제약 시스템이어야 할까요?
Source: Hacker News
CSS는 어렵다. 레이아웃 규칙이 꽤 복잡하고 예시만으로는 익히기 힘들다. ““를 가운데 정렬한다”는 문제는 유명하게도 골칫거리다. 혹은 2000년대 초에 A List Apart를 보면서 “Holy Grail” 레이아웃, 즉 헤더, 본문, 같은 높이의 사이드바, 그리고 푸터를 구현하는 온갖 기법들을 읽던 기억이 있나? 이렇게 엉망진창이라면, 차라리 완전히 다른 시스템으로 처음부터 다시 시작하는 것이 나을까?
나는 어느 정도 권위 있게 이 이야기를 할 수 있다고 생각한다. 대학원 시절 나는 CSS 2.2 사양의 첫 번째 공식 명세를 작성했으며, 그 명세는 (관련된 부분의) 적합성 테스트를 통과했다. 그래서 기존 알고리즘을 꽤 상세히 알고 있지만, 디자이너가 실제로 하는 일이나 다른 시스템에 대해 이야기할 때는 그 전문성을 넘어서는 부분도 있다.
제약 조건
CSS를 대체하기 위해 흔히 제안되는 것이 제약 시스템이다. 제약 시스템에서는 다음과 같이 직접 말할 수 있다:
(obj.top + obj.bottom) / 2 == (obj.parent.top + obj.parent.bottom) / 2
이 식은 obj의 수직 중앙점이 부모의 수직 중앙점과 같아야 한다는 제약을 설정한다; 즉 수직으로 가운데 정렬된다는 의미다.
실제로 이 아이디어는 많이 탐구돼 왔다. CSS 분야에서는 잘 알려진 Constraint Cascading Style Sheets 논문이 있다. 그 논문에서 제시된 아이디어는 웹 페이지 작성자가 위와 같은 제약을 작성하고, 브라우저가 제약 해결기를 실행해 제약을 만족하는 위치와 크기를 계산한다는 것이다. 이는 저자가 규칙을 쓰고 브라우저가 레이아웃 알고리즘을 실행해 크기와 위치를 계산하는 일반 CSS와 매우 흡사하다. 물론 두 경우 모두 크기와 위치뿐 아니라 폰트, 색상 등도 계산한다. 하지만 레이아웃이 문제의 “어려운 부분”으로 여겨지며, 나는 그 점에 크게 이의를 제기하지 않는다.
실제로 저자들(특히 Alan Borning)은 제약 해결기와 오랜 역사를 가지고 있으며, 특히 Cassowary 증분 제약 해결기와 연관이 있다. 여기서 “증분”이란 페이지가 조금만 바뀌어도(예: JavaScript나 사용자 동작에 대한 반응) 제약을 빠르게 다시 해결할 수 있다는 뜻이다. 실제 브라우저도 그렇게 동작한다. 그리고 꽤 성공적이었다. 가장 눈에 띄는 사례는 iOS가 Cassowary를 재구현한 제약 기반 레이아웃을 제공한다는 점이며, 이는 매우 인기가 있다고 들었다.
제약 시스템의 문제점
그럼에도 불구하고, 나는 제약 시스템이 웹에 더 나은 선택이 될 것이라고 보지는 않는다. 현재 CSS와 같은 규칙 기반 시스템에서는 규칙 자체가 복잡하고, 머리 속으로 규칙을 실행해 레이아웃을 예측하기가 거의 불가능하다. 반면 제약 기반 시스템에서는 레이아웃이 과소 혹은 과다 결정될 수 있다. 즉, 제약을 만족하는 레이아웃이 하나 이상이거나 전혀 없을 수도 있다는 뜻이다.
사실, 과소·과다 결정되지 않은 UI 레이아웃 제약을 작성하는 것은 거의 불가능에 가깝고, 누가 그런 일을 해냈는지는 잘 모른다. 위의 간단한 “수직 가운데 정렬” 제약조차도 두 박스의 크기나 위치를 고정하지 않는다. 외부 박스(obj.parent)가 자식(obj)을 포함하기 위해 가능한 최소 크기여야 한다든가, 두 박스가 화면 안에 있어야 한다든가 하는 조건을 명시하지 않는다. 그런 조건을 추가하려면 더 많은 제약을 써야 하는데, 제약이 많아질수록 충돌 가능성도 커져서 과다 결정 상태에 빠지게 된다.
이를 해결하는 몇 가지 방법이 있다:
- 과소 결정 레이아웃 – 암묵적 규칙을 추가한다(예: 박스는 화면 안에 있어야 함) 혹은 최적화 기준을 둔다(예: 가장 적은 공간을 차지하는 레이아웃 선택).
- 과다 결정 레이아웃 – 각 제약에 가중치를 부여하고, 가중치를 최소화하면서 위배되는 제약을 최소화하도록 최적화한다.
이것이 이상한 접근법은 아니다—사실 LaTeX가 바로 이런 방식으로 동작한다—하지만 가중치, 암묵적 규칙, 최적화 기준을 조정하는 것이 실제 레이아웃을 결정한다. 간단한 규칙·기준·가중치만으로는 이상하고 끔찍한 엣지 케이스가 발생하고, 좋은 레이아웃을 원한다면 복잡한 규칙을 만들어야 한다. 그러면 다시 스타일시트만 보고 레이아웃을 예측하기 어려워진다.
LaTeX는 예측 가능한 레이아웃으로 널리 사랑받는 편은 아니다. iOS에서 제약 기반 레이아웃이 인기를 끌지만, iOS가 지원하는 화면·윈도우 형태가 제한적이라는 점도 눈여겨볼 만하다. 사람들은 여전히 제약 기반 레이아웃이 까다롭고, 부서지기 쉬우며, 예측 불가능하다고 불평한다. 특히 과소·과다 제약 레이아웃을 디버깅하는 것이 번거롭고 귀찮다고 여긴다. 또한 코드가 장황하다는 비판도 있다. UI 디자인에 관례와 패턴이 존재한다면 이를 모듈식으로 표현하고 싶지만, 제약 기반 시스템—특히 암묵적 규칙이나 최적화 기준을 추가하면—은 모듈화가 어렵다는 것이 명확해진다.
Apple이 이를 실제 제품에 적용한 점은 칭찬할 만하고, 많은 사람들이 이를 사용해 문제를 해결하고 있다. LaTeX가 내 문제를 해결해 주듯이 말이다! 최근 학생들 사이에서는 아직 사용해 보지는 않았지만 Typst에 대한 기대감이 높다. 하지만 나는 제약 기반 시스템을 사용하는 디자이너들이 이론적으로 예측되는 문제들을 겪고 있다고 생각한다.
근본적인 문제는 무엇인가?
그렇다면 왜 레이아웃이 이렇게 어려운 걸까? 왜 레이아웃을 할 때마다 이상한 엣지 케이스가 생기는 걸까? 나는 레이아웃 자체가 꽤 어려운 문제라고 본다.
예를 들어 보겠다. 아래는 내가 만든 CSS 2.2 공식 의미론 중 text-align: center에 대한 정의이다:
[(is-text-align/center textalign)
(= (- (left-outer f) (left-content b))
(- (right-content b) (right-outer l)))]
이는 컨테이너 b에 text-align: center가 지정돼 있으면, 첫 번째 자식 f의 왼쪽 외곽선 위치와 컨테이너 b의 왼쪽 콘텐츠 경계 사이의 간격이, 마지막 자식 l의 오른쪽 외곽선 위치와 컨테이너 b의 오른쪽 콘텐츠 경계 사이의 간격과 같아야 함을 의미한다. 즉, 이것도 일종의 제약이다!
하지만 몇 줄 위(여기)를 보면, 실제로 이 제약을 적용하기 전에 먼저 컨테이너가 내용을 담을 만큼 충분히 큰지 확인하고, 그렇지 않다면 무조건 왼쪽 정렬을 한다는 로직이 있다.
뭐라고? 정말? 맞다. 이것은 CSS 의미론의 특이한 부분이다(see CSS 2.1 9.4.2절). 이 특이성을 정확히 규정한 표준은 현재 CSS Text Level 3이며, 여기서는 내용이 박스에 들어가기엔 너무 작을 경우 왼쪽 가장자리를 넘치지 않게(스크롤할 수 없는 화면 밖으로 나가지 않게) 하기 위해 무조건 왼쪽 정렬한다는 점을 명확히 설명한다.
이것은 꽤 특이한 동작이지만, 실제로 눈치채지 못했을 수도 있다. 만약 눈치챘다면, 이 엣지 케이스가 없었을 때보다 레이아웃이 더 나빴을 가능성이 크다. 즉, text-align 정의에 이와 같은 엣지 케이스를 포함시킨 것은 CSS 설계자들이 경험을 통해 얻은 설계 지혜를 직관적인 규칙에 녹여 넣은 현명한 선택이었다. 사람들이 대부분 문제 없이 사용하게 만든 것이다.