이미지와 비디오는 대체된 요소(replaced elements) 라고 부릅니다. 이게 무슨 뜻이냐면, CSS가 이런 요소들의 '내부' 레이아웃에는 전혀 손을 댈 수 없고, 오직 페이지 내의 다른 요소들 사이에서 '어디에 위치할지'만 결정할 수 있다는 뜻이에요. 하지만 앞으로 보시게 되겠지만, CSS로 이미지를 조작할 수 있는 다양한 방법들이 준비되어 있답니다.
이미지나 비디오 같은 특정 대체 요소들은 종횡비(aspect ratio) 를 가지고 있다고도 표현합니다. 즉, 가로(x)와 세로(y) 양방향으로 고유한 크기를 가지고 있으며, 기본적으로는 해당 파일이 원래 가지고 있는 고유의 크기(intrinsic dimensions) 그대로 화면에 표시된다는 의미입니다.
💡 강사님의 실무 팁!
'대체된 요소'라는 말이 좀 낯설죠? 쉽게 말해 브라우저가 HTML 문서를 읽다가<img>태그를 만나면, "아, 여기엔 내가 외부에서 가져온 사진 파일을 쏙 끼워 넣어서(대체해서) 보여줘야겠구나!" 하고 판단하는 요소들을 말해요. 파일 자체가 가진 픽셀 크기와 비율이 이미 존재하기 때문에 일반적인<div>텍스트 박스들과는 성격이 다를 수밖에 없답니다.
이전 레슨들을 잘 따라오셨다면, CSS의 모든 것은 결국 '박스'를 만들어낸다는 걸 이미 알고 계실 거예요. 만약 이미지 파일의 고유 크기보다 더 작거나 더 큰 박스 안에 이미지를 집어넣으면 어떻게 될까요? 이미지가 박스보다 작게 보이거나, 아니면 박스를 뚫고 넘쳐흐르게(overflow) 됩니다. 이때 넘쳐흐르는 걸 어떻게 처리할지 우리가 결정해 줘야 해요.
아래 예제에는 크기가 딱 200픽셀인 박스 두 개가 있습니다:
<div class="wrapper">
<div class="box">
<img
alt="star"
src="https://mdn.github.io/shared-assets/images/examples/big-star.png" />
</div>
<div class="box">
<img
alt="balloons"
src="https://mdn.github.io/shared-assets/images/examples/balloons.jpg" />
</div>
</div>
.wrapper {
display: flex;
align-items: flex-start;
}
.wrapper > * {
margin: 20px;
}
.box {
border: 5px solid darkblue;
width: 200px;
}
img {
}
이렇게 이미지가 박스를 뚫고 나오는(overflow) 문제는 어떻게 해결할 수 있을까요?
CSS에서 크기 지정하기(Sizing items in CSS) 파트에서 배웠듯이, 가장 흔하게 쓰는 기술은 바로 이미지의 max-width를 100%로 설정하는 것입니다. 이렇게 하면 이미지가 박스 크기보다 작아질 수는 있어도, 절대 박스보다 커지지는 않게 막아줍니다. 이 꿀팁은 이미지뿐만 아니라 <video>나 <iframe> 같은 다른 대체 요소들에도 똑같이 적용할 수 있어요!
위 예제의 <img> 요소 CSS 규칙 안에 max-width: 100%를 한 번 추가해 보세요. 작은 이미지는 크기가 변하지 않지만, 박스를 뚫고 나왔던 큰 이미지는 박스 크기에 쏙 맞게 얌전하게 줄어드는 걸 보실 수 있을 거예요.
💡 강사님의 실무 팁!
img { max-width: 100%; height: auto; }이 두 줄은 실무에서 반응형 웹(Responsive Web)을 만들 때 그냥 눈 감고도 칠 줄 알아야 하는 가장 기본적인 코드 세트입니다! 거의 모든 프로젝트의 기본 CSS 리셋에 꼭 들어가는 녀석들이죠.
object-fit으로 이미지 표시 문제 해결하기 (Handling image display issues with object-fit)위의 방법을 쓰다 보면 또 다른 문제에 직면하게 됩니다. 이미지에 max-width: 100%를 주고 나면, 두 번째 둥둥 떠 있는 풍선 이미지가 부모 컨테이너를 꽉 채우지 못하고 아래쪽에 빈틈(gap)이 남는 걸 발견하셨을 거예요. 이건 이미지의 '너비'를 강제로 맞추다 보니, 원래의 종횡비(aspect ratio)를 깨뜨리지 않기 위해 '높이'도 비율에 맞게 줄어들었기 때문입니다.
그럼 이미지가 컨테이너를 꽉 채우도록(cover) 만들려면 어떻게 해야 할까요? 아래 예제처럼 컨테이너에 고정된 width와 height를 주고, 그 안의 이미지에도 width와 height를 모두 100%로 주면 됩니다:
<div class="box">
<img
alt="balloons"
src="https://mdn.github.io/shared-assets/images/examples/balloons.jpg" />
</div>
.box {
border: 5px solid darkblue;
width: 200px;
height: 200px;
margin: 20px;
}
img {
width: 100%;
height: 100%;
}
하지만 세상에, 종횡비가 무시되면서 풍선이 납작하게 찌그러진(stretched) 모습이 되어버렸네요! 이걸 고치려면 바로 object-fit 속성을 사용하면 됩니다. 이 속성은 이미지가 컨테이너(<img> 요소 그 자체) 안에 핏(fit)되는 방식을 결정해 줍니다. object-fit에는 여러 값이 있지만, 가장 유용하게 쓰이는 두 가지는 다음과 같습니다:
cover: 이미지가 고유의 종횡비를 유지하면서 <img> 요소를 완전히 꽉 채웁니다. 비율을 유지하며 꽉 채우려다 보니, 이미지의 일부분은 화면 밖으로 잘려서 보이지 않게 됩니다.contain: 이미지가 고유의 종횡비를 유지하면서 <img> 요소 안에 쏙 들어가게 온전히 표시됩니다. 대신 이미지와 박스의 비율이 달라서 <img> 요소 안에 남는 빈 공간이 생기게 되죠. 위아래로 레터박스(letterboxing)가 생기거나 좌우로 필러박스(pillarboxing)가 생깁니다.다음 예제는 하나의 똑같은 이미지를 두 번 복사해 두고, 각각 cover와 contain 값을 적용해 본 것입니다. 어떤 차이가 있는지 눈으로 직접 비교해 보세요:
<div class="wrapper">
<div class="box">
<img
alt="balloons"
class="cover"
src="https://mdn.github.io/shared-assets/images/examples/balloons.jpg" />
</div>
<div class="box">
<img
alt="balloons"
class="contain"
src="https://mdn.github.io/shared-assets/images/examples/balloons.jpg" />
</div>
</div>
.wrapper {
display: flex;
align-items: flex-start;
}
.wrapper > * {
margin: 20px;
}
.box {
border: 5px solid darkblue;
width: 200px;
height: 200px;
}
img {
height: 100%;
width: 100%;
}
.cover {
object-fit: cover;
}
.contain {
object-fit: contain;
}
참고:
여기서 잊지 말아야 할 핵심 포인트는 다음과 같습니다:
object-fit속성은 이미지를 품고 있는<img>요소 박스 안에서 '이미지 소스 자체'가 어떻게 사이즈가 조절될지를 결정합니다.object-fit이 제대로 힘을 발휘하려면, 부모 요소가 아니라<img>요소 자체의 너비나 높이가 (예: 100% 등으로) 먼저 지정되어 있어야 합니다.만약
<img>요소의 크기를 조절해주지 않으면, 이미지는 원래 파일이 가진 고유 크기와 비율대로 그려지기 때문에object-fit을 줘봤자 아무런 변화도 일어나지 않아요.
대체 요소(이미지 등)를 CSS 레이아웃 기술(Grid 등)과 함께 사용하다 보면, 이 녀석들이 일반 텍스트 박스 요소들과는 미묘하게 다르게 행동한다는 걸 알게 되실 거예요. 예를 들어 그리드(grid) 레이아웃에서는 요소들이 기본적으로 자신이 속한 그리드 영역(grid areas)을 꽉 채우기 위해 쭉 늘어납니다(stretch). 하지만 이미지는 절대 혼자서 억지로 늘어나지 않고, 대신 그리드 영역의 시작점(start)에 딱 맞춰 정렬됩니다.
아래 예제를 통해 그 현상을 볼 수 있습니다. 2열 2행짜리 그리드 컨테이너 안에 총 4개의 아이템이 들어있어요. 일반적인 <div> 요소들은 배경색이 칠해져 있고 해당 행과 열을 꽉 채우기 위해 쫙 늘어났지만, 정작 이미지는 자기 크기를 고집하며 늘어나지 않았죠.
<div class="wrapper">
<img
alt="star"
src="https://mdn.github.io/shared-assets/images/examples/big-star.png" />
<div></div>
<div></div>
<div></div>
</div>
.wrapper {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 100px 100px;
gap: 20px;
}
.wrapper > div {
background-color: rebeccapurple;
border-radius: 0.5em;
}
이런 디테일한 레이아웃 속성들은 나중에 더 깊이 배우게 되실 거예요. 지금은 대체 요소(이미지 등)가 Flexbox나 Grid 같은 레이아웃 시스템 안에 들어가면, 레이아웃 때문에 강제로 기괴하게 찌그러지는 현상을 막기 위해 기본 동작 방식이 조금 다르게 설정되어 있다는 점만 머릿속에 담아두시면 됩니다.
폼(Form) 요소들은 CSS로 스타일링하려고 할 때 꽤 골칫거리가 되곤 합니다. 웹 폼 스타일링 모듈(Web Forms extensions module)에서 특정 폼 인풋 타입들을 스타일링하는 더 복잡한 테크닉들을 자세히 다루겠지만, 여기서는 그 정도까지 깊이 들어가진 않을 거예요. 대신 이 섹션에서 꼭 알고 넘어가야 할 핵심 기초들만 짚어보겠습니다.
여러분의 페이지에 들어가는 대부분의 폼 컨트롤들은 <input> 태그로 만들어집니다. 단순한 텍스트 입력창부터 색상 선택기, 날짜 선택기 같은 복잡한 위젯까지 모두 이 태그를 쓰죠. 그 외에도 여러 줄의 텍스트를 입력받는 <textarea>도 있고, 폼의 각 부분을 그룹으로 묶고 이름표를 달아주는 <fieldset>이나 <legend> 같은 요소들도 있습니다.
HTML에는 필수(required) 입력 필드인지 지정하거나, 사용자가 입력해야 하는 데이터의 종류(이메일, 숫자 등)를 지정할 수 있는 속성(attribute)들도 있습니다. 만약 사용자가 이상한 값을 입력하거나 필수 칸을 비워두고 넘어가려 하면 브라우저가 알아서 에러 메시지를 띄워주기도 하죠. 그런데 문제는 브라우저(크롬, 사파리, 사파리 모바일 등)마다 이런 폼 요소들의 기본 디자인과, 개발자가 커스텀(수정)할 수 있는 자유도의 범위가 제각각이라는 점입니다.
그래도 <input type="text"> 나 조금 더 구체적인 <input type="email">, 그리고 <textarea> 같은 텍스트 입력용 요소들은 스타일링하기가 꽤 쉬운 편입니다. 페이지의 다른 일반 박스 요소들처럼 비슷하게 동작하거든요. 하지만 아까 말씀드렸듯이, 사용자의 운영체제(OS)나 접속한 브라우저에 따라 '기본(default)' 디자인은 조금씩 다르게 보일 겁니다.
아래 예제에서 CSS를 사용해 텍스트 입력창들을 예쁘게 꾸며 보았습니다. 코드를 보면 테두리(borders), 여백(margins), 내부 여백(padding) 등이 우리가 아는 대로 아주 잘 적용된다는 걸 알 수 있어요. 여기서는 [type="text"] 같은 속성 선택자(attribute selectors)를 사용해 특정 인풋 타입들만 쏙쏙 골라서 스타일을 먹이고 있습니다.
보더(테두리) 두께를 바꿔보거나, 배경색을 칠해보거나, 폰트나 패딩 값을 직접 이리저리 수정해 보면서 폼이 어떻게 다르게 변하는지 확인해 보세요!
<form>
<div><label for="name">Name</label> <input id="name" type="text" /></div>
<div><label for="email">Email</label> <input id="email" type="email" /></div>
<div class="buttons"><input type="submit" value="Submit" /></div>
</form>
input[type="text"],
input[type="email"] {
border: 2px solid black;
margin-bottom: 1em;
padding: 10px;
width: 80%;
}
input[type="submit"] {
border: 3px solid #333333;
background-color: #999999;
border-radius: 5px;
padding: 10px 2em;
font-weight: bold;
color: white;
}
input[type="submit"]:hover,
input[type="submit"]:focus {
background-color: #333333;
}
MDN Playground에서 직접 예제 코드 만져보기
경고:
폼 요소들의 스타일을 파격적으로 바꿀 때는, 이게 폼 요소라는 것을 사용자가 직관적으로 알아챌 수 있도록 반드시 신경 써야 합니다. 예를 들어 테두리도 없애고 배경색도 다 지워버려서 주변의 일반 텍스트 글과 아예 구분이 안 가게 만들 수도 있지만, 그러면 사용자는 어디를 클릭해서 글자를 입력해야 할지 찾지 못해 엄청난 혼란을 겪게 될 거예요.
체크박스나 라디오 버튼, 셀렉트 박스 같이 조금 더 복잡한 인풋 타입들은 아예 운영체제(OS) 자체에서 그려내기 때문에 CSS 스타일링이 전혀 먹히지 않는 경우가 많습니다. 그러니 항상 명심하세요! "내가 만든 폼은 접속하는 기기나 브라우저에 따라 꽤 다르게 보일 수 있다"고 가정하고, 작업 후에는 꼭 여러 브라우저를 켜서 폼이 잘 나오는지 테스트해보는 습관을 들이셔야 합니다.
폼 요소들은 브라우저나 운영체제마다 제각각 다르게 동작하고 다르게 생겼다고 했죠? 이 섹션에서는 가장 흔하게 겪는 골치 아픈 문제점들과, 그걸 어떻게 해결할 수 있는지에 대한 전략을 알아볼게요.
일부 브라우저에서는 폼 요소들이 기본적으로 부모 요소의 폰트 스타일을 '상속(inherit)'받지 않습니다. 그래서 폼 입력창 안의 글자가 내가 전체 문서(body)나 부모 컨테이너에 예쁘게 설정해 둔 그 폰트로 나오게 하려면, 아래처럼 직접 "부모 폰트를 물려받으렴!" 하고 CSS 규칙을 명시적으로 적어줘야 합니다.
button,
input,
select,
textarea {
font-family: inherit;
font-size: 100%;
}
박스 모델 레슨에서 box-sizing 속성에 대해 배우셨을 텐데요. 브라우저마다 폼의 여러 위젯 요소들에 서로 다른 box-sizing 기본값을 적용하고 있습니다. 그래서 폼 요소들의 너비와 높이를 일관성 있게 제어하고 싶다면, 예전에 배운 지식을 활용해 폼 요소들의 박스 사이즈 계산 기준을 하나로 맞춰주는 것이 좋습니다.
가장 깔끔한 방법은 모든 폼 요소의 마진과 패딩을 0으로 싹 초기화한 다음, 나중에 필요한 컨트롤에만 개별적으로 여백을 다시 추가해 주는 것입니다:
button,
input,
select,
textarea {
box-sizing: border-box;
padding: 0;
margin: 0;
}
위에서 살펴본 규칙들에 더해서, <textarea> 요소에는 꼭 overflow: auto를 설정해 두는 것이 좋습니다. 이걸 넣어주지 않으면 구형 브라우저들에서는 굳이 스크롤이 필요 없을 정도로 글이 적은데도 거추장스럽게 스크롤바를 띄워놓거든요.
textarea {
overflow: auto;
}
마지막으로, 위에서 열심히 설명한 세 가지 섹션의 내용들을 하나로 싹 모아서 이른바 "폼 리셋(form reset)" 코드를 만들어 볼 수 있습니다. 이 코드를 프로젝트 최상단에 딱 박아두면, 어떤 환경에서든 일관성 있는 베이스라인을 잡고 개발을 시작할 수 있죠.
button,
input,
select,
textarea {
font-family: inherit;
font-size: 100%;
box-sizing: border-box;
padding: 0;
margin: 0;
}
textarea {
overflow: auto;
}
참고:
수많은 프론트엔드 개발자들이 모든 프로젝트를 시작할 때 기준점을 잡기 위해 이러한 '정규화(Normalizing) 스타일시트'를 가져다 씁니다. 이런 스타일시트들은 방금 우리가 만든 코드처럼, 브라우저마다 제각각인 기본값들을 우리가 디자인 작업을 시작하기 전에 싹 다 일관되게 맞춰주는 역할을 하죠.
물론 요즘 브라우저들은 과거에 비해 훨씬 서로 비슷해져서 그 중요성이 예전만큼 절대적이진 않지만요. 그래도 실무에서 어떻게 쓰이는지 진짜 예시를 보고 싶으시다면 전 세계 수많은 프로젝트의 뼈대가 되고 있는 아주 유명한 Normalize.css 를 한번 구경해 보세요!
이번 레슨에서는 이미지나 기타 미디어, 그리고 조금 까탈스러운 폼(form) 요소들을 다룰 때 여러분이 CSS에서 마주치게 될 여러 가지 독특한 차이점들을 짚어 보았습니다.