문서 읽는 데 78분 · A3

A-3: 미디어와 테이블

전체 23강 중 3강 · HTML·CSS·JS
난이도 · 입문

안녕하세요, 홍순구 튜터입니다. 세 번째 시간이에요!

지난 시간에 우리는 인스타그램에 텍스트링크를 잔뜩 추가해봤어요. 제목, 문단, 강조, 목록, 그리고 페이지를 이어주는 메뉴까지 — 페이지가 제법 인스타그램다워졌죠. 그런데 한 가지가 빠진 느낌이 들지 않나요?

인스타그램이 인스타그램인 이유는 결국 사진이잖아요. 텍스트만 가득한 인스타그램은 인스타그램이 아니에요.

오늘은 페이지에 이미지, 동영상, 소리를 넣는 법을 배웁니다. 지난 시간 빈 골격으로 두고 떠난 feed.html에 진짜 게시물 사진이 들어가기 시작하고요, profile.html에는 프로필 아바타가 추가됩니다.

마지막에는 인스타그램 비즈니스 계정에서 자주 보는 표(table) 도 한 번 만들어 봐요.

💡 오늘 수업의 핵심 — "이미지·비디오·오디오로 페이지에 시각·청각 자료를 채우고, 표로 데이터를 보기 좋게 정렬한다" 🎯

🎯 학습 목표

  • <img> 태그로 페이지에 이미지를 넣고, src와 경로를 자유롭게 다룹니다.
  • alt 속성으로 이미지에 의미를 담고, 보이지 않는 사용자에게도 정보를 전달합니다.
  • loading="lazy" 속성으로 첫 화면 로딩 속도를 빠르게 만드는 트릭을 익힙니다.
  • <figure> + <figcaption>으로 이미지와 캡션을 한 덩어리로 묶습니다.
  • <video><audio> 태그로 동영상과 소리를 페이지에 임베드합니다.
  • <table> + <thead> + <tbody> + <th> + <caption>으로 시멘틱한 표를 만듭니다.

Step 1: "첫 이미지 넣기 — <img> 태그와 src 속성"

자, 이제 사진을 넣어볼 시간이에요. HTML에서 이미지를 보여주는 태그는 단 하나, <img>입니다.

<img>도 빈 요소예요

지난 시간 우리는 빈 요소(Empty Element) 라는 개념을 만났어요. 기억나시죠? 닫는 태그 없이 한 줄로 끝나는 태그들이었어요.

<br>  <!-- 줄바꿈 -->
<hr>  <!-- 수평선 -->

<img>도 이 빈 요소 가족입니다.

<img src="강아지.jpg">

src 안에는 "이미지가 어디 있는지" 그 위치를 적어요. Source(원본) 의 약자예요. 이 정보가 없으면 브라우저는 어떤 이미지를 보여줘야 할지 모릅니다.

경로 표현이 다시 등장

여기서 지난 시간에 배운 경로 표현이 다시 나옵니다. <a href="...">에서 썼던 그 경로 그대로예요.

경로 의미 예시
절대 경로 https://로 시작하는 전체 주소 https://picsum.photos/600/600
상대 경로 현재 파일 기준 위치 ./assets/images/post1.jpg
상위 폴더 한 단계 위 폴더 ../images/avatar.png

지난 시간 메뉴를 만들 때 썼던 경로 규칙이 <img>에서도 100% 같습니다. 한 번 익힌 규칙을 다른 자리에서 그대로 써먹는 게 HTML이 익숙해지는 첫 신호예요.

그런데 더미 이미지를 어디서 구하지?

실습하는 동안 직접 사진을 찍거나 다운받기는 번거롭잖아요. 다행히 랜덤 이미지를 무료로 제공하는 사이트가 있어요.

이번 강의에서는 picsum.photos를 씁니다. URL에 가로/세로 크기만 넣으면 매번 랜덤한 사진을 돌려주는 서비스예요.

https://picsum.photos/600/600                 — 600×600 정사각형 랜덤 이미지
https://picsum.photos/seed/insta1/600/600     — seed 값이 같으면 같은 이미지 반환

seed를 넣으면 새로고침해도 같은 이미지가 나옵니다. 실습이 끝날 때까지 같은 사진을 안정적으로 쓸 수 있어요.

feed.html에 첫 게시물 넣기

이제 우리 feed.html을 열어 첫 게시물을 만들어 봅시다. 지난 시간엔 "피드 카드는 다음 모듈(B-3)에서 본격적으로 만들어요" 라는 메모만 남기고 비워뒀었죠.

<!-- instagram-clone-frontend/feed.html -->
<main>
  <h2>피드</h2>

  <article>
    <img src="https://picsum.photos/seed/insta1/600/600"
         width="600" height="600">
  </article>
</main>

저장하고 브라우저를 확인하세요. 정사각형 게시물 사진이 짠 하고 나타날 거예요.

<article> 태그로 감싼 이유가 궁금하시죠? 인스타그램의 게시물 한 개는 "독립적으로 떼어내도 의미가 통하는 콘텐츠 덩어리"잖아요. 이런 경우를 위해 만들어진 시멘틱 태그가 <article>입니다. "이건 하나의 독립된 게시물이에요" 라는 의미를 브라우저와 검색엔진에 전달해요.

width와 height를 왜 미리 적는가?

코드에서 width="600" height="600"을 적었어요. 사진 자체가 600×600이라서 굳이 안 적어도 같은 크기로 나오긴 합니다. 그런데 왜 적었을까요?

이유는 Layout Shift(레이아웃 출렁임)라는 현상 때문이에요. 한 번 비유로 풀어볼게요.

친구가 카페에서 자리를 잡아준다고 했어요. "여기 앉아!" 하고 의자를 잡아두는 거죠. 그런데 의자 크기를 모르면 "얼마나 비워둘지" 모르잖아요. 일단 작은 의자만큼 비워뒀다가, 진짜 의자가 도착하면 깜짝 놀라며 위치를 재조정하게 됩니다. 옆에 앉으려던 사람도 떠밀려요.

브라우저도 똑같아요. <img>의 크기를 미리 알면 그만큼의 공간을 예약해두고, 진짜 이미지가 로드되면 그 위치에 끼워 넣어요. 화면이 출렁이지 않습니다. 반대로 크기를 모르면 사진이 도착하는 순간 페이지가 갑자기 늘어나서, 그 아래 글을 읽던 사용자가 "어? 글이 어디 갔지?" 하게 돼요.

⚠️ 실무에서는 모든 <img>width/height를 명시하는 게 표준입니다. 작은 습관이지만 사용자 경험에 큰 영향을 줘요.

이미지가 안 보일 때 — 경로를 의심해요

코드를 정확히 따라 쳤는데 깨진 이미지 아이콘만 보인다면, 90%는 경로 문제예요.

🟢 https://picsum.photos/seed/insta1/600/600    — OK
🔴 https//picsum.photos/seed/insta1/600/600     — 콜론(:)이 빠짐
🔴 ./assets/images/post1.jpg                    — 파일이 실제로 없는 경우

브라우저에서 F12 → Console 탭을 열어보세요. 경로가 잘못됐다면 빨간 에러가 떠 있을 거예요. "Failed to load resource: the server responded with a status of 404" 같은 메시지가 보이면 그게 우리가 찾던 단서입니다. 404는 "그런 파일이 거기에 없어요" 라는 서버의 대답이에요.

🙋 "그럼 절대 경로 https://...만 쓰면 안전한가요? 왜 굳이 상대 경로를 쓰는 거예요?" — 좋은 질문이에요. 우리 프로젝트 안에 직접 둔 이미지(예: assets/images/avatar.png)는 상대 경로가 정답입니다. 절대 경로로 적으면 도메인이 바뀔 때마다 (예: 개발 서버 → 운영 서버) 일일이 고쳐야 해요. 반면 외부 서비스(picsum 같은)는 절대 경로가 자연스럽고요. "내 프로젝트 안 → 상대 / 외부 사이트 → 절대" 가 일반적인 기준이에요.

🙋 학생 질문 — "PNG, JPG, GIF, WebP... 이미지 포맷이 너무 많아요. 뭘 써야 하나요?"

지금 단계에서 외워야 할 큰 차이만 짚어드릴게요.

포맷 어울리는 곳 한 마디
JPG 사진 (자연 풍경, 인물) 용량이 작지만 약간의 화질 손실
PNG 로고, 아이콘, 투명 배경 필요 깨끗하지만 사진엔 용량이 큼
GIF 짧은 애니메이션 화질이 낮음. 요즘은 동영상으로 대체
WebP / AVIF 거의 모든 곳에 더 좋은 대체재 더 작은 용량 + 더 좋은 화질. 옛 브라우저 호환만 주의

실무에서는 사진엔 WebP / AVIF, 로고엔 PNG 또는 SVG를 권장해요. 다만 이번 강의에선 picsum이 자동으로 JPG를 돌려주기 때문에, 학습 중에는 JPG로 충분합니다. 자세한 최적화는 F-2 모듈(이미지 & 리소스 최적화)에서 본격적으로 다뤄요.

첫 이미지가 화면에 들어왔어요

feed.html을 다시 열어보면, 빈 골격이었던 페이지에 진짜 사진이 들어와 있어요. 인스타그램다워지는 첫 순간이에요.

그런데 한 가지 신경 쓰이는 점이 있어요. 만약 인터넷이 느려서 이미지가 안 뜨거나, 시각 장애가 있는 사용자가 스크린 리더로 페이지를 듣고 있다면 — 이 사진이 무슨 사진인지 어떻게 알릴까요? 우리 코드 어디에도 "이건 해변 사진이에요" 라는 정보가 없잖아요.

이걸 해결하는 게 다음 Step에서 만날 alt 속성입니다.


Step 2: "alt 속성 — 이미지가 안 보일 때 말해주는 텍스트"

직전 Step 끝에서 던진 질문 — "이 사진이 무슨 사진인지 어떻게 알릴까요?" — 의 답을 만나봅시다. HTML은 이 자리를 위해 alt 속성을 준비해뒀어요.

alt = Alternative Text (대체 텍스트)

altAlternative(대안)의 약자예요. "이미지를 못 볼 때 대신 보여줄 텍스트" 라는 뜻입니다.

<!-- instagram-clone-frontend/feed.html -->
<img src="https://picsum.photos/seed/insta1/600/600"
     alt="석양이 지는 제주도 협재 해변 — 하늘이 주황빛으로 물들었다">

alt 안에 적은 텍스트는 평소엔 화면에 보이지 않아요. 하지만 다음 세 가지 상황에서 큰 역할을 합니다.

alt 가 활약하는 세 가지 상황

상황 alt 가 하는 일
1. 스크린 리더 사용자 시각 장애 사용자가 음성으로 페이지를 들을 때 — "석양이 지는 제주도 협재 해변 사진" 이라고 읽어줘요
2. 검색엔진 구글 이미지 검색에서 우리 사진이 "해변 사진" 으로 분류되려면 alt 가 필요해요
3. 이미지 로드 실패 인터넷이 끊겨서 이미지를 못 받아오면, 깨진 아이콘 옆에 alt 텍스트가 보입니다

세 가지 모두 "보이지 않는 사용자에게 정보를 전달" 한다는 공통점이 있어요. 디자인 위에 한 단계 더 있는 의미의 영역이에요.

DevTools로 alt 가 동작하는 모습 확인하기

이론은 충분히 들었죠. 직접 확인해봅시다.

  1. feed.html을 Live Server로 열고 F12 → Elements 탭에서 <img> 태그를 찾아요.
  2. src 값을 일부러 깨뜨려보세요. 예: https://picsum.photos/seed/insta1/600/600https://picsum.photos/INVALID/600/600
  3. 사진이 깨진 아이콘으로 바뀌고, 그 옆에 우리가 적은 alt 텍스트가 보일 거예요.

이 작은 실험이 "alt 가 있어서 다행이다" 라는 감각을 만들어줘요. 사용자 입장에서는 "무슨 사진인지" 짐작이라도 할 수 있잖아요.

🙋 "근데 alt 가 없어도 사진은 잘 보이잖아요. 굳이 매번 적어야 하나요?" — 네, 매번 적습니다. alt가 빠진 <img>는 사실상 "의미가 없는 이미지" 로 처리돼요. HTML 표준은 "alt 는 모든 <img> 의 필수 속성" 이라고 명시할 정도입니다.

feed.html 전체에 alt 채우기

이제 우리 feed.html의 모든 사진에 의미 있는 alt 를 채워봅시다.

<!-- instagram-clone-frontend/feed.html -->
<main>
  <h2>피드</h2>

  <article>
    <img src="https://picsum.photos/seed/insta1/600/600"
         alt="석양이 지는 제주도 협재 해변 — 하늘이 주황빛으로 물들었다"
         width="600" height="600">
  </article>

  <article>
    <img src="https://picsum.photos/seed/insta2/600/600"
         alt="동네 카페 테이블 위에 놓인 라떼아트가 그려진 커피잔"
         width="600" height="600">
  </article>

  <article>
    <img src="https://picsum.photos/seed/insta3/600/600"
         alt="공원 잔디밭에서 공을 물고 뛰어오는 골든 리트리버"
         width="600" height="600">
  </article>
</main>

사진은 picsum 의 랜덤 이미지라 실제로는 alt 와 다를 수 있어요. 하지만 핵심은 "이미지의 의미를 텍스트로 한 줄 묘사하는 습관" 입니다.

의미 있는 이미지 vs 장식 이미지 — alt="" 의 비밀

모든 이미지가 의미를 갖는 건 아니에요. 디자인용 배경 이미지, 공간을 채우는 장식 아이콘은 "읽어줄 필요가 없는 이미지" 거든요.

이때는 alt="" 빈 alt를 적습니다.

<!-- 장식용 — 스크린 리더가 건너뛰어야 함 -->
<img src="decorative-line.png" alt="">

<!-- 정보 전달용 — 스크린 리더가 읽어야 함 -->
<img src="user-avatar.png" alt="홍순구님의 프로필 사진">

⚠️ alt="" (빈 따옴표)와 alt가 아예 없는 것은 다릅니다.

  • alt="" — "이건 의도적으로 장식 이미지예요" (스크린 리더가 건너뜀)
  • alt 누락 — "alt 를 빼먹은 실수예요" (스크린 리더가 파일명을 그대로 읽어서 "u-s-e-r-dot-a-v-a-t-a-r-dot-p-n-g" 같은 끔찍한 소리가 남)

장식 이미지에 일부러 비울 때만 alt=""를 씁니다. 나머지 모두는 의미 있는 텍스트를 채워야 해요.

좋은 alt 와 나쁜 alt

같은 사진을 두고도 alt 의 품질은 천차만별이에요.

사진 🔴 나쁜 alt 🟢 좋은 alt
강아지 사진 alt="강아지" alt="공원 잔디밭에서 공을 물고 뛰어오는 골든 리트리버"
카페 사진 alt="image" alt="동네 카페 테이블 위에 놓인 라떼아트가 그려진 커피잔"
프로필 사진 alt="홍순구_profile_v2.jpg" alt="홍순구님의 프로필 사진"

"강아지" 만 적으면 사실상 정보가 없어요. "어디서, 어떤 강아지가, 뭘 하고 있는지" 한 줄로 묘사하는 게 좋은 alt 의 기본입니다.

🙋 "그럼 alt 가 너무 길면 어떡해요? 100 단어쯤 적어도 되나요?" — 너무 길어도 곤란해요. 스크린 리더 사용자가 듣기에 "적당히 한 문장" 정도가 표준입니다. 대략 100~150 자 이내로 끊는 걸 권장해요. 그보다 더 자세한 설명이 필요한 사진이면, 본문 텍스트에서 다시 다루는 게 자연스럽고요.

profile.html 의 아바타에도 alt 채우기

지난 시간 빈 골격으로 둔 profile.html도 열어볼게요. 오늘 아바타 이미지 한 장을 추가하면서 alt 를 미리 채워둡시다.

<!-- instagram-clone-frontend/profile.html -->
<main>
  <section>
    <img src="https://picsum.photos/seed/avatar1/150/150"
         alt="홍순구님의 프로필 사진"
         width="150" height="150">
    <h2>홍순구</h2>
    <p>@hong_tutor</p>
    <p>풀스택 개발자 / 강의 진행 중</p>
  </section>
</main>

저장 후 브라우저로 확인하면 아바타가 동그랗게... 아직은 아니에요. 지금은 정사각형으로만 보일 거예요. "동그란 아바타" 는 다음 모듈(B-1)에서 CSS border-radius로 만들어요.

첫 번째 게시물은 화면에 보이고, 나머지는?

feed.html을 다시 열고 마우스로 스크롤을 내려보세요. 두 번째, 세 번째 게시물이 차례로 나타나죠.

그런데 잠깐 — 만약 게시물이 100 장, 1000 장이라면 어떨까요? 페이지를 열자마자 모든 사진이 한꺼번에 다운로드되면 인터넷이 얼마나 느려질까요?

이 문제를 해결하는 게 다음 Step의 loading="lazy" 속성입니다.


Step 3: "보일 때만 로드 — loading="lazy" 속성"

페이지에 사진이 한두 장이면 큰 문제가 없어요. 하지만 인스타그램 피드처럼 "스크롤을 한참 내려야 하는 페이지" 라면 얘기가 달라집니다.

즉시 로드 vs 지연 로드

기본 동작은 "즉시 로드" 예요. 브라우저는 페이지를 열자마자 모든 <img>를 한꺼번에 다운로드합니다. 화면에 보이는지 안 보이는지는 따지지 않아요. 스크롤 100 번 내려야 나오는 사진까지 미리 받아둡니다.

문제는 모바일이에요. 사용자의 데이터가 줄줄 새고, 첫 화면이 뜨는 속도도 느려집니다.

loading="lazy" — 보일 때 받기

이 문제를 해결하는 속성이 loading="lazy"입니다. Lazy(게으른)의 약자 그대로예요. 브라우저에게 "게으르게 굴어서, 화면에 보일 때까지 기다렸다가 받아라" 라는 지시입니다.

<img src="https://picsum.photos/seed/insta2/600/600"
     alt="동네 카페 테이블 위에 놓인 라떼아트가 그려진 커피잔"
     width="600" height="600"
     loading="lazy">

이 한 줄을 추가하면 "브라우저 창에 보이려 할 때" 비로소 다운로드가 시작돼요. 스크롤 한참 아래 있는 사진은 아예 받아오지도 않습니다.

첫 화면 vs 그 아래 — 어떻게 가르나?

모든 <img>loading="lazy"를 박는 게 정답은 아니에요. 첫 화면에 보이는 사진까지 lazy 로 두면, 오히려 첫 사진이 "한 박자 늦게" 뜨거든요.

위치 권장
첫 화면에 보이는 이미지 (스크롤 없이 보이는 곳) lazy 안 적용 — 즉시 로드
첫 화면 아래 — 스크롤해야 보이는 이미지 loading="lazy" 적용

인스타그램 피드라면 "첫 게시물 1장은 즉시 로드, 두 번째부터 lazy" 정도가 자연스러워요.

feed.html 의 두 번째 게시물부터 적용

우리 feed.html의 두 번째, 세 번째 게시물에 loading="lazy"를 추가해봅시다.

<!-- instagram-clone-frontend/feed.html -->

<!-- 첫 번째 — 첫 화면 보이는 위치, lazy 없이 -->
<article>
  <img src="https://picsum.photos/seed/insta1/600/600"
       alt="석양이 지는 제주도 협재 해변 — 하늘이 주황빛으로 물들었다"
       width="600" height="600">
</article>

<!-- 두 번째부터 — 스크롤 내려야 보임, lazy 적용 -->
<article>
  <img src="https://picsum.photos/seed/insta2/600/600"
       alt="동네 카페 테이블 위에 놓인 라떼아트가 그려진 커피잔"
       width="600" height="600"
       loading="lazy">
</article>

<article>
  <img src="https://picsum.photos/seed/insta3/600/600"
       alt="공원 잔디밭에서 공을 물고 뛰어오는 골든 리트리버"
       width="600" height="600"
       loading="lazy">
</article>

DevTools Network 탭으로 동작 확인 — 단, 세 가지 함정 먼저

loading="lazy"가 진짜로 동작하는지 눈으로 확인하기 전에, 학생들이 가장 많이 부딪히는 세 가지 함정을 먼저 짚을게요. 이걸 모르면 "코드는 박았는데 안 박은 것처럼 보이는데?" 라는 결론이 나옵니다.

⚠️ 함정 1: 강제새로고침은 lazy 를 우회해요

같은 새로고침이라도 두 종류가 있어요.

새로고침 단축키 lazy 동작
일반 새로고침 F5 / Cmd + R ✅ lazy 정상 동작
강제새로고침 (하드 리로드) Ctrl+Shift+R / Cmd+Shift+R ❌ 모든 리소스 강제 받기 — lazy 우회
DevTools "Disable cache" 켜짐 (체크박스) ❌ 강제새로고침과 같은 효과

DevTools 의 "Disable cache" 체크박스가 켜져 있으면 일반 새로고침을 해도 강제새로고침과 같은 동작이에요. 시연 전에 끄고 시작하세요.

⚠️ 함정 2: picsum.photos 는 302 응답이에요

Network 탭에 "302" 가 보일 수 있어요. 놀라지 마세요. picsum.photos 의 정상 동작입니다.

요청: GET https://picsum.photos/seed/insta1/600/600
응답: HTTP 302 Found
      Location: https://fastly.picsum.photos/id/237/600/600.jpg
요청 (자동): GET https://fastly.picsum.photos/id/237/600/600.jpg
응답: HTTP 200 OK (진짜 이미지)

picsum 은 "실제 이미지가 들어있는 서버" 가 아니라 "랜덤 ID 를 정해서 CDN 으로 안내해주는 라우터" 예요. 첫 응답이 302 이고, 브라우저가 자동으로 "안내받은 CDN 주소" 로 다시 요청해서 실제 이미지를 받습니다. 정상 동작이에요.

⚠️ 함정 3: 짧은 페이지에선 lazy 가 한꺼번에 받을 수 있어요

Chrome 은 lazy 이미지를 "화면에 보일 때" 가 아니라 "viewport 외 1000~3000px" 까지 미리 받아둬요. 사용자가 스크롤할 때 "한 박자 미리" 준비돼 있게 하려는 사용자 경험 정책이에요.

문제는 우리 페이지가 짧다는 거예요. 게시물 3장만 있으면 두 번째·세 번째 게시물도 viewport "근처" 라 모두 한꺼번에 받아질 수 있어요.

확실하게 lazy 동작을 보려면 — 세 가지 조건

위 세 함정을 피하고 "진짜 lazy" 를 보려면 다음 조건들을 갖춰주세요.

  1. 게시물을 충분히 많이 추가 — 마지막 게시물이 viewport 5,000px+ 밖에 자리잡아야 확실히 lazy 가 발동돼요. 본 강의에서는 feed.html 의 게시물을 8장으로 확장해뒀어요 (insta1 ~ insta8).
  2. Network throttling 으로 느린 환경 시뮬레이션 — "Fast 3G" 또는 "Slow 3G" 로 받기 속도를 늦추면 받는 시점 차이가 두드러지게 보여요.
  3. 일반 새로고침 (F5 / Cmd+R) + Disable cache 꺼짐

feed.html 게시물 8장으로 확장된 코드

확실한 시연을 위해 게시물을 5장 더 추가해서 총 8장으로 만들었어요. 세 번째 게시물(insta3) 아래에 다음을 이어 붙입니다.

<!-- instagram-clone-frontend/feed.html (게시물 4~8 추가) -->
<article>
  <figure>
    <img src="https://picsum.photos/seed/insta4/600/600"
         alt="해질녘 강가에서 노을을 바라보는 사람의 실루엣"
         width="600" height="600"
         loading="lazy">
    <figcaption>강가의 노을 #riverside #sunset</figcaption>
  </figure>
</article>

<article>
  <figure>
    <img src="https://picsum.photos/seed/insta5/600/600"
         alt="눈 쌓인 산 정상에서 본 일출 풍경"
         width="600" height="600"
         loading="lazy">
    <figcaption>새해 첫 일출 #mountain #sunrise</figcaption>
  </figure>
</article>

<article>
  <figure>
    <img src="https://picsum.photos/seed/insta6/600/600"
         alt="작은 골목길 카페 앞 자전거 한 대"
         width="600" height="600"
         loading="lazy">
    <figcaption>골목 산책 #alleyway #bike</figcaption>
  </figure>
</article>

<article>
  <figure>
    <img src="https://picsum.photos/seed/insta7/600/600"
         alt="도서관 책장 사이로 비치는 따스한 햇살"
         width="600" height="600"
         loading="lazy">
    <figcaption>오후의 도서관 #library #reading</figcaption>
  </figure>
</article>

<article>
  <figure>
    <img src="https://picsum.photos/seed/insta8/600/600"
         alt="비행기 창가에서 내려다본 구름 위 풍경"
         width="600" height="600"
         loading="lazy">
    <figcaption>구름 위에서 #airplane #travel</figcaption>
  </figure>
</article>

저장하고 위 시연 절차대로 시도하면 "viewport 와 멀리 떨어진 게시물 7·8번" 은 확실히 lazy 가 발동돼서 "스크롤 다가갈 때 그제야 받기" 가 보입니다.

🙋 "브라우저가 알아서 lazy 처리해주면 좋을 텐데, 왜 우리가 일일이 적어야 해요?" — 좋은 질문이에요. 브라우저는 "어떤 이미지가 진짜로 lazy 해도 되는지" 자동으로 판단하기 어렵거든요. 첫 화면에 보일 사진을 lazy 처리하면 오히려 사용자 경험이 나빠지잖아요. "어떤 이미지를 lazy 할지" 는 개발자가 의도적으로 결정해야 하는 부분이에요.

모든 브라우저에서 지원해요

loading="lazy"는 2026년 현재 Chrome, Firefox, Safari, Edge 모두 안정 지원입니다. 호환성 걱정 없이 마음껏 쓰셔도 좋아요.

⚠️ 단 한 가지 — loading="lazy"가 잘 동작하려면 widthheight가 함께 있어야 합니다. 크기를 모르면 브라우저가 "이 이미지가 화면 안에 들어와있는지" 판단할 수 없거든요. 직전 Step에서 배운 width/height가 또 한 번 진가를 발휘하는 부분이에요.

우리는 이제 이미지를 다룰 줄 알아요

여기까지 Step 1, 2, 3 을 모으면 "제대로 된 이미지 태그 한 줄" 의 모습이 보입니다.

<img src="https://picsum.photos/seed/insta2/600/600"
     alt="동네 카페 테이블 위에 놓인 라떼아트가 그려진 커피잔"
     width="600" height="600"
     loading="lazy">

이 한 줄에 "어디서, 무엇을, 어떤 크기로, 언제 받을지" 가 모두 담겨있어요. SRC 로 어디서, ALT 로 무엇을, WIDTH/HEIGHT 로 어떤 크기로, LOADING 으로 언제.

이제 한 단계 더 나아가봅시다. 인스타그램 게시물은 사진 한 장만 있는 게 아니죠 — "사진 아래에 캡션" 이 있잖아요. 이미지와 캡션을 "한 덩어리" 로 묶는 시멘틱 태그가 다음 Step에 등장합니다.


Step 4: "이미지에 의미를 붙이기 — <figure> + <figcaption>"

지금까지 우리 feed.html의 게시물은 사진 한 장씩이었어요. 하지만 인스타그램 게시물은 "사진 + 캡션" 한 덩어리잖아요. "제주도 협재 해변 — 오늘의 일상 #sunset" 같은 글이 사진 아래에 붙죠.

이걸 시멘틱하게 표현하는 두 태그가 <figure><figcaption>입니다.

<figure> — 그림 한 덩어리

<figure>는 "그림(figure) 한 덩어리" 를 묶는 태그예요. 사진, 비디오, 도표, 코드 블록 — "본문의 일부지만 떼어내도 의미가 통하는 시각 자료" 를 감쌉니다.

<figcaption> — 그림 설명

<figcaption>figure + caption(설명) 의 합성어예요. "이 그림에 붙는 짧은 설명" 부분입니다.

둘을 같이 쓰면 이렇게 돼요.

<!-- instagram-clone-frontend/feed.html -->
<figure>
  <img src="https://picsum.photos/seed/insta1/600/600"
       alt="석양이 지는 제주도 협재 해변 — 하늘이 주황빛으로 물들었다"
       width="600" height="600">
  <figcaption>오늘의 일상 — 제주도 협재 해변 #sunset #travel</figcaption>
</figure>

figure vs div 의 차이

"<div>로 묶어도 되지 않나요?" 라고 묻고 싶으실 거예요. 겉모습은 거의 같아요. 하지만 의미가 달라요.

태그 의미 검색엔진/스크린 리더 해석
<div> "여기 뭔가 묶인 게 있어요" (의미 없음) "그냥 박스 하나" 로 인식
<figure> "이건 그림 한 덩어리예요" (시각 자료) "본문의 시각 자료" 로 인식하고 별도 처리

<figure>는 "본문에서 떼어내도 의미가 통하는 시각 자료" 라는 의미를 갖습니다. 독자는 본문을 읽다가 "이 figure 를 한 번 보고 다시 본문으로 돌아온다" 는 흐름이에요.

alt 와 figcaption 의 역할 분담

여기서 자주 헷갈리는 부분 하나 — "alt 가 있는데 figcaption 까지 필요해요?"

속성 누구를 위한 텍스트인가?
alt "이미지를 못 보는 사람" 을 위한 설명 (스크린 리더, 로드 실패)
<figcaption> "이미지를 보는 사람도 포함, 모두를 위한 캡션" (사진작가, 출처, 본문 일부)

alt는 "이미지의 시각적 내용을 텍스트로 풀어낸 것" 이에요. figcaption은 "이미지에 더해진 본문 글" 이에요. 둘은 보완 관계지, 중복이 아닙니다.

🙋 "그럼 alt 와 figcaption 에 같은 내용을 적어도 되나요?" — 권장하지 않아요. 스크린 리더는 alt 와 figcaption 을 둘 다 읽어줘요. 같은 내용이면 "석양이 지는 제주도 협재 해변... 석양이 지는 제주도 협재 해변..." 처럼 두 번 듣게 됩니다. "alt 는 사진 그 자체 묘사 / figcaption 은 캡션 글" 로 역할을 나누는 게 자연스러워요.

feed.html 게시물에 figure 적용

이제 우리 feed.html의 세 게시물을 <figure> + <figcaption>으로 감싸봅시다.

<!-- instagram-clone-frontend/feed.html -->
<article>
  <figure>
    <img src="https://picsum.photos/seed/insta1/600/600"
         alt="석양이 지는 제주도 협재 해변 — 하늘이 주황빛으로 물들었다"
         width="600" height="600">
    <figcaption>오늘의 일상 — 제주도 협재 해변 #sunset #travel</figcaption>
  </figure>
</article>

<article>
  <figure>
    <img src="https://picsum.photos/seed/insta2/600/600"
         alt="동네 카페 테이블 위에 놓인 라떼아트가 그려진 커피잔"
         width="600" height="600"
         loading="lazy">
    <figcaption>오늘 발견한 동네 카페 #coffee #latte</figcaption>
  </figure>
</article>

<article>
  <figure>
    <img src="https://picsum.photos/seed/insta3/600/600"
         alt="공원 잔디밭에서 공을 물고 뛰어오는 골든 리트리버"
         width="600" height="600"
         loading="lazy">
    <figcaption>주말 산책 친구 #goldenretriever #weekend</figcaption>
  </figure>
</article>

저장 후 브라우저를 확인하면 사진 아래에 캡션 텍스트가 보일 거예요. "그냥 사진 한 장" 이었던 게 "의미가 담긴 시각 자료" 로 한 단계 격상됐어요.

profile.html 의 갤러리에도 figure 적용

profile.html에는 게시물 갤러리 3장을 추가하면서, 모두 <figure> + <figcaption>으로 감싸봅시다.

<!-- instagram-clone-frontend/profile.html -->
<section>
  <h3>내 게시물</h3>

  <figure>
    <img src="https://picsum.photos/seed/myinsta1/300/300"
         alt="새벽 작업실 책상 위 키보드와 듀얼 모니터"
         width="300" height="300"
         loading="lazy">
    <figcaption>오늘의 작업실 #coding #devlife</figcaption>
  </figure>

  <figure>
    <img src="https://picsum.photos/seed/myinsta2/300/300"
         alt="강의장 화이트보드에 그려진 시스템 아키텍처 도식"
         width="300" height="300"
         loading="lazy">
    <figcaption>오늘의 강의 화이트보드 #teaching</figcaption>
  </figure>

  <figure>
    <img src="https://picsum.photos/seed/myinsta3/300/300"
         alt="저녁 노을이 물든 서울 한강 풍경"
         width="300" height="300"
         loading="lazy">
    <figcaption>퇴근길 한강 #seoul #sunset</figcaption>
  </figure>
</section>

세 갤러리 사진이 각각 "사진 + 캡션" 의 시멘틱 덩어리로 들어가 있어요. 지금은 위에서 아래로 한 줄씩 나열되지만, B-4 모듈의 CSS Grid 가 들어오면 "3열 갤러리" 로 정렬됩니다. 오늘은 의미만 먼저 박아두는 단계예요.

동영상에도 figure 가 쓰여요

<figure>가 이미지 전용이 아니라는 점도 짚고 갈게요. 비디오, 도표, 코드 블록 — "본문에서 떼어낼 수 있는 시각 자료" 라면 모두 figure 로 묶을 수 있어요.

<figure>
  <video src="reels.mp4" controls></video>
  <figcaption>오늘의 릴스 — 즐거운 한 컷</figcaption>
</figure>

이 패턴이 다음 Step의 비디오/오디오와 자연스럽게 연결됩니다.


Step 5: "동영상과 소리 — <video><audio> 태그"

인스타그램에는 사진만 있는 게 아니죠. 짧은 동영상(릴스), 라이브 방송, 오디오까지 — 멀티미디어가 핵심이에요. HTML 은 이 모두를 위한 태그를 준비해뒀어요.

<video> 태그 — 동영상 임베드

<video>는 페이지에 동영상을 임베드하는 태그예요.

<video src="reels.mp4" controls></video>

src에 동영상 파일 주소를 적고, controls 속성을 추가하면 "재생 / 일시정지 / 볼륨" 버튼이 자동으로 보여요. 브라우저가 동영상 플레이어를 통째로 그려주는 거예요. 우리가 만들 필요가 없어요.

<video>의 주요 속성

속성 의미
controls 재생/일시정지/볼륨 버튼 표시
autoplay 페이지 열자마자 자동 재생
muted 음소거
loop 끝나면 처음부터 다시
poster 재생 전에 보일 썸네일 이미지
width / height 화면 크기 (<img>처럼 미리 예약)

controls가 없으면 "재생 버튼이 안 보이는 동영상" 이 돼요. 보통은 controls를 명시하는 게 기본입니다.

autoplay 와 muted 의 짝꿍

"페이지 열자마자 자동으로 비디오가 재생됐으면 좋겠어요" 라는 경우가 많아요. autoplay를 추가하면 됩니다. 하지만 한 가지 함정이 있어요.

2026년 현재, Chrome / Safari / Firefox 모두 "소리가 나는 동영상의 자동 재생을 차단" 합니다.

이유는 사용자 경험이에요. "페이지를 열자마자 큰 소리가 나오는 광고" 는 모두가 싫어하잖아요. 브라우저가 "사용자가 클릭하기 전까지는 소리 나는 자동 재생을 막는다" 로 정책을 정해두었어요.

그래서 autoplay는 거의 항상 muted와 짝꿍입니다.

<video src="reels.mp4" autoplay muted loop></video>

"자동 재생 + 음소거 + 무한 반복" — 인스타그램 릴스의 기본 패턴이에요. 소리가 듣고 싶으면 사용자가 직접 음소거를 풀어요.

⚠️ 이 정책은 "사용자 친화" 와 "광고/UX 강제" 사이의 균형이에요. "내가 만든 동영상이 자동 재생 안 돼요!" 라고 당황하지 마세요. 브라우저가 일부러 차단한 거니까, muted를 명시하면 풀려요.

<source> 로 다중 포맷 지원

동영상은 포맷이 여럿이에요 — MP4, WebM, OGG. 모든 브라우저가 모든 포맷을 지원하는 건 아니라서, "여러 포맷을 같이 제공" 하는 패턴이 있어요.

<video controls>
  <source src="reels.mp4" type="video/mp4">
  <source src="reels.webm" type="video/webm">
  <p>이 브라우저는 video 태그를 지원하지 않아요.</p>
</video>

브라우저는 위에서 아래로 <source>를 훑으면서 "지원하는 포맷" 을 만나면 그걸 씁니다. 모두 지원 안 하면 마지막 <p>의 텍스트가 대신 보여요. "플랜 A → 플랜 B → 마지막 안전망" 의 흐름이에요.

feed.html 에 릴스 한 편 추가

이제 feed.html 끝에 릴스 한 편을 추가해봅시다. 릴스의 핵심 패턴은 controls + muted + loop + poster.

<!-- instagram-clone-frontend/feed.html -->
<article>
  <h3>오늘의 릴스</h3>
  <figure>
    <video controls
           muted
           loop
           poster="https://picsum.photos/seed/reel1/600/800"
           width="600" height="800">
      <source src="https://www.w3schools.com/html/mov_bbb.mp4" type="video/mp4">
      <p>이 브라우저는 video 태그를 지원하지 않아요. 영상을 보시려면 최신 브라우저로 접속해주세요.</p>
    </video>
    <figcaption>오늘의 릴스 — 즐거운 한 컷 #reels</figcaption>
  </figure>
</article>

poster는 "재생 시작 전에 보이는 썸네일" 이에요. 사용자가 재생 버튼을 누르기 전까지는 정지된 이미지가 보여요. 없으면 첫 프레임이 보이는데, 어두운 첫 프레임이면 페이지가 칙칙해 보일 수 있어요.

<source> 도 명시했어요. 2026년 표준 패턴이라 mp4 한 줄만 있어도 대부분 동작하지만, "폴백을 의식하는 습관" 이 실무에서 빛납니다.

<audio> 태그 — 짧게 짚고 가기

<audio><video>와 거의 같은 방식이에요. 오디오만 다루므로 더 간단합니다.

<audio controls>
  <source src="podcast.mp3" type="audio/mpeg">
  <p>이 브라우저는 audio 태그를 지원하지 않아요.</p>
</audio>

인스타그램에서 오디오 자리가 흔하진 않아요. "오디오 일기" 나 "라이브 음원" 같은 가상의 위치에 들어가는 정도예요.

<!-- instagram-clone-frontend/feed.html (계속) -->
<article>
  <h3>오디오 일기</h3>
  <p>오늘 들었던 짧은 소리 한 토막. 재생 버튼을 눌러보세요.</p>
  <audio controls>
    <source src="https://www.w3schools.com/html/horse.mp3" type="audio/mpeg">
    <p>이 브라우저는 audio 태그를 지원하지 않아요.</p>
  </audio>
</article>

🙋 "왜 audio 는 video 만큼 자세히 안 다루나요?" — 솔직히 인스타그램 도메인에서 오디오만 단독으로 다루는 자리가 드물어요. "음악 / 팟캐스트 / 오디오 일기" 같은 위치에는 쓰이지만, 사진과 비디오가 인스타그램의 주연이거든요. <audio>도 "있다는 사실" 만 알아두고, 필요할 때 <video>와 같은 방식으로 쓰면 됩니다.

브라우저가 그려주는 UI 의 한계

<video controls>가 그려주는 기본 플레이어는 "브라우저마다 모양이 조금씩 달라요". Chrome 과 Safari 의 컨트롤 버튼 모양이 다릅니다.

"디자인을 통일하고 싶으면 어떻게 해요?" 라는 질문이 자연스럽게 나와요. 답은 "기본 controls 를 끄고, JS 와 CSS 로 직접 만든다" 예요. 이 부분은 C / D 모듈에 가서 본격적으로 다뤄요. 지금은 "브라우저가 그려준 기본 UI 로 충분히 동작한다" 는 감각만 잡으면 OK 예요.


Step 6: "표 한 장 만들기 — <table>과 그 가족들"

오늘의 마지막 Step 입니다. 인스타그램의 정체성은 사진이지만, "표 형태로 데이터를 보여줘야 하는 자리" 도 가끔 있어요. 대표적인 게 비즈니스 광고 가격표 예요.

표를 표답게 만드는 가족 태그들

표는 한 태그로 끝나지 않아요. "표 가족" 여럿이 같이 등장합니다.

태그 역할
<table> 표 전체를 감싸는 바깥
<thead> 표의 머리 (헤더 행)
<tbody> 표의 본문 (데이터 행들)
<tr> Table Row — 한 행
<th> Table Header — 머리 셀 (제목 칸)
<td> Table Data — 데이터 셀 (내용 칸)
<caption> 표 전체의 제목

이름 그대로 "표(Table) 의 행(Row) 머리(Header) 데이터(Data)" 예요.

첫 표 만들기

가장 단순한 표부터 그려봐요.

<table>
  <thead>
    <tr>
      <th>패키지</th>
      <th>월 비용</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>스타터</td>
      <td>₩300,000</td>
    </tr>
    <tr>
      <td>그로스</td>
      <td>₩1,200,000</td>
    </tr>
  </tbody>
</table>

브라우저에서 확인하면 "패키지 | 월 비용" 헤더 한 줄과 "스타터 ₩300,000", "그로스 ₩1,200,000" 두 데이터 행이 그려져요.

<caption> — 표의 제목

표 위에는 "이 표가 무엇에 관한 것인지" 알려주는 제목을 붙일 수 있어요. <caption> 태그입니다.

<table>
  <caption>월간 광고 패키지 (2026년 기준)</caption>
  <thead>
    ...
  </thead>
</table>

<caption><table>의 "첫 자식" 으로 적어야 해요. 순서가 중요합니다. 스크린 리더는 caption 을 "이 표는 ㅇㅇ에 관한 것" 으로 먼저 읽어준 뒤 본문을 안내해요.

scope — 접근성의 핵심

여기서 한 단계 더 갑니다. <th> 태그에 scope 속성을 명시하면 "이 헤더가 어디에 적용되는 헤더" 인지 알려줄 수 있어요.

scope 의미
scope="col" 이 헤더는 세로로 한 열의 제목
scope="row" 이 헤더는 가로로 한 행의 제목

예시:

<thead>
  <tr>
    <th scope="col">패키지</th>
    <th scope="col">월 비용</th>
  </tr>
</thead>
<tbody>
  <tr>
    <th scope="row">스타터</th>
    <td>₩300,000</td>
  </tr>
</tbody>

위 코드의 "패키지" 와 "월 비용" 은 "한 열의 제목" 이라 scope="col", "스타터" 는 "한 행의 첫 칸이자 그 행의 제목" 이라 scope="row" 예요.

스크린 리더는 "스타터 패키지의 월 비용은 ₩300,000" 처럼 헤더와 데이터를 연결해서 읽어줍니다. 시각으로 표를 못 보는 사용자가 "어떤 칸이 어떤 의미인지" 따라갈 수 있게 해주는 거예요.

완성된 가격표 — pricing.html

이제 전체 pricing.html을 한 번에 만들어봅시다.

<!-- instagram-clone-frontend/pricing.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Instagram - 비즈니스 광고 가격표</title>
</head>
<body>
  <header>
    <h1><a href="index.html">Instagram</a></h1>
    <nav>
      <ul>
        <li><a href="index.html">홈</a></li>
        <li><a href="explore.html">탐색</a></li>
        <li><a href="feed.html">피드</a></li>
        <li><a href="profile.html">프로필</a></li>
      </ul>
    </nav>
  </header>

  <main>
    <h2>비즈니스 광고 가격표</h2>
    <p>인스타그램 비즈니스 계정으로 광고를 집행할 때 참고할 수 있는 월간 패키지 가격표입니다.</p>

    <table>
      <caption>월간 광고 패키지 (2026년 기준 · 예시 단가)</caption>
      <thead>
        <tr>
          <th scope="col">패키지</th>
          <th scope="col">예상 노출 횟수</th>
          <th scope="col">월 비용</th>
          <th scope="col">추가 혜택</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <th scope="row">스타터</th>
          <td>10,000회</td>
          <td>₩300,000</td>
          <td>기본 분석 리포트</td>
        </tr>
        <tr>
          <th scope="row">그로스</th>
          <td>50,000회</td>
          <td>₩1,200,000</td>
          <td>+ 타겟 세분화 도구</td>
        </tr>
        <tr>
          <th scope="row">엔터프라이즈</th>
          <td>200,000회 이상</td>
          <td>별도 협의</td>
          <td>+ 전담 매니저 + 광고 컨설팅</td>
        </tr>
      </tbody>
    </table>

    <p><small>위 가격은 학습용 예시이며, 실제 광고 단가는 시장 상황에 따라 달라집니다.</small></p>
  </main>

  <footer>
    <p>&copy; 2026 Instagram Clone Project</p>
  </footer>
</body>
</html>

저장 후 브라우저로 확인하면 "3 패키지 × 3 정보" 의 가격표가 그려져요. 스타일은 거의 없지만 구조 자체로 "표" 라는 의미가 들어가 있어요.

표 vs 레이아웃 — 헷갈리지 마세요

여기서 자주 발생하는 오해 하나.

"화면을 가로 세 칸으로 나누고 싶은데 <table>로 만들면 안 되나요?"

옛날에는 <table>로 레이아웃을 잡는 일이 흔했어요. 2000년대 초반 웹사이트는 "표 안에 표 안에 표" 로 구조를 만들곤 했죠. 하지만 2026년 현재는 "표는 표 데이터에만, 레이아웃은 CSS로" 가 표준입니다.

용도 권장 도구
"표 형식의 데이터" (가격표, 성적표, 시간표) <table> + 친구들
"화면을 나누는 레이아웃" (3열 갤러리, 사이드바) CSS Flexbox / Grid (B-3, B-4 모듈)

기준은 의미예요. "이건 표 데이터인가, 아니면 그냥 칸 나누기인가" 를 스스로 물어서 결정합니다. 의미가 "표 데이터" 라면 <table>, 아니라면 CSS 로 가요.

🙋 "인스타그램 프로필의 '게시물 / 팔로워 / 팔로잉' 통계는 표일까요, 아닐까요?" — 좋은 질문이에요. 보기엔 "3 칸으로 나뉜 표" 같지만, "표 데이터" 라기보다는 "통계 박스 3개를 가로로 나열" 한 거예요. 실제 인스타그램은 <div> + CSS Flexbox 로 만들어요. "비교 가능한 단일 표" 인지 "독립된 박스들을 나열한 것" 인지를 보면 구분이 돼요.

<table>의 진가는 데이터일 때 빛나요

표가 본격적으로 빛나는 자리는 "한 번에 비교해야 하는 다차원 데이터" 예요. 가격표, 성적표, 일정표, 운영 시간표 — "한 줄로 죽 풀어내기엔 비효율적인 정보" 가 표의 자리예요.

오늘 만든 가격표가 딱 그 예시예요. 패키지마다 "노출 수 / 비용 / 혜택" 을 한눈에 비교할 수 있죠. 표가 아니라면 "스타터는 10,000회에 30만원이고 분석 리포트가..." 처럼 줄줄이 풀어 적어야 했을 거예요.

오늘 6 Step 의 여정이 여기서 끝납니다.


마무리

오늘 배운 핵심 여섯 가지

🎯 하나, <img> 는 닫는 태그 없는 빈 요소예요. src 에 이미지 주소, width/height 는 미리 명시해 Layout Shift 를 막아요.

🎯 , alt 는 "이미지를 못 보는 사용자" 를 위한 텍스트입니다. 스크린 리더 / 검색엔진 / 로드 실패 세 상황에서 빛나요. 장식 이미지엔 alt="", 정보 이미지엔 의미 있는 문장.

🎯 , loading="lazy" 한 줄로 "보일 때 받기" 가 켜져요. 첫 화면 이미지는 즉시 로드, 그 아래는 lazy 가 표준 패턴.

🎯 , <figure> + <figcaption> 은 "본문의 시각 자료 한 덩어리" 라는 의미를 담아요. <div> 와 모양은 같지만 시멘틱이 달라요.

🎯 다섯, <video>controls, autoplay, muted, loop, poster 다섯이 핵심. autoplay 는 거의 항상 muted 와 짝꿍 (브라우저 정책).

🎯 여섯, 표는 <table> + <thead> + <tbody> + <tr> + <th> + <td> + <caption> + scope 의 가족이에요. "표 데이터에만 쓰고, 레이아웃은 CSS 로" 가 2026년 표준.

다음 시간 예고

오늘까지는 "읽고 보는 페이지" 였어요. 사진을 띄우고, 영상을 재생하고, 표를 그렸죠.

다음 시간엔 "사용자가 직접 입력하는 페이지" 를 만들어요. 폼(Form) 의 등장입니다.

  • <form> + <input> — 로그인 ID / 비밀번호 입력
  • <label> — 입력칸에 의미 있는 라벨 붙이기
  • <input type="..."> — 16 가지 입력 타입 (email, password, date, color, ...)
  • <dialog> — 모달 다이얼로그 (인스타그램의 "공유하기" 팝업)

오늘 만든 index.html 의 "비밀번호를 잊으셨나요?" 링크 옆에 진짜 로그인 폼 이 들어와요. feed.html 의 게시물 아래에는 "댓글 입력칸" 이 추가되고요. 공유 버튼을 누르면 떠오르는 다이얼로그도 만나봅시다.


과제

[구현] 프로필 페이지 갤러리 확장하기

지금 profile.html의 갤러리에는 게시물 3장만 있어요. 오늘 배운 시멘틱 태그를 활용해서 갤러리에 게시물 3장을 더 추가해보세요. 총 6장의 figure 덩어리가 들어가야 합니다.

요구 사항:

  • 각 게시물은 <figure> 로 감쌀 것
  • 모든 <img> 에 의미 있는 alt 채울 것 (사진의 "무엇을, 어디서, 무엇을 하는지" 한 줄)
  • 첫 화면 아래 게시물(스크롤해야 보이는 곳)은 loading="lazy" 추가
  • width/height 명시 (300×300 권장)
  • <figure> 안에 <figcaption> 으로 캡션 한 줄 적을 것
  • picsum 의 seed 는 자유롭게 (mygallery4, mygallery5, mygallery6 등)

기본 게시물 3장(myinsta1~myinsta3)에 이어 새로운 게시물 3장을 더하면 됩니다.

DevTools Network 탭에서 "스크롤 내리면 추가 이미지가 그제야 로드되는 모습" 을 직접 확인해보세요.

[탐구] 좋아하는 웹사이트 3곳의 이미지 alt 분석하기

자주 가는 웹사이트 3곳을 열고 F12 → Elements 탭에서 <img> 태그 5개씩을 골라 alt 속성을 살펴봐주세요.

각 사이트마다 확인 항목:

  1. alt 속성이 아예 없는 이미지가 몇 개?
  2. alt="" (빈 alt) 가 의도적으로 들어간 위치는 어디?
  3. 의미 있는 한 문장 alt 가 잘 적힌 위치는 어디?
  4. "파일명 그대로" (예: img_3829.jpg) 같은 끔찍한 alt 가 있는가?

세 사이트의 결과를 간단한 표로 정리하고, "가장 alt 를 잘 챙긴 사이트는 어디였나" 자기 의견을 한 줄 덧붙여주세요.

스크린 리더 사용자의 입장이 되어 페이지를 "듣는 경험" 을 상상해보면, alt 의 중요성이 더 와닿아요.


생각해볼 주제

1. 좋은 alt 텍스트는 어떻게 써야 할까?

같은 강아지 사진을 두고 누군가는 alt="강아지", 누군가는 alt="공원 잔디밭에서 공을 물고 뛰어오는 골든 리트리버" 를 적어요. 둘 다 문법상 틀린 alt 가 아니에요. 하지만 "이미지를 못 보는 사용자" 에게 어느 쪽이 더 친절할까요?

반대로 "장식 이미지" 라면 왜 alt="" 로 비우는 게 정답일까요? "alt 가 비어있으니까 누락이랑 같지 않나?" 라고 오해할 수 있는데, 둘은 결정적으로 달라요. 스크린 리더 사용자의 입장에서 "파일명을 그대로 읽는 끔찍한 경험" 과 "건너뛰는 깔끔한 경험" 의 차이를 떠올려보세요.

좋은 alt 의 기준을 자기 언어로 한 문장으로 정의해보면 어떨까요?

2. 자동 재생 비디오는 사용자에게 친절한 기능일까?

<video autoplay muted> 패턴은 인스타그램 릴스의 기본 흐름이에요. 브라우저가 "소리 나는 자동 재생은 차단" 정책을 가진 이유도 잘 생각해볼 만해요.

하지만 "음소거 자동 재생" 도 모두에게 친절한 건 아니에요. 모바일에서 데이터 사용량이 폭증하고, 회의실이나 도서관에서 화면이 깜박이고, 시각적 자극에 민감한 사용자에게는 부담이 될 수 있어요.

어떤 페이지에 autoplay muted 가 어울리고, 어떤 페이지에서는 사용자가 직접 재생 버튼을 누르게 하는 게 자연스러울까요? "브라우저 정책 vs 사용자 선택권 vs 비즈니스 목적" 세 축의 균형을 생각해보세요.

3. <table> 은 어디까지가 표이고, 어디부터가 레이아웃인가?

오늘 우리는 가격표를 <table> 로 만들었어요. "한 번에 비교해야 하는 다차원 데이터" 였으니까요.

그런데 "인스타그램 프로필의 게시물 / 팔로워 / 팔로잉 통계" 는 어떨까요? 보기엔 "3 칸으로 나뉜 표" 같지만, 실제로는 <div> + CSS Flexbox 로 만들어요.

"표 데이터 vs 레이아웃" 의 경계를 어떻게 그을까요? "한 번에 비교 가능한 데이터인가", "각 칸이 독립된 의미를 갖는가" 같은 기준을 떠올려보세요.

다음 모듈(B-3 Flexbox) 부터 본격적으로 시작될 "표가 아닌 가로 정렬" 의 세계에서, 이 기준이 어떻게 적용될지 미리 생각해두면 좋아요.

✅ 예시 답안정답 보기

🎯 [과제 1 예시답안] 프로필 페이지 갤러리 확장하기

채점 포인트

항목 배점 기준
추가 게시물 3장 20% 기존 3장에 더해 새로운 3장이 들어갔는가
<figure> 시멘틱 사용 20% 각 게시물이 <figure> 로 감싸졌는가
모든 <img>에 의미 있는 alt 20% "사진의 무엇을, 어디서, 무엇을" 한 줄로 묘사됐는가
loading="lazy" 적용 15% 첫 화면 아래 게시물에 lazy 가 들어갔는가
width/height 명시 10% 300×300 으로 미리 크기가 예약됐는가
<figcaption> 캡션 15% 각 figure 안에 캡션 한 줄이 들어갔는가

완성 코드

<!-- instagram-clone-frontend/profile.html -->
<main>
  <section>
    <img src="https://picsum.photos/seed/avatar1/150/150"
         alt="홍순구님의 프로필 사진"
         width="150" height="150">
    <h2>홍순구</h2>
    <p>@hong_tutor</p>
    <p>풀스택 개발자 / 강의 진행 중</p>
  </section>

  <hr>

  <section>
    <h3>내 게시물</h3>

    <!-- 기존 게시물 3장 -->
    <figure>
      <img src="https://picsum.photos/seed/myinsta1/300/300"
           alt="새벽 작업실 책상 위 키보드와 듀얼 모니터"
           width="300" height="300"
           loading="lazy">
      <figcaption>오늘의 작업실 #coding #devlife</figcaption>
    </figure>

    <figure>
      <img src="https://picsum.photos/seed/myinsta2/300/300"
           alt="강의장 화이트보드에 그려진 시스템 아키텍처 도식"
           width="300" height="300"
           loading="lazy">
      <figcaption>오늘의 강의 화이트보드 #teaching</figcaption>
    </figure>

    <figure>
      <img src="https://picsum.photos/seed/myinsta3/300/300"
           alt="저녁 노을이 물든 서울 한강 풍경"
           width="300" height="300"
           loading="lazy">
      <figcaption>퇴근길 한강 #seoul #sunset</figcaption>
    </figure>

    <!-- 과제로 추가한 게시물 3장 -->
    <figure>
      <img src="https://picsum.photos/seed/mygallery4/300/300"
           alt="비 오는 날 창가에 김이 서린 따뜻한 차 한 잔"
           width="300" height="300"
           loading="lazy">
      <figcaption>비 오는 날의 차 한 잔 #rainyday #tea</figcaption>
    </figure>

    <figure>
      <img src="https://picsum.photos/seed/mygallery5/300/300"
           alt="서점 책장 사이에서 책을 고르는 사람의 뒷모습"
           width="300" height="300"
           loading="lazy">
      <figcaption>주말 서점 나들이 #bookstore #reading</figcaption>
    </figure>

    <figure>
      <img src="https://picsum.photos/seed/mygallery6/300/300"
           alt="저녁 하늘을 가로지르는 비행기와 분홍빛 구름"
           width="300" height="300"
           loading="lazy">
      <figcaption>퇴근길 하늘 #sunset #sky</figcaption>
    </figure>
  </section>
</main>

💡 튜터의 포인트

이 과제의 핵심은 여섯 장 모두에 같은 패턴이 들어갔는가 입니다.

  1. 시멘틱 — 각 게시물이 <figure> 한 덩어리
  2. 접근성 — 모든 <img> 에 의미 있는 alt (장식 이미지가 아니므로 alt="" 는 사용 금지)
  3. 성능 — 첫 화면 아래는 loading="lazy". 아바타와 첫 사진은 즉시 로드도 OK
  4. 레이아웃 안정 — 모든 이미지에 width="300" height="300". Layout Shift 방지
  5. 본문 연결<figcaption> 한 줄로 사진에 맥락 부여

🙋 "아바타에도 loading="lazy"를 박아야 하나요?" — 아바타는 페이지 첫 화면에 보이는 이미지라 lazy 를 안 박는 게 정답입니다. 첫 화면 이미지를 lazy 처리하면 오히려 한 박자 늦게 떠서 첫인상이 나빠지거든요. "첫 화면 보이는 곳 = 즉시 로드 / 그 아래 = lazy" 기준을 잊지 마세요.

또 한 가지 — picsum 의 seed 값을 자유롭게 정하시면 됩니다. 같은 seed 면 같은 이미지가 나오니까 새로고침해도 사진이 바뀌지 않아요. 실습이 끝날 때까지 안정적으로 같은 갤러리를 볼 수 있어요.

DevTools Network 탭 → Img 필터 → 새로고침 → 천천히 스크롤. "첫 화면 사진 1~2장만 받고, 스크롤 내려야 나머지가 받아진다" 가 확인되면 성공입니다.

⚠️ 다만 lazy 시연은 세 가지 조건 이 필요해요. "일반 새로고침(F5/Cmd+R) + Disable cache 꺼짐 + Throttling Fast 3G" 세 가지를 갖춰야 lazy 가 진짜로 동작하는 모습이 보여요. 강제새로고침(Cmd+Shift+R)이나 Disable cache 가 켜져있으면 lazy 가 우회돼서 모두 한꺼번에 받아져요. Step 3 의 함정 1·2·3 을 다시 참고하세요. 또한 profile.html 의 게시물 6장만으로는 viewport "근처" 정책 때문에 lazy 가 안 보일 수도 있어요. 그땐 feed.html (게시물 8장) 에서 시연하면 확실해요.


🎯 [과제 2 예시답안] 좋아하는 웹사이트 3곳의 이미지 alt 분석하기

채점 포인트

항목 배점 기준
3곳 이상 분석 20% 서로 다른 웹사이트 3곳을 조사했는가
alt 누락 여부 확인 20% alt 가 아예 없는 이미지를 찾았는가
alt="" 의도적 사용 확인 20% 장식 이미지에 빈 alt 가 박힌 위치를 식별했는가
의미 있는 alt 확인 20% 한 문장으로 잘 묘사된 alt 가 어디인지 짚었는가
자기 의견 한 줄 20% "alt 를 가장 잘 챙긴 사이트" 평가 근거 제시

조사 예시

여러분이 직접 확인한 결과와 다를 수 있어요. 이 예시는 형식을 보여드리는 거예요.

웹사이트 alt 누락 alt="" 장식 의미 있는 alt 끔찍한 alt (파일명 그대로)
GitHub 0/5 1/5 (장식 아이콘) 4/5 0/5
인스타그램 1/5 (광고 이미지) 0/5 3/5 1/5 ("instagram_post_xxx")
개인 블로그 A 3/5 (썸네일 다수) 0/5 2/5 0/5

💡 튜터의 포인트

조사를 마치고 나면 한 가지 통찰이 보일 거예요.

"큰 회사도 모든 이미지의 alt 를 완벽하게 챙기진 않더라."

특히 광고 이미지, 사용자 업로드 콘텐츠, 자동 생성 썸네일 에서 alt 가 부실한 경우가 많아요. 이유는 두 가지예요.

  1. 광고/사용자 콘텐츠는 자동 생성 — 일일이 alt 를 채울 수 없어서 파일명이나 빈 값으로 처리되는 경우가 많아요.
  2. 시각적으로는 문제가 안 보임 — alt 가 없어도 사진은 보이니까, 개발자가 누락을 인지하기 어려워요.

🙋 "그럼 우리도 자동 생성 이미지에는 alt 안 챙겨도 되나요?" — 아니요. "자동 생성이라도 의미 있는 alt 를 채우는 방법" 이 있어요. 업로드 시점에 사용자에게 "이 사진을 한 줄로 묘사해주세요" 라고 물어보거나, AI 가 자동으로 묘사를 생성하는 방법이 있어요. 인스타그램 자체도 "자동 alt 생성 기능" 을 2018년부터 제공하고 있어요 ("두 사람이 야외에 있는 사진" 같은 식으로).

이 과제의 진짜 목적은 "alt 를 챙긴 사이트 vs 안 챙긴 사이트의 차이를 직접 느끼는 것" 이에요. 스크린 리더 사용자의 입장에서 "alt 가 없는 사이트는 어떤 경험인지" 한 번 상상해보면, alt 의 중요성이 와닿으실 거예요.

마지막으로 — 여러분이 만들 사이트는 어떤 사이트가 되었으면 좋겠어요? 모든 이미지에 의미 있는 alt 가 들어간 "접근성을 챙긴 사이트" 가 답이라면, 오늘 배운 alt 가 평생의 습관이 될 거예요.


🤔 [생각해볼 주제 1 예시답안] 좋은 alt 텍스트는 어떻게 써야 할까?

[문제 상황 요약]

같은 강아지 사진을 두고 두 개의 alt 가 가능해요.

<!-- A: 짧지만 부실한 alt -->
<img src="dog.jpg" alt="강아지">

<!-- B: 한 문장으로 묘사한 alt -->
<img src="dog.jpg" alt="공원 잔디밭에서 공을 물고 뛰어오는 골든 리트리버">

A 는 문법상 틀린 alt 가 아니에요. 그런데 "이미지를 못 보는 사용자" 에게는 어느 쪽이 더 친절할까요?

[튜터의 가이드 및 해설]

좋은 alt 의 기준은 세 가지 축 으로 정리할 수 있어요.

1. 정보의 양 — "이 사진의 핵심을 한 문장으로"

"강아지" 만 적으면 "강아지가 있다" 라는 사실밖에 전달이 안 돼요. 스크린 리더 사용자는 "어떤 강아지가 어디서 무엇을 하는지" 를 알 수 없어요.

좋은 alt 는 3W 패턴 을 갖춥니다.

  • Who/What (무엇이) — 골든 리트리버
  • Where (어디서) — 공원 잔디밭
  • Doing (무엇을 하는지) — 공을 물고 뛰어오는

세 가지가 한 문장에 자연스럽게 녹아들면 "좋은 alt" 의 8할은 완성이에요.

2. 길이의 적정선 — "스크린 리더가 듣기에 한 호흡"

너무 길어도 곤란해요. 스크린 리더가 "강아지가 공원 잔디밭에서 공을 물고, 잔디는 봄에 새로 자란 어린 잎이고, 그 위로 햇살이 따스하게..." 처럼 끝없이 읽으면 사용자가 지쳐요.

권장 길이는 100~150자 이내 입니다. 그보다 더 자세한 묘사가 필요한 사진이라면, alt 가 아니라 본문 텍스트나 <figcaption> 으로 풀어내는 게 맞아요.

3. 장식 이미지는 일부러 비우기 — alt="" 의 가치

여기서 자주 오해가 발생해요. "alt 가 비어있으면 누락이랑 같지 않나?" 라는 질문이 자연스럽게 떠오르거든요.

답은 "전혀 다르다" 예요.

코드 스크린 리더의 반응
<img src="..."> (alt 누락) "u-s-e-r-dot-a-v-a-t-a-r-dot-p-n-g" (파일명을 그대로 읽음)
<img src="..." alt=""> (조용히 건너뜀)

장식 이미지는 "읽어줄 필요가 없는 이미지" 예요. 사용자에게 "읽지 마" 라는 명확한 지시가 alt="" 이고, "실수로 빠뜨림" 이 alt 누락이에요. 같은 빈 값처럼 보여도 의도가 완전히 다릅니다.

좋은 alt 한 줄 정의

이 모든 걸 한 문장으로 묶으면 이래요.

"좋은 alt 는 사진을 못 보는 사용자가 사진을 보는 사용자와 거의 같은 정보를 얻을 수 있게 해주는 한 문장."

이 기준을 떠올리면 "이 alt 가 적절한지" 가 자연스럽게 판단됩니다.

🎯 면접관을 홀리는 핵심 멘트

"좋은 alt 텍스트는 단순한 텍스트 라벨이 아니라 접근성의 첫 단추예요. 같은 사진이라도 alt="강아지"alt="공원에서 공을 물고 뛰어오는 골든 리트리버" 는 스크린 리더 사용자에게 완전히 다른 경험을 줍니다. 3W (무엇이, 어디서, 무엇을 하는지) 패턴을 100자 이내로 한 문장에 담는 게 제 기준이에요. 그리고 장식 이미지는 반드시 alt="" 로 명시해서 "의도적으로 건너뛰라" 는 신호를 줍니다. alt 누락과 alt="" 는 결과는 비슷해 보여도 의도가 완전히 다르고, 진짜 접근성을 챙기는 개발자는 그 차이를 의식적으로 구분합니다."


🤔 [생각해볼 주제 2 예시답안] 자동 재생 비디오는 사용자에게 친절한 기능일까?

[문제 상황 요약]

인스타그램 릴스를 보면 페이지를 열자마자 영상이 "음소거 자동 재생" 됩니다. <video autoplay muted loop> 패턴이에요.

브라우저는 "소리 나는 자동 재생" 은 차단하지만 "음소거 자동 재생" 은 허용해요. 하지만 음소거라고 해서 모두에게 친절한 기능일까요?

[튜터의 가이드 및 해설]

자동 재생의 친절함은 "세 축의 균형" 으로 봐야 해요.

1. 사용자 경험의 양면성

autoplay muted 의 장점은 분명해요.

  • "클릭 한 번 없이 콘텐츠가 시작된다" — 인스타그램 릴스의 핵심 매력
  • 시각적 자극으로 사용자의 관심을 자연스럽게 끌어옴
  • 짧은 영상은 "음소거여도 충분히 매력적"

하지만 단점도 만만치 않아요.

  • 모바일 데이터 폭증 — 사용자가 보지 않아도 영상이 다운로드됨
  • 배터리 소모 — 영상 디코딩이 계속 CPU/GPU 를 점유
  • 시각적 자극에 민감한 사용자 — ADHD, 시각 과민증, 광민감성 간질 등
  • 회의실/도서관 같은 공공장소 — 화면이 깜박이는 게 신경 쓰일 수 있음

2. 브라우저 정책의 진짜 의미

브라우저가 "소리 나는 자동 재생을 차단" 한 건 사용자 경험 보호 정책이에요. "갑자기 큰 광고 소리가 나오는 페이지" 를 막은 거죠.

하지만 "음소거 자동 재생" 까지는 허용했어요. 이게 "사용자 친화 vs 비즈니스 요구" 의 타협점이에요. 완전 금지하면 인스타그램 릴스, 트위터 비디오, 페이스북 자동 재생이 모두 작동 안 해요. 완전 허용하면 광고 소리가 사용자를 괴롭혀요.

"음소거 자동 재생만 허용""콘텐츠 노출은 OK, 청각 침해는 NO" 라는 명확한 선언이에요.

3. 개발자의 선택 — 어떤 페이지에 어떻게 적용할까?

판단 기준을 두 가지로 나눠보면 좋아요.

autoplay muted 가 어울리는 페이지

  • 짧고 시각적인 콘텐츠가 주연 (릴스, 스토리, 광고 영상)
  • 사용자가 "빠르게 훑어보는" 흐름 (피드 스크롤)
  • 영상의 첫 1~2초가 "클릭을 유도하는 미끼" 역할

사용자가 직접 재생하는 게 자연스러운 페이지

  • 긴 영상 (3분 이상 — 강의 영상, 영화 트레일러)
  • 정보 전달이 핵심인 영상 (튜토리얼, 인터뷰)
  • 청각 정보가 영상의 본질인 콘텐츠 (음악, 팟캐스트)

이 기준을 적용하면 "인스타그램 릴스에는 autoplay muted, 유튜브 영상에는 클릭 재생" 이 자연스럽게 갈려요.

추가 고려 — 사용자 선택권을 존중하는 옵션

진짜 접근성을 챙기는 사이트는 "자동 재생 끄기" 옵션을 사용자 설정에 제공해요. 시각적 자극에 민감한 사용자나 모바일 데이터를 아끼고 싶은 사용자가 끌 수 있게요. 이게 "디자인의 친절함" 에서 한 단계 더 올라가는 "포용적 디자인(Inclusive Design)" 이에요.

🎯 면접관을 홀리는 핵심 멘트

"자동 재생 비디오는 사용자 친화와 비즈니스 요구의 균형점 입니다. 브라우저가 소리 자동 재생만 차단하고 음소거 자동 재생을 허용한 건, "콘텐츠 노출은 허용, 청각 침해는 차단" 이라는 사용자 보호의 절충안이에요. 그래서 저는 페이지 성격에 따라 다르게 적용합니다. 짧은 시각 콘텐츠 (인스타 릴스, 광고) 에는 autoplay muted loop 패턴, 긴 정보 콘텐츠 (강의, 인터뷰) 에는 클릭 재생. 한 단계 더 가서 "자동 재생 끄기" 사용자 옵션까지 제공하면 시각 과민증이 있는 분이나 모바일 데이터를 아끼고 싶은 분까지 챙길 수 있어요. 이게 포용적 디자인 의 출발이라고 생각합니다."


🤔 [생각해볼 주제 3 예시답안] <table> 은 어디까지가 표이고, 어디부터가 레이아웃인가?

[문제 상황 요약]

오늘 우리는 가격표를 <table> 로 만들었어요. 그런데 "인스타그램 프로필의 게시물 / 팔로워 / 팔로잉" 통계도 보기엔 "3 칸으로 나뉜 표" 처럼 생겼어요.

인스타그램 프로필:  [게시물 42]  [팔로워 1,234]  [팔로잉 567]

이건 <table> 일까요, <div> + Flexbox 일까요?

[튜터의 가이드 및 해설]

판단 기준은 "이게 표 데이터인가, 아니면 칸 나누기인가" 입니다. 구체적으로 세 가지 질문 을 자문해보세요.

질문 1: "한 번에 비교 가능한 다차원 데이터인가?"

표는 "여러 행, 여러 열" 의 데이터가 서로 비교될 때 진가가 발휘돼요.

자리 표인가?
가격표 (패키지 × 가격, 노출, 혜택) ✅ 표
성적표 (학생 × 과목별 점수) ✅ 표
운영 시간표 (요일 × 시간) ✅ 표
인스타 프로필 (게시물, 팔로워, 팔로잉) ❌ 표 아님

가격표는 "스타터 vs 그로스 vs 엔터프라이즈" 를 비교하고 싶잖아요. 하지만 인스타 프로필의 "게시물 42, 팔로워 1234, 팔로잉 567""비교" 가 아니에요. 그냥 "3 개의 독립된 통계" 예요.

질문 2: "각 셀이 의미상 같은 종류인가, 독립된 의미인가?"

표의 각 셀은 "같은 종류의 데이터" 예요. 가격표의 "₩300,000 / ₩1,200,000 / 별도 협의" 모두 "월 비용" 이라는 같은 카테고리예요.

반면 "게시물 42, 팔로워 1,234, 팔로잉 567""숫자" 이긴 한데 카테고리가 셋이 다 달라요. "같은 종류의 다른 값" 이 아니라 "세 가지 독립된 통계" 예요.

질문 3: "이 정보를 텍스트로 풀어 적을 때 표가 더 효율적인가?"

가격표를 글로 풀어 적으면:

"스타터 패키지는 월 10,000회 노출에 ₩300,000 이고 기본 분석 리포트가 포함됩니다. 그로스 패키지는 월 50,000회 노출에 ₩1,200,000 이고 타겟 세분화 도구가 추가됩니다..."

이렇게 길게 이어져요. 표가 압도적으로 효율적이죠.

반면 "게시물 42, 팔로워 1,234, 팔로잉 567" 은 한 줄로 충분해요. 표로 만들 필요가 없어요.

최종 기준 — "표 vs 레이아웃" 결정 트리

세 질문을 통과하면 "표 vs 레이아웃" 이 명확해져요.

이 데이터는...
├─ 다차원 비교가 필요한가? ────── 예 → <table>
│                                  아니오 → 다음 질문
├─ 모든 셀이 같은 카테고리인가? ── 예 → <table>
│                                  아니오 → 다음 질문
└─ 글로 풀어 적으면 비효율적인가? 예 → <table>
                                   아니오 → CSS 레이아웃 (div + Flexbox/Grid)

세 질문 모두 "예"<table>, 어느 하나라도 "아니오" 면 CSS 레이아웃으로 가는 게 자연스러워요.

다음 모듈(B-3) 의 복선

다음 모듈부터 등장할 CSS Flexbox 와 Grid 는 "표가 아닌 칸 나누기" 의 세계예요. "화면을 가로로 3등분", "카드를 격자로 정렬" 같은 자리는 Flexbox/Grid 의 영역이에요. 그 자리에 <table> 을 쓰면 "옛날 사이트 같은 코드" 가 됩니다. 오늘의 기준을 떠올리며 "표는 표 데이터에만, 레이아웃은 CSS 에" 라는 원칙을 챙기시면 돼요.

🎯 면접관을 홀리는 핵심 멘트

"<table> 을 쓸지 <div> + Flexbox 를 쓸지는 세 가지 질문 으로 판단합니다. (1) 다차원 비교가 필요한 데이터인가? (2) 모든 셀이 같은 카테고리인가? (3) 글로 풀어 적으면 비효율적인가? 세 질문 모두 라면 <table> 이고, 하나라도 아니오 면 CSS 레이아웃이에요. 예를 들어 가격표는 세 질문 모두 <table> 로 만들지만, 인스타그램 프로필의 게시물/팔로워/팔로잉 통계는 세 가지 독립된 통계<div> + Flexbox 로 만들어요. 2026년 표준은 표는 표 데이터에만, 레이아웃은 CSS 에 이고, 이 기준이 시멘틱 마크업과 접근성을 모두 지키는 길이라고 생각합니다."

더 배우려면

실무 프로젝트까지 가고 싶다면

팀스파르타 백엔드 부트캠프에서 인스타그램 클론을 풀스택으로 완성합니다.