[번역] Mobile First Design vs Desktop First Design
원문 : https://ishadeed.com/article/the-state-of-mobile-first-and-desktop-first/
모바일 퍼스트
- 모바일 버전 css를 먼저 작성한 다음 더 큰 버전을 작성
.section {
padding: 2rem 1rem;
}
@media (min-width: 62.5rem) {
.section {
display: flex;
align-items: center;
gap: 1rem;
padding: 4rem 2rem;
}
}
(min-width : 62.5rem) 쿼리 조건은 width가 62.5rem보다 같거나 크면을 의미함.
더 큰 뷰포트에서 더 큰 패딩과 간격을 의미.
데스크톱 퍼스트
- 데스크톱 버전 css를 먼저 작성하고 더 작은 버전을 작성
.section {
display: flex;
align-items: center;
gap: 1rem;
padding: 4rem 2rem;
}
@media (max-width: 62.5rem) {
.section {
display: block;
padding: 2rem 1rem;
}
}
먼저 더 큰 뷰포트 크기에 대한 CSS를 작성한 다음 CSS 미디어 쿼리를 사용하여 더 작은 크기에 대한 CSS를 변경함.
(max-width : 62.5rem) 쿼리 조건은 width가 62.5rem보다 같거나 작으면을 의미함.
모바일 퍼스트 작업 방식
- 전체 페이지를 모바일 퍼스트로 작성하고, 각 중단점에 대한 컴포넌트 별 css를 작성 (직렬)
- 컴포넌트 별로 모바일 퍼스트로 작성하고, 중단점에 대한 컴포넌트 별 CSS를 작성하는 작업을 반복 (병렬)
각 컴포넌트가 서로 영향을 안미치게 하려면 2번 방식이 좋음
예시로 hero 영역 CSS를 보자.
.hero {
display: flex;
align-items: flex-end;
background-image: url('hero.jpg');
background-size: cover;
background-repeat: no-repeat;
}
.hero__title {
font-size: 1rem;
}
.hero__thumb {
display: none;
}
@media (min-width: 60rem) {
.hero {
align-items: center;
background-image: initial;
background-color: #7ecaff;
}
.hero__title {
font-size: 2rem;
}
.hero__thumb {
max-width: 320px;
display: block;
}
}
폰트 사이즈와 배경 정도만 재정의해주면 됨.
네비게이션은 생각보다 재정의 할 요소들이 많음.
.nav {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow-y: auto;
padding-top: 2rem; /* Space for the toggle */
}
.nav__toggle {
position: absolute;
right: 1rem;
top: 1rem;
}
.nav__item {
padding: 1rem;
display: block;
}
.nav__item:not(:last-child) {
border-bottom: 1px solid #fff;
}
/* Desktop styles */
@media (min-width: 60rem) {
.nav {
position: initial;
width: initial;
height: initial;
overflow-y: initial;
display: flex;
align-items: center;
padding-top: 0;
background-color: blue;
}
.nav__toggle {
display: none;
}
.nav__item:hover {
color: blue;
background-color: initial;
}
.nav__item:not(:last-child) {
border-bottom: 0;
border-left: 1px solid #fff;
}
}
즉, 데스크톱 버전 CSS를 위해 많은 CSS를 재정의해야함.
또한 셀렉티비티 문제가 있음.
모바일 버전이 셀렉티비티가 더 높으면 데스크톱 버전이 동작안함.
.nav__item {
border-bottom: 0;
}
:not 선택자 떄문에 셀렉티비티가 높아졌기 때문
이를 예방하려면, 모바일 버전의 셀렉티비티를 낮추거나, 대칭꼴로 선택자를 적용함.
.nav .nav__item {
border-bottom: 0;
}
/* Or */
.nav__item:not(:last-child) {
border-bottom: 0;
border-left: 1px solid #fff;
}
데스크톱 퍼스트 작업 방식
.nav {
display: flex;
align-items: center;
background-color: blue;
}
.nav__toggle {
position: absolute;
right: 1rem;
top: 1rem;
}
.nav__item {
padding: 1rem;
display: block;
}
.nav__item:hover {
color: blue;
background-color: initial;
}
.nav__item:not(:last-child) {
border-bottom: 0;
border-left: 1px solid #fff;
}
@media (max-width: 25rem) {
.nav {
display: block;
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow-y: auto;
padding-top: 2rem; /* Space for the toggle */
}
.nav__toggle {
display: block;
}
.nav__item:not(:last-child) {
border-bottom: 1px solid #fff;
}
}
max-width로 뷰포트가 넓어졌을 때의 디자인이 추가되도록 하기 때문에, 재정의가 적음
생각해보면 모바일 퍼스트는 min-width 범위가 모든 미디어 쿼리에서 겹친다.
미디어 쿼리에 의해 레이아웃이 변경되는 시점도 테스트 해주는것이 좋다.(layout flickering.)
스타일 범위 지정(Scoping Styles)
이 게시물(this great Article)에서 설명한 하이브리드 접근 방식을 사용한다.
- 특정 컴포넌트는 각 중단점에서 다르게 보인다.
- 각 뷰포트 별 CSS를 재정의를 고려하지 않을 수 있도록 작성한다.
.nav {
/* Base styles: not related to any viewport size */
}
/* Desktop styles */
@media (min-width: 800px) {
.nav { ... }
}
/* Mobile styles */
@media (max-width: 799px) {
.nav { ... }
}
특정 컴포넌트는 대부분의 중단점에서 비슷할 수 있다. 이 경우는 필요한 경우만 작성한다.
.section {
padding: 1rem;
}
/* Desktop styles */
@media (min-width: 800px) {
.section {
padding: 2rem 1rem;
}
}
미래의 반응형 디자인 작업방식
최신 css 레이아웃 방법을 사용하면 미디어 쿼리 없이 쉽게 반응형 디자인을 구축할 수 있음
따라서 뷰포트 크기에 따라 복잡하게 바뀌는 컴포넌트를 제외하면,
대부분의 반응형 디자인은 화면 크기에 따라 무엇을 숨기느냐 / 보이느냐의 이야기가 될 것임
아래 그림에서 큰 차이가 있는 부분은 헤더와 네비게이션임.
이 둘은 min-width, max-width 미디어 쿼리를 혼합해서 사용하면 좋을 것임
나머지는 min-width 정도면 충분할 것임
모바일 네비게이션을 살펴보자
이 디자인을 모바일 퍼스트 CSS로 구현하면 많은 재정의가 발생함.
.header {
/* Base styles */
}
/* Desktop styles */
@media (min-width: 1000px) {
.nav__toggle,
.nav__close {
display: none;
}
}
/* Mobile styles */
@media (max-width: 999px) {
.nav {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: #4777dB;
}
}
hero 영역은 flexbox면 충분함. 그리고 .hero_thumb만 조건부로 순서를 바꿔주면 됨.
<section class="hero">
<div class="wrapper">
<img src="thumb.jpg" alt="" />
<h2><!-- Headline --></h2>
<p><!-- Description --></p>
</div>
</section>
.hero {
display: flex;
flex-direction: column;
}
@media (min-width: 1000px) {
flex-direction: row;
}
@media (max-width: 999px) {
.hero__thumb {
order: -1;
}
}
max-width 미디어 쿼리로 범위를 지정하여 order: -1을 한 번만 작성함.
이를 모바일 퍼스트 만으로 작성하면 다음과 같이 될 것임.
.hero__thumb {
order: -1;
}
@media (min-width: 1000px) {
.hero__thumb {
order: initial;
}
}
위 CSS는 다음과 같은 문제가 있음에 주의하자.
- 시각적 순서와 DOM 순서의 불일치
- CSS 중복
해당 작업의 플로우 차트는 다음과 같다.
미디어 쿼리에 동일한 중단점 크기 사용 피하기
min-width와 max-width에 동일한 중단점 크기를 사용하면 어떻게 될까?
@media (max-width: 500px) {
.nav {
display: none;
}
}
@media (min-width: 500px) {
.nav__toggle {
display: none;
}
}
500px에선 아무것도 안보이게 된다.
개발자 도구를 사용해 수동으로 테스트 하는 경우 아니면 이런 문제가 발생하는지도 모르고 넘어갈 수 있음.
@media (max-width: 499px) {
.nav {
display: none;
}
}
@media (min-width: 500px) {
.nav__toggle {
display: none;
}
}
디자이너에게 모바일 퍼스트가 좋지 않은 이유
- 스크롤이 짜증남
- 작은 화면은 창의성을 제한함
최신 CSS는 디바이스 너비에 무관하게 디자인할 수 있게 해줌
Flexbox Wrapping
참고 : How to Make a Media Query-less responsive Card Component
아래와 같이 너비에 따라 변경되는 카드 레이아웃은 flexbox로 쉽게 작성할 수 있음
flex-basis의 합 600px만큼 컨테이너 너비를 확보하지 못하면 줄바꿈되도록 flex-wrap을 사용할 수 있음.
Minmax
minmax에 대한 더 자세한 내용은 this article 참조.
- 최소 200px 크기로 전체 컨테이너에 최대한 많은 요소를 넣으려 함.
- 하나의 아이템만 존재하면 화면을 자동으로 채움
https://defensivecss.dev/tip/auto-fit-fill/
.wrapper {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 1rem;
}
뷰포트 단위와 비교 함수
뷰포트 단위와 비교 함수를 사용하면 폰트 크기, 패딩, 마진 등의 변경을 줄일 수 있음.
.title {
font-size: clamp(16px, (1rem + 5vw), 50px);
}
.hero {
padding: clamp(2rem, 10vmax, 10rem) 1rem;
}
.sidebar {
flex-basis: max(30vw, 150px);
}
컨테이너 쿼리
뷰포트 너비가 아닌 컨테이너 너비에 반응하도록 반응형 디자인을 적용할 수 있음.
.wrapper {
contain: layout inline-size;
}
@container (min-width: 250px) {
.pagination {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.pagination li:not(:last-child) {
margin-bottom: 0;
}
}
@container (min-width: 500px) {
.pagination {
justify-content: center;
}
.pagination__item {
display: block;
}
}