CSS에서 네이티브 무작위성 (Native Randomness)의 중요성
요약
CSS는 선언적이고 결정론적인 언어적 특성 때문에 무작위성을 구현하는 데 어려움이 있었습니다. 본 글은 CSS에서 자연스러운 변화를 만들기 위해 개발자들이 패턴 시뮬레이션이나 전처리기 등을 사용하여 무작위성을 구현하려 했던 과정과 그 한계를 다룹니다.
핵심 포인트
- CSS는 '무엇을'에 집중하는 선언적 특성과 동일 입력에 동일 출력을 내는 결정론적 특성을 가짐
- 결정론적 특성은 예측 가능성을 높여주지만, 무작위 콘텐츠 생성에는 제약이 됨
- 과거에는 :nth-child()나 애니메이션을 이용한 의사 무작위성(Pseudo-Randomness) 패턴을 사용함
- 이러한 방식은 기계가 예측 가능한 편법(Hacks)에 불과함
최근 저는 CSS에 도입된 새로운 무작위 함수 (random functions)와 그 작동 방식에 관한 이야기를 게시했습니다. 이 글에서는 CSS에서 무작위성 (randomness)이 직면한 과제, 이 개념이 시간이 흐르며 어떻게 진화해 왔는지, 그리고 왜 이 네이티브 기능 (native feature)이 중요한지에 대해 살펴보겠습니다.
웹사이트 개발을 시작했을 때 제가 가장 먼저 하고 싶었던 일 중 하나는 사람마다 달라지는 독특한 경험을 만드는 것이었습니다. 여기에는 무작위 배경 (random background), 저기에는 무작위 색상 (random colors)과 같은 아주 작은 것들이 포함되었습니다. 꽃가루 (confetti)나 내리는 눈 (falling snow) 같은 작은 마이크로 인터랙션 (micro-interactions)조차도 자연스럽게 느껴지려면 어느 정도의 무작위성이 필요했습니다.
그리고 저만 그런 것이 아니었습니다! 저는 곧 많은 웹 개발자들(당시에는 "웹마스터"라고 불렸던) 역시 사이트에 놀라움 (wow factors)과 독특함을 더하기 위해 그런 것들을 하고 싶어 한다는 것을 발견했습니다. 하지만 우리에게는 문제가 있었습니다. 바로 CSS였습니다.
CSS는 선언적 (declarative)이고 결정론적 (deterministic)인 언어입니다. 이는 자연스러운 변화라는 개념과 충돌하는 두 가지 특성입니다.
**선언적 (Declarative)**이라는 것은 '어떻게 (how)'가 아니라 '무엇을 (what)'에 집중한다는 의미입니다. 명령형 언어 (imperative languages)와 대조적으로, CSS를 사용하는 개발자는 브라우저에게 기대되는 결과가 무엇인지는 말하지만, 그것을 어떻게 달성할지는 말하지 않습니다. **결정론적 (Deterministic)**이라는 것은 주어진 입력에 대해 항상 동일한 출력을 얻게 된다는 의미입니다. 언제나 같습니다. 만약 색상을 빨간색으로 지정한다면, 그 색상은 파란색이나 노란색이 아닌 항상 빨간색일 것입니다.
이것은 설계 의도이며, CSS를 예측 가능하고 신뢰할 수 있게 만드는 요소 중 하나입니다. 레이아웃 엔진 (layout engine)이 어떻게 작동하는지 이해한다면, 어느 시점에 어떤 스타일이 적용될지 알 수 있습니다. 이는 훌륭한 일이지만... 무작위 콘텐츠를 생성하고 싶을 때는 그리 훌륭하지 않습니다.
그리하여 디자이너와 개발자들이 결정론적인 시스템으로부터 자연스러운 변화를 구현하기 위한 도전적이고 (때로는 고통스러운) 여정이 시작되었습니다.
무작위 스타일을 향한 길고 험난한 여정
CSS에서 무작위 스타일을 구현하기 위한 경로는 수많은 시도와 결함들로 점철되어 있습니다. 하지만 그 과정의 매 단계마다 개발자들은 이전보다 개선된 새로운 해결책들을 찾아냈습니다. 설령 그것이 아주 미미한 개선일지라도 말입니다.
참고: 이 타임라인은 엄격한 역사적 또는 연대기적 순서라기보다 논리적인 진행 과정을 반영합니다.
CSS 의사 무작위성 (Pseudo-Randomness)과 패턴
우리는 패턴을 생성함으로써 CSS에서 무작위성을 시뮬레이션할 수 있습니다. 하지만 이것은 진정한 무작위가 아닙니다. 결과는 항상 동일할 것이며, 조만간 사람들은 그 패턴을 알아차릴 것입니다.
이러한 시뮬레이션을 만드는 한 가지 방법은 :nth-child() 선택자를 사용하거나 애니메이션 (Animations)을 활용하는 것입니다. 첫 번째 방법은 쉽지만 결과가 미흡하며, 두 번째 방법은 일부 사람들을 속이거나 감탄시킬 수도 있습니다.
경고: 자동 재생 미디어 (Auto-playing media)
말할 필요도 없이, 이러한 방법들은 어떤 수준의 무작위성도 제공하지 못하는 편법 (Hacks)입니다. 인간은 다음 값이 무엇인지 정확히 예측하지 못할 수도 있지만 — 적어도 어느 정도의 노력이 없다면 말입니다 — 기계는 분명히 예측할 수 있습니다.
구원투수로 등장한 전처리기 (Pre-Processors)
우리는 차선책인 도구 (Tooling)로 눈을 돌렸습니다. 특히 Sass, SCSS, Less 등과 같은 CSS 전처리기 (Preprocessors)가 그 대상입니다. 이러한 도구들은 컴파일 시간 (Compilation time)에 사용할 수 있는 무작위 함수 (Random functions)를 제공하는 수학 모듈을 포함하고 있습니다.
이전 단락의 핵심 문구는 "컴파일 시간 (at compilation time)에"입니다. 그렇습니다. 우리는 CSS 속성을 위한 무작위 값을 생성하고 있습니다. 하지만 컴파일 중에 해당 값들이 생성되고 나면, 그 값들은 영원히 (더 정확하게는 다음 컴파일 전까지) 고정됩니다. 마치 호박 (Amber) 속에 갇힌 모기처럼 말입니다.
CSS가 생성될 때는 값이 무작위이겠지만, 방문자가 페이지를 방문하거나 새로고침할 때마다 그들은 항상 동일한 값을 받게 될 것입니다. 새로운 값을 생성하려면 스타일시트를 다시 컴파일해야 합니다.
이것은 스타일링 무작위성을 향한 작은 발걸음이었지만, 여전히 갈 길은 멀었습니다.
서버 측 무작위성 (Server-Side Randomness)
우리는 그다음 차선책으로 이동했습니다. 다른 언어를 사용하여 무작위 값을 생성하고, 이를 HTML을 통해 CSS로 전달하는 방식입니다. PHP, Java, ASP 등과 같은 서버 측 언어들은 HTML(또는 CSS 자체)을 생성하는 동안 이 작업을 수행하기에 완벽했습니다.
이 방식은 잘 작동합니다. 페이지가 생성될 때마다(보통 페이지를 방문하거나 새로고침할 때마다) 새로운 무작위 값을 얻을 수 있기 때문입니다. 또한 우리만의 함수를 구현할 수 있으므로 무작위성 (Randomization)을 완전히 제어할 수 있습니다.
하지만 단점도 있습니다. 페이지에 새로운 콘텐츠가 동적으로 추가되는 경우, 초기 페이지 로드 시 생성된 "고정된 (frozen)" 값에 갇히게 됩니다. 패턴 (Patterns)이나 전처리기 (Preprocessors)보다는 낫지만, 여전히 완벽하지는 않습니다.
이러한 한계는 싱글 페이지 애플리케이션 (Single-page applications)과 클라이언트 측 JavaScript (Client-side JavaScript) 아키텍처가 부상하고 널리 채택되면서 더욱 큰 문제가 되었습니다.
그리고 JavaScript… 드디어!
웹 애플리케이션이 급증함에 따라 무작위성을 JavaScript로 옮기는 것이 합리적이었습니다. 이 언어는 이미 광범위하게 사용되고 있으며, 여기에 몇 가지 무작위 함수를 추가하는 것은 그리 어려운 일도 아니기 때문입니다.
그리고 JavaScript가 마침내 이를 해결했습니다! 처음으로 스타일이 자연스러운 변동성을 가지고 실제로 동작할 수 있게 되었습니다. 생성 시, 새로고침 시, 심지어 변형 (Mutation) 시에도 무작위로 동작할 수 있게 된 것입니다.
또한 프레임워크 (Frameworks), CSS-in-JS 라이브러리, 또는 순수 바닐라 JavaScript (Plain vanilla JavaScript)를 사용하는 등 다양한 방법으로 구현할 수 있습니다. 이 언어를 통해 스타일링을 통합하는 방법은 방대하며 지원도 잘 됩니다. 성능 및 복잡성에 대한 우려가 일부 존재하지만, JavaScript는 목적을 달성합니다.
우리는 마침내 웹 스타일에서 진정한 무작위성을 갖게 되었습니다… 다만 CSS 자체에서 구현된 것은 아닐 뿐입니다.
웹의 문제, 그리고 웹의 해결책
마지막 부분이 중요합니다. 웹에는 무작위성이 존재하지만 (JavaScript가 목적을 달성하니까요), 무언가 어색한 느낌이 듭니다. 무언가 딱 맞지 않는 느낌입니다. 본질적으로 그 불편함은 두 가지 이유에서 기인합니다.
- 우리는 선언적 문제 (Declarative problem)에 명령형 해결책 (Imperative solution)을 적용하고 있습니다.
- 우리는 레이아웃 결정권을 CSS에서 JavaScript로 옮기고 있습니다.
선언적 문제에 대한 명령형 해결책
앞서 언급했듯이 CSS는 *무엇 (what)*에 집중하는 선언적 언어 (Declarative language)인 반면, JavaScript는 *어떻게 (how)*에 집중하는 명령형 언어 (Imperative language)입니다.
무작위성 (Randomization)을 JavaScript로 옮김으로써, 우리는 *무엇 (what)*에 대한 질문을 *어떻게 (how)*라는 답변으로 해결하려 하고 있습니다. 작동은 하지만, 이상적인 방법은 아닙니다.
JavaScript를 사용하여 우리는 마침내 모든 단계에서 스타일의 무작위성을 달성했습니다. 페이지가 생성될 때, 새로고침될 때, 그리고 요소가 추가되거나 변경될 때 (변이, mutation) 말입니다. 하지만 그렇게 함으로써 우리는 모델을 깨뜨리고 있습니다.
CSS는 레이아웃 (Layout)을 담당하고, JavaScript는 로직 (Logic)을 담당합니다. 우리는 레이아웃 결정권을 JavaScript로 옮김으로써 CSS의 한계를 해결했지만, 이로 인해 기술적으로는 모든 것이 작동함에도 불구하고 미묘하게 "뭔가 잘못된 것 같다"는 느낌을 주는 불일치를 만들어냈습니다.
CSS 솔루션
이러한 모델 불일치에 대한 해결책은 간단합니다. 무작위성을 CSS로 옮기는 것입니다. 레이아웃 문제를 다른 도구나 언어에 위임하는 대신, 레이아웃 계층 (Layout layer)에서 직접 해결하십시오. 그리고 이는 CSS Values and Units Module Level 5의 일부로 두 가지 새로운 무작위 함수가 도입되면서 실현되었습니다:
random(): 최소값과 최대값 사이의 무작위 값을 생성합니다.
random-item(): 주어진 목록에서 무작위 값을 선택합니다.
이러한 접근 방식은 주어진 목적에 적합한 가장 강력하지 않은 언어를 선택할 것을 제안하는 **최소 권한의 원칙 (Rule of Least Power)**과도 일치합니다. 실제로 이는 문제를 표현하고 해결할 수 있는 가장 강력하지 않은 언어를 사용하여 문제를 해결하는 것을 의미합니다.
보통 그러한 언어가 해당 작업에 더 적합할 것입니다. 그 기능들은 적용되는 수준에 맞춰 조정되므로, 더 단순하고 효율적이며 성능이 뛰어납니다. 더 강력한 언어로도 분명히 작업을 수행할 수 있지만, 이는 종종 불필요한 복잡성과 추상화 계층을 도입하게 됩니다.
웹 플랫폼에는 구조를 위한 HTML (가장 강력하지 않음), 스타일링과 레이아웃을 위한 CSS (더 강력함), 그리고 JavaScript (현저히 더 강력함)가 있습니다. CSS에서 무작위성을 구현함으로써, 우리는 최소 권한의 원칙을 따르는 동시에 솔루션을 적절한 계층으로 이동시킵니다.
그리고 그것이 새로운 무작위 CSS 기능들이 매우 중요한 이유 중 하나이며, 왜 이 기능들이 단순히 또 하나의 기능 그 이상의 의미를 갖는지에 대한 이유입니다.
중요한 이유 (The Big Deal)
CSS는 설계상 항상 결정론적 (Deterministic)이었으며, 네이티브 무작위성 (Native randomness)은 그 전통을 깨뜨립니다. 이것은 단순히 또 하나의 기능이 아니라, 우리가 CSS를 언어로 바라보는 방식과 웹 플랫폼 자체를 생각하는 방식의 변화를 의미합니다.
처음으로 CSS는 변동성이 있는 자연 시스템을 직접 모델링할 수 있게 되었습니다. 해킹 (Hacks)도, 도구도, 레이아웃 결정을 다른 언어로 외주를 주는 것도 필요 없습니다. 무작위화 (Randomization)는 그것이 항상 속해야 했던 곳인 스타일링 계층 (Styling layer)에서 명예로운 자리를 차지하게 됩니다.
이는 창의적인 가능성을 열어줍니다. 생성형 레이아웃 (Generative layouts), 유기적인 패턴 (Organic patterns), 유희적인 마이크로 인터랙션 (Micro-interactions), 그리고 살아있고 독특하게 느껴지는 디자인 시스템 (Design systems) 등이 가능해집니다. 하지만 이는 또한 아키텍처적 명확성 (Architectural clarity)을 회복합니다. 웹의 각 계층이 다시 한번 설계된 목적에 맞는 역할을 수행하게 되는 것입니다.
이러한 변화와 함께, CSS는 순수한 스타일링 언어에서 생성형 레이아웃 시스템 (Generative layout system)으로 나아갑니다. CSS는 더 이상 웹 개발에서 수동적인 행위자가 아닙니다. 브라우저가 구체적인 페이지로 해결(Resolve)할 수 있는 가능한 결과값들의 공간을 정의하는 렌더링 프로세스 (Rendering process)의 능동적인 참여자가 됩니다.
그리고 그것이 진정으로 중요한 점입니다. 네이티브 무작위성은 단순히 사물을 다르게 보이게 만드는 것에 관한 것이 아닙니다. 플랫폼을 더욱 일관성 있고 표현력 있게 만드는 것에 관한 것입니다.
또한 이는 CSS가 여전히 진화하고 있다는 사실과, 때때로 사람들이 간과하는 기능들이 우리가 언어를 생각하는 방식과 웹에서 무엇이 가능한지 상상하는 방식을 재편할 수 있다는 점을 상기시켜 줍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 CSS-Tricks의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기