Values and units/Using typed arithmetic

김동현·2026년 3월 21일

mdn 학습 번역 - CSS

목록 보기
168/190

안녕하세요! 프론트엔드 강사입니다.

오늘은 CSS에서 프로그래머처럼 멋지게 계산을 수행할 수 있게 해주는 기능, 'CSS 타입 연산(typed arithmetic)'에 대해 MDN 문서를 통해 알아보겠습니다.

과거에는 픽셀(px)은 픽셀끼리, 퍼센트(%)는 퍼센트끼리만 더하고 뺄 수 있었죠. 하지만 calc() 함수가 등장하고, 더 나아가 서로 다른 단위를 나누어 단위가 없는 순수한 숫자(unitless quotient)를 뽑아내는 기능이 추가되면서 CSS만으로도 엄청나게 유연한 반응형 디자인이 가능해졌습니다. 이게 도대체 어떻게 가능하고, 실무에서 어떻게 쓰이는지 제가 꼼꼼하게 짚어드릴게요!


CSS 타입 연산 사용하기 (Using CSS typed arithmetic)

CSS 타입 연산(CSS typed arithmetic)calc() 같은 함수를 통해 타입이 지정된 CSS 값들을 계산하는 것을 의미하며, 구체적으로는 CSS 값과 단위 (CSS values and units) 모듈에 정의된 동작을 말합니다. 이 기능의 핵심은 특정 단위를 가진 값을 동일한 데이터 타입 내의 다른 단위를 가진 값으로 나눌 수 있게 해 준다는 점입니다. 이 나눗셈의 결과로 '단위가 없는 몫(unitless quotients)'이 생성됩니다.

이렇게 만들어진 단위 없는 몫은 단위가 필요 없는 속성의 <number> 값(예: opacity, line-height)으로 바로 사용될 수도 있고, 여기에 다른 숫자와 단위를 곱해서 <length>(길이), <percentage>(비율), <angle>(각도) 등 원하는 어떤 숫자형 데이터 타입으로든 다시 변환할 수 있습니다!

이러한 타입 연산 동작을 활용하면 페이지 내의 전혀 다른 값들(예: 브라우저 너비와 폰트 크기, 화면 너비와 배경색 투명도) 사이에 연결 고리(relationship)를 만들 수 있습니다.

참고 (Note): 초창기 CSS 명세서에서는 '0으로 나누기(dividing by zero)' 에러를 파싱 단계에서 미리 잡아내고 100px * 100px처럼 단위가 제곱(squared units)이 되어버리는 이상한 상황을 막기 위해, 곱셈과 나눗셈에 사용할 수 있는 인수를 아주 엄격하게 제한했습니다. 하지만 최신 브라우저들에서는 새로운 타입 연산 스펙을 적용하여 이러한 제한을 일부 완화했습니다.


이 문서의 목차


CSS 타입 연산 규칙 (CSS typed arithmetic rules)

CSS에서 값을 계산할 때는 서로 다른 데이터 타입을 가진 값들 간의 '호환성'에 대한 몇 가지 깐깐한 규칙이 있습니다.

덧셈과 뺄셈 (Addition and subtraction)

값을 더하거나 뺄 때는, 모든 값이 반드시 같은 대분류의 데이터 타입(overall data type) 안에 있어야 합니다. (예: '길이'는 '길이'끼리만, '각도'는 '각도'끼리만 더하고 뺄 수 있습니다. 단위가 달라도 데이터 타입만 같으면 브라우저가 알아서 변환해서 계산해 줍니다.)

/* 올바른 예시 */
/* <length-percentage> 타입들 (길이와 비율) */
calc(250px - 150px)
calc(100% - 50px) /* 브라우저가 %를 픽셀로 변환해서 계산합니다 */
calc(50vw + 2rem)
calc(25cqw + 3in)

/* <angle> 타입들 (각도) */
calc(40deg + 2rad)
calc(420deg - 1turn)

하지만, 아래처럼 아예 '데이터 타입'이 다른 것들끼리 섞어서 더하거나 빼는 것은 유효하지 않습니다. 이런 계산은 의미가 없기 때문이죠.

/* 틀린 예시 (에러!) */
calc(200px + 100ms) /* 길이에 시간을 더할 수 없습니다 */
calc(50% + 90deg)   /* 길이에 각도를 더할 수 없습니다 */

곱셈 (Multiplication)

CSS에서 곱셈을 할 때는, 오직 하나의 값만 단위를 가질 수 있습니다. 나머지 곱해지는 값은 반드시 단위가 없는 순수한 숫자(<number>)여야 합니다. 왜냐하면 곱셈은 기존 단위의 양을 늘리거나 줄이는 용도로 써야지, pxpx를 곱해서 '픽셀 제곱(px²)'이라는 CSS에 존재하지도 않는 이상한 단위를 만들어 내면 안 되기 때문입니다.

/* 올바른 예시 */
calc(200px * 4) /* = 800px (정상 작동) */
calc(60deg * 3) /* = 180deg (정상 작동) */

단위가 있는 두 값을 곱하려고 시도하면 (심지어 그게 같은 단위일지라도) 에러가 발생합니다.

/* 틀린 예시 (에러!) */
calc(200px * 4px) /* 800px² 이라는 단위는 CSS에 없습니다! */

나눗셈 (Division)

CSS에서 나눗셈을 할 때는 단위를 가진 값을 '단위가 없는 순수한 숫자'로 나눌 수 있습니다. (이건 기존부터 되던 방식이죠.)

/* 올바른 예시 */
calc(1000px / 2) /* = 500px */
calc(360deg / 4) /* = 90deg */

하지만 단위가 없는 숫자를 단위가 있는 값으로 나누는 것은 말이 안 되기 때문에 에러가 납니다.

/* 틀린 예시 (에러!) */
calc(1000 / 2px) /* ?!? 의미를 알 수 없음 */

[⭐ 핵심 업데이트]
이제 최신 CSS에서는 특정 단위의 값을 '같은 데이터 타입을 가진 다른 단위의 값'으로 나눌 수 있습니다! 이렇게 나누면 단위끼리 서로 약분(cancel out)되어, 최종적으로 '단위가 없는 순수한 숫자(Unitless value)'만 쏙 빠져나오게 됩니다. 내부적으로 브라우저는 두 값을 같은 단위로 맞춘 뒤에 나눗셈을 수행합니다.

이로 인해 같은 계산식이라도 '어떤 단위로 나누느냐'에 따라, 그리고 '실행되는 환경(브라우저 창 크기 등)'에 따라 아주 다른 결과가 나올 수 있습니다.

예를 들어 봅시다:

calc(100vw / 1px)

100vw는 현재 뷰포트(브라우저 창) 너비의 100%를 의미하죠. 만약 현재 뷰포트 너비가 1000px이라면, 위 계산식은 1000px / 1px이 되어 단위가 떨어져 나가고 순수한 숫자 1000을 반환합니다. 만약 뷰포트가 500px이라면 500이 나오겠죠.

만약 나누는 값(divisor)을 1px이 아니라 1em으로 바꾼다면 결과는 완전히 달라집니다:

calc(100vw / 1em)

뷰포트 너비가 1000px이고 브라우저의 기본 폰트 크기(1em)가 16px인 환경이라면, 위 계산은 1000px / 16px로 계산되어 순수한 숫자 62.5를 반환하게 됩니다!

👨‍🏫 강사님의 꿀팁:
과거에는 100vw1px로 나누면 브라우저가 에러를 뿜었습니다. 하지만 이제는 이 문법이 허용됩니다! 이 "px로 나눠서 단위 떼어내기" 스킬은 최신 CSS 테크닉에서 마법과도 같은 역할을 합니다.


이 기능이 왜 유용한가요? (Why is the behavioral update useful?)

"단위 있는 값을 다른 단위로 나눠서 순수한 숫자를 만든다고? 그게 뭐 어쨌다는 건데?"라고 생각하실 수 있습니다. 하지만 이 기능 덕분에 우리는 전혀 다른 타입의 값들 사이에 연결 고리를 만들어서 기가 막힌 '반응형 UI'를 구축할 수 있게 되었습니다.

이 모든 마법의 핵심은 아래 코드처럼 동적인 값(vw 등)을 단위 없는 숫자 형태로 추출해 변수에 담아두는 데서 시작합니다:

:root {
  /* 현재 브라우저 너비(px)를 순수한 숫자로 추출해서 변수에 저장! */
  --viewport-width-in-pixels: calc(100vw / 1px);
}

이제 --viewport-width-in-pixels에는 브라우저 너비에 따라 실시간으로 변하는 숫자가 담겼습니다. 이 숫자는 숫자가 들어갈 수 있는 모든 CSS 속성이나 calc() 함수 안에 마음대로 쑤셔 넣을 수 있죠!

예를 들어, 브라우저 너비에 따라 투명도(opacity)가 변하게 만들 수 있습니다. (opacity는 단위를 안 받는 속성이죠!)

/* 창이 넓어질수록 투명도가 변합니다 */
opacity: calc(var(--viewport-width-in-pixels) / 1000 - 0.5);

이 숫자에 1deg를 곱하면 즉석에서 각도(<angle>) 값으로 둔갑시킬 수 있습니다:

/* 창 너비가 1000px이면 1000도로 회전합니다 */
rotate: calc(var(--viewport-width-in-pixels) * 1deg);

또는 1rem을 곱해서 순식간에 길이(<length>) 값으로 만들어 폰트 사이즈에 적용할 수도 있습니다:

/* 브라우저 창 너비에 완벽하게 비례하는 폰트 사이즈 생성! */
font-size: calc(var(--viewport-width-in-pixels) * 1rem / 200);

정말 엄청나지 않나요? 이제 이 기법을 활용한 실제 예제들을 살펴봅시다!


기본 예제: 반응형 배경 투명도 (Basic example: responsive background opacity)

이 예제는 뷰포트(브라우저 창)의 너비가 좁아짐에 따라 배경 이미지의 투명도(opacity)가 자동으로 조절되는 효과를 보여줍니다. 창이 좁아져서 글자와 배경 이미지가 겹치기 시작할 때쯤, 배경 이미지가 스르륵 흐려지도록 만들어서 글자의 가독성을 해치지 않게 하는 멋진 기법이죠.

HTML

<div class="wrapper">
  <h1>Prose of the century</h1>
  <p>Lorem ipsum dolor sit amet...</p>
</div>

CSS

먼저 :root--width-percentage라는 커스텀 속성(변수)을 만듭니다. 여기에 100vw / 2000px라는 계산식을 넣습니다.
이 값은 뷰포트 너비가 2000px일 때 정확히 1 (100% 투명도)이 되고, 2000px보다 작아질수록 1보다 작은 소수점이 됩니다.

:root {
  --width-percentage: calc((100vw / 2000px));
}

참고: 색상값 등에서 알파(투명도) 값이 1을 초과하더라도 브라우저는 알아서 최대치인 1로 처리해 주므로, 굳이 clamp()를 써서 최댓값을 묶어둘 필요는 없습니다.

그다음 <body>에 여러 겹의 배경(multiple backgrounds)을 깝니다. 가장 밑에는 예쁜 하트 그림을 깔고, 그 위를 linear-gradient를 사용해 흰색(white)으로 덮어버리는데, 이 흰색 그라데이션의 '투명도(Alpha)' 자리에 방금 우리가 만든 변수를 활용한 계산식을 넣습니다.

body {
  background:
    linear-gradient(
      /* 뷰포트가 2000px이면 알파값이 0(투명), 
         뷰포트가 좁아질수록 알파값이 1에 가까워짐(불투명한 흰색) */
      rgb(255 255 255 / calc(1 - var(--width-percentage))),
      rgb(255 255 255 / calc(1 - var(--width-percentage)))
    ),
    url("[https://mdn.github.io/shared-assets/images/examples/colorful-heart.png](https://mdn.github.io/shared-assets/images/examples/colorful-heart.png)")
      no-repeat top 50px right 50px;
}

결과적으로 브라우저 창이 좁아질수록 덮고 있는 흰색 막이 불투명해지면서 밑에 깔린 하트 그림을 흐릿하게 가려버리는 효과가 나타납니다. 자바스크립트 리사이즈 이벤트 없이 오직 CSS 계산식만으로 이루어낸 것이죠!


하나의 값을 기준으로 여러 타입의 값 변화시키기 (Varying values of different types based on a single value)

다음 예제는 하나의 동적 변수(--viewport-in-pixels)를 사용해서, 데이터 타입이 완전히 다른 여러 속성(폰트 크기와 배경색)을 동시에 반응형으로 바꾸는 마법을 보여줍니다.

HTML

<p>Hello there!</p>

CSS

앞서 배운 대로 100vw1px로 나누어 창 너비를 순수한 숫자로 추출합니다.

:root {
  --viewport-in-pixels: calc(100vw / 1px);
}

이제 <p> 태그에 두 가지 재미있는 수식을 적용해 봅시다:
1. font-size: 추출한 숫자를 200으로 나눈 뒤, 1em을 곱해서 길이(length) 값으로 변신시킵니다.
2. background-color: 최신 색상 함수인 lch()를 사용합니다. 명도(Lightness)와 채도(Chroma)는 고정해 두고, 색상(Hue) 부분에 수식을 넣습니다. 추출한 숫자를 10으로 나눈 뒤 100을 더하고, 거기에 1deg를 곱해서 각도(angle) 값으로 변신시킵니다!

p {
  border: 5px solid black;
  text-align: center;
  /* 브라우저가 커질수록 글씨가 점점 커집니다 */
  font-size: calc(1em * (var(--viewport-in-pixels) / 200));
  /* 브라우저가 커질수록 배경색(Hue)이 빙글빙글 돌아가며 변합니다! */
  background-color: lch(
    75% 50% calc((100 + (var(--viewport-in-pixels) / 10)) * 1deg)
  );
}

(참고로 lch() 함수의 Hue 자리는 단위 없는 숫자를 넣어도 알아서 작동하지만, 여기서 다른 단위로 변환할 수 있다는 걸 보여주기 위해 굳이 1deg를 곱했습니다!)

이 페이지에서 직접 코드를 테스트해 보시길 원하신다면, 아래에 제가 준비한 인터랙티브 계산기 위젯을 사용해 보세요! 뷰포트 너비를 입력하면 내부적으로 어떤 계산이 일어나는지 한눈에 볼 수 있습니다.

{"component":"LlmGeneratedComponent","props":{"height":"500px","prompt":"Create an interactive CSS Typed Arithmetic Calculator inspired by the MDN concepts. The layout should be modern and dark-themed. Use a Form Layout. \n\nInputs:\n1. 'Simulated Viewport Width (100vw)': A range slider from 320px to 2000px. Default to 1000px.\n\nOutputs (Real-time calculation display):\n1. 'Unitless Base Value (--viewport-in-pixels)': Show the calculation `100vw / 1px` yielding just the raw number (e.g., 1000).\n2. 'Dynamic Font Size (em)': Show the formula `calc(1em * (Base Value / 200))` and the resulting output (e.g., 5em).\n3. 'Dynamic Hue Angle (deg)': Show the formula `calc((100 + (Base Value / 10)) * 1deg)` and the resulting output (e.g., 200deg).\n\nVisual/Behavior:\nBelow the formulas, display a prominent 'Preview Box' containing sample text. As the user moves the slider, dynamically update the Preview Box's `font-size` and `background-color` (using hsl or lch if supported, based on the calculated hue) in real-time to visually demonstrate the power of CSS typed arithmetic without needing to resize the actual browser window. Ensure smooth transitions and high contrast for readability.","id":"im_7a51d49b724890a2"}}

애니메이션 스토리 서클 만들기 (An animated story circle)

마지막 애니메이션 스토리 서클 예제는 CSS 타입 연산, 컨테이너 쿼리(Container Queries), 그리고 형제 인덱스 함수(sibling-index()) 등 최신 기술들을 모조리 쏟아부어 만든 걸작입니다.

부모 컨테이너(<body>)의 너비가 좁아지면 글자들이 접힌 부채처럼 모여 있고, 너비가 넓어지면 스르륵 펴지면서 완벽한 둥근 원형을 그리며 배치되는 엄청난 효과를 보여줍니다. 자세한 원리는 MDN 링크의 소스 코드를 통해 분석해 보세요!


같이 보기 (See also)


MDN 개선에 도움을 주세요 (Help improve MDN)

이 페이지가 도움이 되었나요? (Was this page helpful to you?)

이 페이지는 MDN 기여자들에 의해 2025년 11월 7일에 마지막으로 수정되었습니다.


수고하셨습니다! 오늘은 자바스크립트의 전유물로만 여겨졌던 복잡한 반응형 연산을 CSS의 calc()와 단위 제거 스킬( / 1px)을 통해 우아하게 해결하는 방법을 배웠습니다. 실무에서 브라우저 크기에 맞춰 폰트나 여백이 유동적으로 변하는 타이포그래피(Fluid Typography)를 구현할 때 이 스킬이 정말 강력한 무기가 될 테니 꼭 기억해 두세요!

profile
프론트에_가까운_풀스택_개발자

0개의 댓글