Skip to content
This page has been auto-translated and may contain errors.View in English

반응형 디자인

docs.scrimba.com

노트북에서 페이지를 만들었는데 잘 보이다가, 누군가 휴대폰에서 열면 어떻게 될까요? 신중하게 정렬한 칼럼들이 이제는 좁은 띠로 짜부라지고, 텍스트가 가장자리를 넘어가고, 있어야 할 곳에 가로 스크롤바가 나타납니다. 아무도 사이트의 별도 휴대폰 버전을 만들지 않았습니다. 단 하나의 페이지가 있으며, 380픽셀 휴대폰과 1400픽셀 모니터 모두에서 올바르게 보여야 하는데 두 배의 내용을 제공할 수 없습니다. 이러한 적응성을 반응형 디자인이라고 하며, 대부분은 이미 보유하고 있는 도구로 구축됩니다.

반응형의 의미와 뷰포트

반응형 페이지는 자신이 놓인 모든 화면에 맞춰 다시 형태를 바꾸는 하나의 페이지입니다. 동일한 HTML과 동일한 CSS가 휴대폰, 태블릿, 데스크톱을 제공하며, 레이아웃은 조정되어 아무것도 오버플로우되지 않고 아무것도 답답하지 않습니다. 당신은 세 개의 사이트를 만드는 것이 아니라 구부러지는 하나를 만드는 것입니다.

이 모든 것이 작동하기 전에 브라우저는 HTML에서 하나의 지시사항이 필요합니다. 휴대폰은 예전에 데스크톱 너비인 척했고 전체 페이지를 축소해서 맞췄으므로, 오래된 사이트들은 작고 축소된 혼란스러운 모습으로 표시되었습니다. HTML의 <head>에 있는 이 한 줄은 휴대폰에게 대신 실제 너비를 사용하라고 말합니다:

html
<meta name="viewport" content="width=device-width, initial-scale=1" />

이것 없이는 CSS가 가상의 넓은 화면에 스타일을 지정하고 있으며 신중하게 만든 레이아웃은 기회를 얻지 못합니다.

반응형 디자인은 뷰포트, 즉 브라우저 창 내의 페이지의 보이는 영역 크기에 따라 레이아웃을 조정하는 단일 코드베이스를 의미합니다. 하나의 HTML 세트와 하나의 스타일시트가 있으며, 변하는 것은 그 스타일시트가 주어진 공간에 어떻게 응답하는지입니다. 대체 방법인 별도의 모바일 및 데스크톱 사이트는 유지관리를 두 배로 만들고, 이것이 업계가 몇 년 전에 반응형으로 결정한 이유입니다.

시작 요구사항은 HTML head의 뷰포트 메타 태그입니다. 브라우저가 레이아웃하는 너비를 기기의 실제 너비로 설정하고, 모바일 브라우저가 가짜 데스크톱 너비에서 렌더링하고 축소하는 것을 중지합니다:

html
<meta name="viewport" content="width=device-width, initial-scale=1" />

width=device-width는 레이아웃 뷰포트를 화면과 일치시키고, initial-scale=1은 초기 줌을 100%로 설정합니다. 이것을 생략하면 올바른 CSS가 아무리 많아도 레이아웃을 구할 수 없습니다. 브라우저가 잘못된 너비에 대해 측정하고 있기 때문입니다.

반응형 디자인은 고정된 몇 개의 화면을 대상으로 하는 대신 뷰포트 크기의 연속 범위에 적응하는 레이아웃을 가진 하나의 문서를 구축하는 관행입니다. 뷰포트는 브라우저가 페이지를 레이아웃하고 그리는 데 사용하는 영역이며, 그 너비는 모든 상대 단위, 백분율, 미디어 쿼리가 궁극적으로 확인되는 값입니다. 그 너비를 잘못 얻으면 그 다음의 모든 것은 잘못된 값을 측정합니다.

이것이 정확히 뷰포트 메타 태그가 방지하는 것입니다. 모바일 브라우저는 역사적으로 레이아웃 뷰포트(CSS가 레이아웃하는 너비)를 시각적 뷰포트(실제로 화면에 있는 것)와 구별했으며, 레거시 데스크톱 전용 사이트가 렌더링되고 맞도록 축소되도록 레이아웃 뷰포트를 약 980px로 기본 설정했습니다. 메타 태그는 그 간격을 닫습니다:

html
<meta name="viewport" content="width=device-width, initial-scale=1" />

width=device-width는 레이아웃 뷰포트를 기기 자체의 CSS 픽셀 너비에 연결하고, initial-scale=1은 초기 줌을 고정하여 두 뷰포트가 정렬된 상태로 시작되도록 합니다. HTML head에 있어야 하며, 편의사항이 아닌 전제 조건입니다: 이것 없으면 브라우저는 390px 휴대폰에서 대략 980px에 대해 미디어 쿼리와 백분율을 평가하므로, min-width: 40rem 쿼리가 그럴 리 없을 때 작동하고 테스트한 레이아웃은 나타나지 않습니다. 이 장의 모든 것은 그 태그가 있다고 가정합니다.

Juno반응형의 의미와 뷰포트 반응형 페이지는 휴대폰부터 데스크톱까지 모든 화면에 맞춰 다시 형태를 바꾸는 하나의 페이지이며, 단일 HTML 및 CSS 세트에서 비롯됩니다. CSS가 공정한 기회를 얻기 전에, HTML head는 휴대폰에게 데스크톱인 척하는 대신 실제 너비를 사용하도록 말하는 뷰포트 메타 태그가 필요합니다. 이것을 생략하면 CSS가 아무리 좋아도 레이아웃이 손상되어 보입니다.
Juno반응형의 의미와 뷰포트 반응형은 뷰포트, 즉 보이는 페이지 영역에 적응하는 단일 코드베이스를 의미하며, 별도의 모바일 및 데스크톱 빌드가 아닙니다. HTML head의 뷰포트 메타 태그는 시작 요구사항입니다: width=device-width는 브라우저가 실제 화면 너비에 대해 레이아웃하게 합니다. 이것을 건너뛰면 미디어 쿼리가 가짜 데스크톱 너비에 대해 작동하므로 올바른 CSS도 여전히 잘못되어 보입니다.
Juno반응형의 의미와 뷰포트 모든 백분율과 미디어 쿼리는 뷰포트 너비에 대해 확인되므로 뷰포트 메타 태그는 편의사항이 아닌 전제 조건입니다. width=device-width, initial-scale=1은 레이아웃 뷰포트를 레거시 980px 기본값 대신 기기의 실제 너비에 연결합니다. 휴대폰에서 이것을 잊으면 min-width: 40rem 쿼리가 그럴 리 없을 때 작동하고, 테스트한 레이아웃은 렌더링되지 않습니다.

먼저 유동적 레이아웃

반사적으로 바로 특수 휴대폰 규칙을 찾으려는 충동이 있지만, 많은 반응성은 하나도 작성하기 전에 자유롭게 옵니다. 유동적 값으로 구축하면, 공간으로 늘어나고 축소되는 값을 사용하면, 레이아웃이 이미 적응합니다.

두 가지 습관이 대부분을 수행합니다. 너비를 백분율로 설정하여 상자가 고정된 픽셀 수가 아닌 컨테이너의 일부를 차지하도록 하고, max-width를 사용하여 작은 화면에서는 축소할 수 있지만 큰 화면에서 편안한 읽기 크기를 초과하지 않도록 합니다:

css
.container {
  width: 90%;              /* 가진 공간의 90%를 차지 */
  max-width: 60rem;        /* 하지만 큰 화면에서 60rem보다 넓지 않음 */
  margin: 0 auto;          /* 중앙 정렬 */
}

휴대폰에서 이 컨테이너는 좁은 화면의 90%입니다. 모니터에서는 60rem에서 증가를 멈추고 중앙에 앉습니다. 휴대폰 규칙을 작성하지 않았습니다. 값이 적응했습니다.

미디어 쿼리 전에 유동적 값으로 레이아웃을 구축하여 공간 변화에 따라 유동적으로 움직입니다. 상대 단위와 백분율은 요소가 고정 크기 대신 컨테이너의 일부를 차지하게 하며, flexboxgrid는 이미 공간이 변할 때 자식을 다시 배열합니다. 좋은 유동적 레이아웃은 처음부터 화면과 싸우지 않기 때문에 경직된 레이아웃보다 훨씬 적은 중단점이 필요합니다.

유동적 레이아웃이 더 이상 대처할 수 없을 때만 미디어 쿼리를 찾습니다. 백분율과 max-width는 너비를 처리하고, 하나의 그리드 패턴은 미디어 쿼리 없이도 일반적인 카드 벽 경우를 처리합니다:

css
.card-grid {
  display: grid;
  gap: 1.5rem;
  /* 최소 16rem에서 맞는 만큼의 열, 남은 공간을 동일하게 공유 */
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

repeat(auto-fit, minmax(16rem, 1fr))는 "최소 16rem씩 맞을 수 있는 만큼의 열을 맞춘 다음 행을 채우도록 늘려라"라고 말합니다. 넓은 화면에서는 3열, 태블릿에서는 2열, 휴대폰에서는 1열이며 미디어 쿼리는 없습니다. 이러한 값에 사용할 상대 단위는 크기 단위에서 다룹니다.

가장 강력한 반응형 레이아웃은 대부분 **유동적**이며 단일 미디어 쿼리가 작성되기 전이고, 중단점은 불연속 점프이며 대부분의 작업은 연속적입니다. 백분율, max-width, flexboxgrid에 내장된 본질적 크기 조정을 통해 레이아웃은 뷰포트를 부드럽게 추적하고, 미디어 쿼리는 디자인이 정말로 모양을 바꿔야 하는 지점만 처리하고, 그 사이의 모든 픽셀 크기 조정은 아닙니다.

번외의 가치가 있는 패턴은 자체 조정 그리드로, 전체 중단점 세트를 하나의 선언으로 축소합니다:

css
.card-grid {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}

minmax(16rem, 1fr)은 각 트랙에 16rem의 최소값과 여유 공간의 동일한 일부의 최대값을 제공하고, auto-fit은 그 최소값에서 맞는 만큼의 트랙을 만든 다음 행을 채우도록 확장합니다. 열 개수는 컨테이너 너비에서 자동으로 떨어지므로 3개의 중단점이 필요한 레이아웃은 0개를 차지합니다. 기억할 가치가 있는 정확도 참고: auto-fit은 존재하는 항목이 남은 공간을 채우도록 늘어나도록 빈 트랙을 축소하는 반면, auto-fill은 빈 트랙을 예약 상태로 유지하므로, 항목을 남은 공간에 확장하려면 auto-fit을 선택하고 안정적인 그리드를 원하면 auto-fill을 선택합니다. 이러한 remfr 값이 어떻게 해결되는지는 크기 단위를 참조합니다.

Juno먼저 유동적 레이아웃 특수 규칙을 작성하기 전에 많은 반응성이 자유롭습니다: 백분율을 사용하여 상자가 공간의 일부를 차지하도록 하고, max-width를 사용하여 휴대폰에서는 축소하지만 모니터에서는 너무 넓게 늘어나지 않도록 합니다. 이 두 값은 레이아웃을 자체 적응하게 합니다. 먼저 유동적으로 구축하면, 다음에 오는 휴대폰별 규칙이 훨씬 더 필요합니다.
Juno먼저 유동적 레이아웃 중단점을 구축하기 전에 유동적으로 구축합니다: 백분율과 max-width는 너비를 처리하고, flexbox와 grid는 이미 스스로 다시 배열합니다. 기억할 한 그리드 라인은 repeat(auto-fit, minmax(16rem, 1fr))이며, 이것은 미디어 쿼리 없이 반응형 카드 벽을 제공합니다. 유동적 레이아웃이 정말로 대처를 멈춘 후에만 쿼리를 찾습니다.
Juno먼저 유동적 레이아웃 중단점은 불연속 점프이지만 크기 조정은 연속적이므로 백분율, max-width, 본질적 flex 및 grid 크기 조정이 부드러운 부분을 수행하고 미디어 쿼리는 실제 형태 변경을 위해 저장합니다. repeat(auto-fit, minmax(16rem, 1fr))은 여러 중단점을 한 라인으로 축소합니다. auto-fit은 빈 트랙을 축소하여 항목을 늘어나게 하는 반면 auto-fill은 트랙을 예약 상태로 유지한다는 점을 기억합니다.

미디어 쿼리

때때로 유동적 값으로는 충분하지 않으며 특정 크기에서 레이아웃이 실제로 변경되어야 합니다. 예를 들어 휴대폰에서는 세로로 쌓이지만 데스크톱에서는 행으로 나타나는 메뉴가 있습니다. 이것이 **미디어 쿼리**가 하는 것입니다: 당신이 설정한 조건을 화면이 충족할 때만 적용되는 CSS 블록입니다.

작은 화면의 스타일로 시작하여 일반 CSS로 사용한 다음, 더 넓은 화면 변경사항을 레이어하는 쿼리를 추가합니다:

css
.nav {
  display: flex;
  flex-direction: column;   /* 작은 화면에서 쌓임 */
}

@media (min-width: 40rem) {
  .nav {
    flex-direction: row;    /* 공간이 생기면 나란히 배치 */
  }
}

@media (min-width: 40rem) 내의 규칙은 화면이 최소 40rem 이상 넓어진 후에만 작동합니다. 그 아래에서는 일반 .nav 규칙이 적용되고 메뉴는 쌓입니다.

**미디어 쿼리**는 규칙 세트를 조건에 감싸고, 대부분 너비 테스트이며, 이러한 규칙은 조건이 유지될 때만 적용됩니다. 채택할 패턴은 **모바일 우선**입니다: 쿼리 없이 가장 작은 화면에 대한 기본 스타일을 작성한 다음, min-width로 위로 향상시킵니다.

css
.layout {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;      /* 기본: 단일 열, 작은 화면 */
}

@media (min-width: 40rem) {
  .layout {
    grid-template-columns: 1fr 1fr; /* 공간이 생기면 2열 */
  }
}

모바일 우선은 정돈 이상의 이유로 중요합니다. 기본 스타일은 항상 적용되는 스타일이므로 가장 간단하고 신뢰성 있는 레이아웃을 만들면 가장 작고 제약이 많은 기기가 기본적으로 가장 안전한 경험을 얻고 각 쿼리는 기능만 추가합니다. 다른 방식으로 구축하면 데스크톱 우선이고 max-width 쿼리가 제거되면 모바일 보기가 망가지는 사후 생각이 되는 경향이 있습니다.

**미디어 쿼리**는 미디어 조건이 참으로 평가될 때만 규칙 블록을 적용하며, 반응형 레이아웃의 경우 그 조건은 거의 항상 뷰포트 너비 테스트입니다. 버티는 규칙은 **모바일 우선**입니다: 무조건적 기본 스타일은 좁은 레이아웃을 설명하고, 모든 min-width 쿼리는 순전히 위에 추가됩니다.

css
.layout {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr;       /* 기본, 모든 곳에 적용 */
}

@media (min-width: 40rem) {         /* 태블릿 이상 */
  .layout { grid-template-columns: 1fr 1fr; }
}

@media (min-width: 64rem) {         /* 데스크톱 이상 */
  .layout { grid-template-columns: 1fr 2fr 1fr; }
}

가산적 min-width를 뺄셈적 max-width 위에 선호하는 이유는 캐스케이드입니다. 이러한 쿼리가 특이성을 공유하기 때문에, 나중에 작동되는 쿼리가 이전 쿼리를 재정의하므로 작은 것부터 큰 것으로 순서를 지정하면 각 더 넓은 중단점이 적용되는 곳에서 명확하게 승리합니다. 기억할 가치가 있는 두 가지 정확도: 조건에서 px 대신 rem을 선호하여 중단점이 사용자의 글꼴 크기를 추적하고, 너비 쿼리가 레이아웃 뷰포트를 읽는다는 것을 알아둡니다. 이것은 뷰포트 메타 태그가 기기 너비에 고정했기 때문에만 올바릅니다. min-width 스택의 중간에 있는 길잃은 max-width 쿼리는 규칙이 신비롭게 적용을 중단하는 일반적인 원인이며, 이것은 순서를 확장하는 대신 싸우고 있기 때문입니다.

Juno미디어 쿼리 미디어 쿼리는 화면이 최소 40rem 이상 넓어지는 등 조건을 충족할 때만 작동하는 CSS 블록입니다. 작은 화면 스타일을 먼저 일반 CSS로 작성한 다음 @media (min-width: ...)를 사용하여 더 큰 화면의 변경사항을 추가합니다. 이렇게 하면 휴대폰 보기가 나중에 붙인 패치가 아닌 시작점이 됩니다.
Juno미디어 쿼리 미디어 쿼리는 조건(보통 너비)이 참일 때만 규칙을 적용합니다. 모바일 우선으로 가세요: 가장 작은 화면에 대한 쿼리 없이 기본 스타일을 한 다음, min-width로 위로 레이어링하여 모든 쿼리가 기능만 추가하도록 합니다. 다른 방법으로 max-width로 구축하면 모바일 보기가 망가진 사후 생각이 되는 경향이 있습니다.
Juno미디어 쿼리 모바일 우선은 좁은 레이아웃에 대한 무조건적 기본 스타일과 작은 것부터 큰 것으로 쌓인 추가적 min-width 쿼리를 의미하므로 캐스케이드가 각 중단점을 명확하게 해결합니다. 조건에서 rem을 사용하여 사용자의 글꼴 크기를 추적하고, 너비 쿼리가 메타 태그가 고정한 레이아웃 뷰포트를 읽는다는 것을 기억합니다. min-width 스택의 외로운 max-width는 규칙이 조용히 적용을 중단하는 고전적인 이유입니다.

중단점 선택

**중단점**은 미디어 쿼리가 작동되고 레이아웃이 변경되는 너비입니다. 유혹하는 방법은 인기 있는 휴대폰이나 태블릿의 너비를 찾고 맞추는 것입니다. 이것이 함정입니다. 수백 개의 기기 크기가 있으며, 해마다 변경되고, 이를 쫓는 것은 이길 수 없는 게임입니다.

더 나은 질문은: 레이아웃이 어느 너비에서 나빠 보이기 시작합니까? 브라우저를 천천히 넓혀서 보세요. 텍스트 줄이 너무 길어져서 편하게 읽을 수 없거나, 두 열이 너무 좁아지면 그 중단점이 있어야 할 곳이며, 그 숫자가 무엇이든 상관없습니다.

css
/* 중단점이 선택된 이유는 텍스트가 여기서 너무 넓어졌기 때문이지, */
/* 휴대폰이 우연히 이 크기이기 때문이 아님 */
@media (min-width: 45rem) {
  .article { max-width: 38rem; }
}

**중단점**은 레이아웃을 변경하는 뷰포트 너비입니다. 기기 이름을 맞추지 말고 자신의 콘텐츠를 보고 선택합니다. "768px는 iPad이기 때문"이라는 둥근 숫자는 권위 있어 보이며 함정입니다: 기기 환경은 지속적으로 변경되고, 지난해의 인기 있는 크기에 맞춘 레이아웃은 올해의 기기에서 깨집니다.

콘텐츠가 결정하게 하세요. 창을 조정하고 설계가 실제로 부담을 느끼는 너비에 중단점을 추가하세요. 줄이 너무 길어지고, 카드가 너무 답답해지고, 네비게이션이 공간이 부족해질 때입니다. 보통 전체 사이트에서 2개 또는 3개만 필요합니다:

css
/* 콘텐츠 기반: 이 줄이 약 75자에 도달하고 읽기 어려워짐 */
@media (min-width: 42rem) {
  .prose { max-width: 65ch; }   /* 공간이 생기면 범위를 제한 */
}

/* 콘텐츠 기반: 사이드바 레이아웃은 여기서만 공간을 벌 가치가 있음 */
@media (min-width: 60rem) {
  .page { grid-template-columns: 16rem 1fr; }
}

**중단점**은 설계가 형태를 바꾸는 곳이며, 내구성 있는 방법은 기기 사양지가 아닌 콘텐츠로 선택하는 것입니다. 명명된 기기 너비를 맞추는 것은 구조적 이유 때문에 취약합니다: 화면 크기의 모집단은 크고 해마다 변경되므로, 특정 기기 크기에 맞춰진 모든 레이아웃은 이동 목표에 맞춰지며 예정표에 따라 손상됩니다. 콘텐츠로 정당화된 중단점, "범위가 읽을 수 있는 줄 길이를 통과했습니다"는 다음에 어떤 하드웨어가 출시되든 정당화된 상태로 유지됩니다.

실제로 이것은 레이아웃이 부담을 느낄 때까지 조정하고 거기에 중단점을 배치하는 것을 의미하며, 대부분의 사이트는 몇 개만 필요합니다:

css
/* 줄이 읽을 수 있는 길이를 통과하는 범위를 제한, 약 65자 */
@media (min-width: 42rem) {
  .prose { max-width: 65ch; }
}

/* 메인 열이 너비를 제공할 수 있는 곳에만 사이드바 도입 */
@media (min-width: 60rem) {
  .page { grid-template-columns: 16rem 1fr; }
}

고급 방법은 유동적 타입과 간격에 clamp()를 활용하는 것이므로 더 적은 중단점이 필요합니다. clamp(min, preferred, max)는 뷰포트로 확장하지만 양쪽 끝에서 제한되는 값을 선택하므로, 각 쿼리에서 점프하는 대신 제목과 간격이 부드럽게 증가합니다:

css
h1 {
  /* 1.75rem 아래로, 3rem 위로 가지 않음, 그 사이 유동적 */
  font-size: clamp(1.75rem, 4vw + 1rem, 3rem);
}

이것은 중단점이 없이 대부분의 타이포그래피 스케일을 다룹니다. 자세한 내용은 크기 단위에서 vw 항이 어떻게 해결되는지, 그리고 타이포그래피에서 타입 스케일 측면을 참조합니다. 세 가지 관련 도구로 이것을 마무리합니다. 컨테이너 쿼리는 전체 페이지가 아닌 자체 컨테이너의 너비에 따라 컴포넌트를 스타일링하므로, 카드가 전체 페이지 대신 앉은 열에 반응할 수 있으며, 이것은 뷰포트 기반 미디어 쿼리에 대한 컴포넌트 기반 대응입니다. prefers-reduced-motion 쿼리는 애니메이션을 감소하도록 요청한 독자의 애니메이션을 제거하도록 하고, prefers-color-scheme은 시스템 밝음 또는 어두움 선호도를 존중하도록 하며, 둘 다 미디어 쿼리로 작성됩니다:

css
@media (prefers-reduced-motion: reduce) {
  * { animation: none; transition: none; }  /* OS 설정 존중 */
}

@media (prefers-color-scheme: dark) {
  body { background: #111; color: #eee; }
}

이미지의 경우, 반응성은 HTML로 이동합니다: srcset 속성은 브라우저가 화면당 적절한 크기의 파일을 선택하도록 하며, 이는 CSS가 아닌 HTML 반응형 이미지 장에서 다룹니다.

Juno중단점 선택 중단점은 레이아웃이 변경되는 너비이며, 함정은 특정 휴대폰이나 태블릿과 맞추도록 선택하는 것입니다. 추적할 기기 크기가 너무 많으며, 해마다 변경됩니다. 브라우저를 천천히 넓혀서 레이아웃이 실제로 나빠 보이기 시작하는 곳에 중단점을 추가하세요. 그 숫자가 무엇이든 상관없습니다.
Juno중단점 선택 콘텐츠가 중단점을 선택하게 하고, 기기 이름이 아닙니다: 줄이 너무 길어지거나 카드가 너무 답답해지는 것이 실제 신호이며, 768px 같은 둥근 기기 너비는 변경되는 목표입니다. 디자인이 부담을 느낄 때까지 조정하고 중단점을 거기에 배치합니다. 대부분의 사이트는 전체에서 2개 또는 3개만 필요합니다.
Juno중단점 선택 콘텐츠 기반 중단점은 화면 모집단이 해마다 변경되므로 생존합니다. 따라서 기기 크기에 맞춘 모든 것은 예정표에 따라 손상됩니다. 유동적 타입과 간격에 clamp()를 활용하여 모든 중단점이 필요하지 않으며, 컴포넌트 수준이 아닌 뷰포트 수준 스타일링을 위해 컨테이너 쿼리를 염두에 두고, prefers-reduced-motionprefers-color-scheme을 존중합니다. 이미지의 경우, 반응성은 CSS가 아닌 HTML srcset에 있습니다.