CSS 변수와 계산식
지난 장에서 우리는 CSS 트랜지션과 애니메이션을 통해 웹 페이지에 생동감을 불어넣는 방법을 배웠습니다. 이제는 CSS 코드를 더 효율적이고 유지보수하기 쉽게 만드는 고급 기법인 CSS 변수(Custom Properties) 와 계산식(calc()
함수) 에 대해 알아볼 차례입니다. 이 두 가지 기능은 CSS의 유연성을 극대화하여 대규모 프로젝트나 재사용 가능한 컴포넌트 개발에 필수적인 역할을 합니다.
기존 CSS는 변수나 동적인 계산 기능이 부족하여, 특정 값을 여러 곳에서 사용해야 할 때마다 일일이 수정해야 하는 번거로움이 있었습니다. 또한, 두 가지 이상의 값을 조합하여 새로운 값을 계산하는 것이 어려웠습니다. CSS 변수와 계산식은 이러한 불편함을 해소하고, 더 깔끔하고 강력한 CSS 코드를 작성할 수 있도록 돕습니다.
이 장에서는 CSS 변수를 선언하고 사용하는 방법, 변수의 스코프(범위) 이해하기, 그리고 calc()
함수를 이용한 동적인 값 계산 방법에 대해 상세히 학습합니다. 이 지식은 여러분의 CSS 작성 생산성과 코드의 재활용성을 크게 향상시킬 것입니다.
CSS 변수 (Custom Properties) 이해하기
CSS 변수는 말 그대로 CSS 파일 내에서 재사용할 수 있는 사용자 정의 속성입니다. 변수를 한 번 정의해두면, 이 변수 값을 여러 CSS 속성에서 반복적으로 사용할 수 있으며, 필요할 때 변수 값만 변경하면 해당 변수를 사용하는 모든 곳에 자동으로 반영됩니다. 이는 웹사이트의 디자인 일관성을 유지하고, 변경 사항을 적용하는 시간을 크게 단축시켜 줍니다.
변수 선언 및 사용 방법
CSS 변수는 두 개의 하이픈(--
)으로 시작하는 사용자 정의 속성 이름으로 선언합니다.
/* 변수 선언 */
:root { /* 문서 전체에 적용되는 전역 변수 */
--primary-color: #3498db; /* 메인 색상 */
--secondary-color: #2ecc71; /* 보조 색상 */
--font-size-base: 16px; /* 기본 폰트 크기 */
--spacing-unit: 10px; /* 간격 단위 */
}
/* 변수 사용 */
body {
font-size: var(--font-size-base);
color: var(--primary-color);
}
.button {
background-color: var(--secondary-color);
padding: var(--spacing-unit) calc(var(--spacing-unit) * 2); /* calc() 함수와 함께 사용 */
}
.header {
border-bottom: 1px solid var(--primary-color);
}
--변수이름: 값;
: 변수를 선언하는 문법입니다. 변수 이름 앞에는 반드시--
를 붙여야 합니다.var(--변수이름)
: 선언된 변수 값을 가져와 사용합니다.
변수의 스코프 (Scope)
CSS 변수는 일반적인 CSS 속성처럼 캐스케이드(Cascade)되고 상속(Inheritance)됩니다. 이는 변수가 선언된 위치에 따라 적용되는 범위, 즉 '스코프'가 달라진다는 의미입니다.
-
전역(Global) 스코프:
html
또는:root
선택자 내에 선언된 변수는 문서 전체에서 접근 가능합니다.:root
가 더 일반적입니다.:root { --global-bg: #f0f0f0; } body { background-color: var(--global-bg); /* 어디서든 사용 가능 */ }
-
지역(Local) 스코프: 특정 선택자 내에 선언된 변수는 해당 선택자와 그 자식 요소 내에서만 유효합니다.
.card-container { --card-border-color: #ddd; /* .card-container와 그 자식에만 유효 */ border: 1px solid var(--card-border-color); } .card-container .card-item { border: 1px solid var(--card-border-color); /* 부모로부터 상속받아 사용 */ } .another-element { /* background-color: var(--card-border-color); /* 이곳에서는 사용할 수 없음 */ }
스코프 우선순위: 더 구체적인(나중에 선언된 또는 더 깊은 스코프의) 변수가 상위 스코프의 변수보다 우선합니다. 이를 활용하여 특정 컴포넌트 내에서 전역 변수를 오버라이드(재정의)할 수 있습니다.
:root { --text-color: #333; /* 전역 텍스트 색상 */ } .dark-theme { --text-color: #eee; /* .dark-theme 내부에서는 텍스트 색상이 변경됨 */ background-color: #333; } p { color: var(--text-color); /* 상황에 따라 다른 색상 적용 */ }
변수의 장점
- 유지보수 용이: 디자인 변경 시 변수 값 하나만 수정하면 전체 웹사이트에 일괄 적용됩니다.
- 일관성 유지: 동일한 값을 여러 번 입력하는 실수를 줄여 디자인의 일관성을 높입니다.
- 가독성 향상:
--primary-color
와 같이 의미 있는 이름으로 변수를 선언하여 코드 이해도를 높입니다. - 테마 변경 용이: 다크 모드, 라이트 모드 등 테마 기능을 구현할 때 HTML 요소에 클래스만 토글하여 쉽게 테마를 변경할 수 있습니다.
- JavaScript와의 상호작용: JavaScript로 CSS 변수 값을 읽고 쓸 수 있어 동적인 스타일 변경에 유용합니다. (다음 장에서 다룰 예정)
CSS 계산식 (calc()
) 이해하기
calc()
함수는 CSS 속성 값 내에서 사칙연산(+
, -
, *
, /
)을 수행하여 동적인 값을 계산할 수 있게 해줍니다. 이는 특히 다양한 단위(px, %, em, rem, vw, vh)가 혼합될 때 유용하며, 유연하고 반응형인 레이아웃을 만드는 데 필수적입니다.
calc()
함수 기본 문법
calc()
함수 안에 계산식을 작성합니다.
.element {
width: calc(100% - 40px); /* 부모 너비에서 40px을 뺀 너비 */
height: calc(50vh + 2em); /* 뷰포트 높이의 절반에 2em을 더한 높이 */
font-size: calc(16px + 0.5vw); /* 뷰포트 너비에 따라 폰트 크기 조절 */
margin-left: calc(var(--spacing-unit) / 2); /* 변수와 함께 사용 */
}
- 연산자 주의사항:
- 덧셈(
+
)과 뺄셈(-
) 연산자 주위에는 반드시 공백이 있어야 합니다. (100% - 20px
은 가능하지만,100%-20px
은 오류) - 곱셈(
*
)과 나눗셈(/
) 연산자 주위에는 공백이 필수는 아니지만, 가독성을 위해 권장됩니다. - 곱셈과 나눗셈은 단위가 통일되지 않아도 가능합니다. (예:
100px * 2
또는100px / 2
) - 나눗셈은 두 번째 피연산자가
0
이 될 수 없습니다. (오류 발생)
- 덧셈(
calc()
활용 예시
다양한 상황에서 calc()
함수를 활용해 봅시다.
-
반응형 레이아웃에서 여백 제외
.main-content { width: calc(100% - 40px); /* 좌우 20px씩 총 40px의 패딩을 제외한 너비 */ margin: 20px auto; padding: 20px; }
-
동적인 그리드 또는 플렉스 아이템 너비
.grid-item { /* 3개의 아이템을 나란히 배치하면서 20px의 간격을 고려한 너비 */ width: calc((100% - (20px * 2)) / 3); /* (전체 너비 - 총 간격) / 아이템 수 */ /* 또는 Grid의 gap 속성 사용 시: grid-template-columns: repeat(3, 1fr); gap: 20px; */ }
-
최소/최대 크기 설정과 결합
h1 { font-size: calc(2em + 2vw); /* 최소 2em에서 뷰포트 너비에 따라 커짐 */ min-font-size: 2em; /* calc() 자체는 min/max를 제공하지 않으므로 추가 속성 필요 */ /* 또는 clamp() 함수 (최신 CSS) 사용: clamp(최소값, 권장값, 최대값) */ /* font-size: clamp(2em, 2em + 2vw, 4em); */ }
-
변수와 함께 사용
:root { --base-padding: 15px; } .card { padding: var(--base-padding); margin-bottom: calc(var(--base-padding) * 2); /* 기본 패딩의 2배 마진 */ }
calc()
의 장점
- 유연한 레이아웃: 고정된 값과 유동적인 값을 혼합하여 더 복잡하고 유연한 레이아웃을 쉽게 구현할 수 있습니다.
- 단위 혼합: 서로 다른 CSS 단위를 함께 사용하여 계산할 수 있습니다.
- 반응형 디자인: 미디어 쿼리 없이도 어느 정도 반응형 동작을 구현할 수 있습니다.
- 유지보수성: 동적인 계산이 필요한 부분을 코드 내에 명확히 표현하여 가독성을 높이고, 값 변경 시 계산이 자동으로 업데이트됩니다.
실습: 웹 컴포넌트 만들기
CSS 변수와 calc()
함수를 활용하여 재사용 가능한 UI 컴포넌트를 만들어 봅시다. 이번 실습에서는 공통된 스타일과 유연한 너비를 가진 카드 컴포넌트를 구현합니다.
-
프로젝트 폴더 구조
web-dev-practice/ ├── variables_calc.html └── css/ └── component.css
-
variables_calc.html
파일 작성variables_calc.html <!DOCTYPE html> <html lang="ko"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>CSS 변수와 calc() 실습</title> <link rel="stylesheet" href="css/component.css"> </head> <body> <header> <h1>CSS 변수 & calc()</h1> <p>더 스마트한 CSS를 위한 도구</p> </header> <main> <section class="section-container"> <h2>제품 카드 목록</h2> <div class="product-grid"> <div class="product-card"> <img src="https://via.placeholder.com/280x200?text=Product+A" alt="제품 A"> <h3>스마트폰</h3> <p>최신 기술이 집약된 고성능 스마트폰입니다.</p> <span class="price">990,000원</span> <button class="buy-button">구매하기</button> </div> <div class="product-card"> <img src="https://via.placeholder.com/280x200?text=Product+B" alt="제품 B"> <h3>무선 이어폰</h3> <p>선명한 음질과 편안한 착용감을 제공합니다.</p> <span class="price">159,000원</span> <button class="buy-button">구매하기</button> </div> <div class="product-card"> <img src="https://via.placeholder.com/280x200?text=Product+C" alt="제품 C"> <h3>스마트 워치</h3> <p>건강 관리에 필수적인 스마트 워치.</p> <span class="price">299,000원</span> <button class="buy-button">구매하기</button> </div> <div class="product-card"> <img src="https://via.placeholder.com/280x200?text=Product+D" alt="제품 D"> <h3>노트북</h3> <p>강력한 성능과 휴대성을 겸비한 노트북.</p> <span class="price">1,500,000원</span> <button class="buy-button">구매하기</button> </div> </div> </section> <section class="section-container dark-theme"> <h2>다크 테마 예시</h2> <div class="theme-box"> <p>이 박스는 다크 테마가 적용되어 있습니다. 변수의 스코프를 확인해보세요.</p> <button class="theme-button">테마 전환 (JS로 구현 가능)</button> </div> </section> </main> <footer> <p>© 2025 변수와 계산식 실습. 모든 권리 보유.</p> </footer> </body> </html>
-
css/component.css
파일 작성css/component.css /* 뷰포트 메타 태그가 HTML에 설정되어 있는지 확인! */ /* 기본 설정 */ * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background-color: #f0f2f5; color: var(--text-color, #333); /* 변수 사용, 폴백(fallback) 값 지정 */ line-height: 1.6; padding-bottom: 50px; } /* CSS 변수 선언 (전역 스코프) */ :root { --primary-color: #3498db; /* 파란색 */ --secondary-color: #2ecc71; /* 초록색 */ --accent-color: #e67e22; /* 주황색 */ --background-light: #f0f2f5; /* 밝은 배경색 */ --background-card: white; /* 카드 배경색 */ --text-color: #333; /* 기본 텍스트 색상 */ --border-color: #ddd; /* 테두리 색상 */ --box-shadow-light: 0 2px 10px rgba(0,0,0,0.1); /* 밝은 그림자 */ --base-padding: 15px; /* 기본 패딩 단위 */ --card-gap: 20px; /* 카드 간 간격 */ --button-padding-vertical: 10px; /* 버튼 세로 패딩 */ --button-padding-horizontal: 20px; /* 버튼 가로 패딩 */ } /* 다크 테마 변수 오버라이드 (지역 스코프) */ .dark-theme { --background-light: #2c3e50; --background-card: #34495e; --text-color: #ecf0f1; --border-color: #4a6581; --box-shadow-light: 0 4px 12px rgba(0,0,0,0.3); } /* 헤더 스타일 */ header { background-color: var(--primary-color); color: white; text-align: center; padding: calc(var(--base-padding) * 2) var(--base-padding); box-shadow: var(--box-shadow-light); margin-bottom: 30px; } header h1 { font-size: 2.8em; margin-bottom: calc(var(--base-padding) / 2); } header p { font-size: 1.2em; } /* 섹션 공통 스타일 */ .section-container { background-color: var(--background-light); padding: calc(var(--base-padding) * 2); margin-bottom: 30px; border-radius: 8px; box-shadow: var(--box-shadow-light); max-width: 1200px; margin: 0 auto 30px auto; color: var(--text-color); } .section-container h2 { color: var(--primary-color); text-align: center; margin-bottom: calc(var(--base-padding) * 2); font-size: 2em; } /* 제품 카드 그리드 */ .product-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); /* 반응형 그리드 */ gap: var(--card-gap); justify-content: center; } .product-card { background-color: var(--background-card); border: 1px solid var(--border-color); border-radius: 8px; padding: var(--base-padding); text-align: center; box-shadow: var(--box-shadow-light); display: flex; /* 내부 콘텐츠를 위한 Flexbox */ flex-direction: column; justify-content: space-between; align-items: center; } .product-card img { max-width: 100%; height: auto; border-radius: 5px; margin-bottom: var(--base-padding); } .product-card h3 { font-size: 1.4em; color: var(--primary-color); margin-bottom: calc(var(--base-padding) / 2); } .product-card p { font-size: 0.95em; color: var(--text-color); margin-bottom: var(--base-padding); flex-grow: 1; /* 단락이 남은 공간 차지 */ } .product-card .price { font-size: 1.3em; font-weight: bold; color: var(--accent-color); margin-bottom: var(--base-padding); } .buy-button { background-color: var(--secondary-color); color: white; padding: var(--button-padding-vertical) var(--button-padding-horizontal); border: none; border-radius: 5px; cursor: pointer; font-size: 1em; transition: background-color 0.3s ease; } .buy-button:hover { background-color: #27ae60; } /* 다크 테마 박스 */ .theme-box { background-color: var(--background-card); border: 1px solid var(--border-color); padding: calc(var(--base-padding) * 2); border-radius: 8px; text-align: center; box-shadow: var(--box-shadow-light); margin: 0 auto; width: 80%; max-width: 600px; } .theme-box p { margin-bottom: var(--base-padding); color: var(--text-color); } .theme-button { background-color: var(--primary-color); color: white; padding: var(--button-padding-vertical) var(--button-padding-horizontal); border: none; border-radius: 5px; cursor: pointer; font-size: 1em; transition: background-color 0.3s ease; } .theme-button:hover { background-color: #2980b9; } /* 푸터 스타일 */ footer { background-color: var(--primary-color); color: white; text-align: center; padding: calc(var(--base-padding) + 5px) 0; margin-top: 50px; font-size: 0.9em; }
-
결과 확인
- Live Server를 통해
variables_calc.html
파일을 열어보세요. --primary-color
와 같은 변수들이 웹 페이지 전반에 걸쳐 어떻게 사용되었는지 확인하세요.header
의padding
이나product-grid
의gap
에서calc()
함수가 어떻게 사용되었는지 CSS 코드를 확인해 보세요.- 브라우저의 개발자 도구(F12)를 열어 Elements 탭에서
.product-card
나.theme-box
같은 요소들을 선택하고 Styles 탭을 확인해 보세요. Computed 탭에서calc()
로 계산된 최종 값이 어떻게 적용되었는지 볼 수 있습니다. - 특히
.dark-theme
클래스가 적용된 섹션의 배경색, 텍스트 색상, 테두리 색상 등이 전역 변수와 다르게 적용되는 것을 확인하세요. 이는 지역 스코프 변수가 전역 변수를 오버라이드한 결과입니다. CSS 파일에서:root
의--primary-color
값이나--base-padding
값을 변경해 보면, 전체 페이지에 어떻게 즉시 반영되는지 확인할 수 있습니다.
- Live Server를 통해
이번 장에서는 CSS 코드의 효율성과 유지보수성을 극대화하는 두 가지 고급 기법인 CSS 변수(Custom Properties) 와 계산식(calc()
함수) 에 대해 학습했습니다.
- CSS 변수는 재사용 가능한 값을
--변수이름: 값;
형태로 선언하고var(--변수이름)
으로 사용하여, 디자인 일관성을 유지하고 대규모 프로젝트의 스타일 변경을 용이하게 합니다. 또한, 변수의 스코프를 이해하여 전역/지역적으로 값을 제어할 수 있습니다. calc()
함수는 CSS 속성 값 내에서 사칙연산을 수행하여width: calc(100% - 40px);
와 같이 동적이고 유연한 값을 계산할 수 있게 해줍니다. 이는 서로 다른 단위를 혼합하여 사용하거나, 복잡한 레이아웃에서 여백 등을 자동으로 계산하는 데 매우 유용합니다.
이 두 가지 기능을 활용하면 여러분의 CSS 코드는 훨씬 더 강력하고 유연하며, 관리하기 쉬워질 것입니다. 이는 복잡한 웹 애플리케이션이나 디자인 시스템을 구축할 때 필수적인 역량입니다.