연습장

19. Responsive Web Design 반응형 레이아웃 본문

CSS3

19. Responsive Web Design 반응형 레이아웃

js0616 2024. 6. 30. 23:08

https://poiemaweb.com/css3-responsive-web-design

 

CSS3 Responsive Web Design | PoiemaWeb

CSS3 'Responsive Web Design' '반응형 웹디자인' viewport @media

poiemaweb.com

 

 

 

 

모바일과 같이 작은 해상도의 디바이스에서 접근했을 때 화면이 너무 작아져 가시성에 문제가 발생한다.


 

1. Responsive Web Design 개요
layout은 방문자의 화면 해상도를 고려하여야 한다.
또한 스마트폰이나 태블릿 등 모바일 기기는 화면이 작기 때문에 가독성에 더욱 신경써야 한다. 

이러한 문제를 해결하는 방법 중의 하나가 반응형 웹디자인(Responsive Web Design)이다. 

화면 해상도에 따라 가로폭이나 배치를 변경하여 가독성을 높이는 것이다. 

즉, 하나의 웹사이트를 구축하여 다양한 디바이스의 화면 해상도에 최적화된 웹사이트를 제공하는 것이다..

 

 

 

1.1 viewport meta tag
viewport란 웹페이지의 가시영역을 의미한다. viewport는 디바이스에 따라 차이가 있다. 

 

예를 들어 모바일 브라우저는 주화면이 세로 화면이고 윈도우 resize가 불가하며 화면 터치를 사용하는 등 데스크탑 브라우저와 구성이나 형태가 다르다. 또한 모바일의 화면은 데스크탑 화면보다 훨씬 작으므로 데스크탑용 웹페이지를 그대로 모바일에 출력하면 가독성이 현저히 나빠진다

 

meta tag는 브라우저 혹은 검색엔진최적화(SEO)를 위해 검색엔진에게 메타데이터를 전달하기 위해 사용된다.
viewport meta tag는 브라우저의 화면 설정과 관련된 정보를 제공한다.

 

 

meta tag에서는 px단위를 사용하며 단위 표현은 생략한다. 복수개의 프로퍼티를 사용할 때는 쉼표(,)로 구분한다.

 

일반적으로 viewport meta tag는 모바일 디바이스에서만 적용된다.

 
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 

 

위 예제는 가장 일반적인 viewport 설정이다.

가로폭을 디바이스의 가로폭에 맞추고 초기 화면 배율을 100%로 설정하는 것을 의미한다.

 

 

1.2 @media
서로 다른 미디어 타입(print, screen…)에 따라 각각의 styles을 지정하는 것을 가능하게 한다. 

다음은 일반 화면(screen)과 인쇄장치 별로 서로 다른 style을 지정하는 예이다.

<style>
  @media screen {
    * { color: red; }
  }
  @media print {
    * { color: blue; }
  }
</style>

 

일반 페이지

 

 

출력 미리보기 페이지

 

 

@media을 사용하여 미디어 별로 style을 지정하는 것을 Media Query라 한다. 

디바이스를 지정하는 것뿐만 아니라 디바이스의 크기나 비율까지 구분할 수 있다.

 

 
@media not|only mediatype and (expressions) {
  CSS-Code;
}
 

 

 
@media screen and (min-width: 480px) {
    body {
      background-color: lightgreen;
    }
  }
 

 

 

 

orientation을 제외한 모든 프로퍼티는 min/max 접두사를 사용할 수 있다.

 

일반적으로 반응형 웹 디자인은 viewport 너비(width 프로퍼티)를 기준으로 한다.
viewport의 width 프로퍼티를 이용하여 viewport 너비에 따라 반응하는 범위(breakpoint)를 지정할 수 있다.

 

 

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    /* 801px ~ */
    * { color: black; }
    /* ~ 800px */
    @media screen and (max-width: 800px) {
      * { color: blue; }
    }
    /* ~ 480px */
    @media screen and (max-width: 480px) {
      * { color: red; }
    }
  </style>
</head>
<body>
  <h1>@media practice</h1>
  <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</body>
</html>

 

 

설정한 미디어 쿼리의 width 값에 따라서 스타일의 변화를 확인 할 수 있다. 

 

 

 

다음은 화면이 세로일 때, 가로일 때를 구분하는 예제이다. 주의할 점은 데스크탑은 언제나 가로 화면이기 때문에 device-width로 스마트폰의 해상도를 지정하지 않으면 데스크탑에서도 가로화면 시 style이 적용되는 문제가 발생한다.

 
@media screen
/* 디바이스가 모바일일때(device-width 0 ~ 768px) */
and (max-device-width: 760px)
/* 가로 */
and (orientation: landscape) {
* { color: blue; }
}
 

 

 


 

2. Responsive Navigation Bar
이제까지의 내용을 바탕으로 앞서 만들어본 예제를 Responsive Web Design에 맞추어 수정해 보자.
디바이스 해상도에 따라 반응할 수 있도록 viewport meta tag와 media query를 추가한다.

 

 
/* for tablet ~ 800px */
  @media screen and (max-width:800px) {
   
  }

  /* for smartphone ~ 480px */
  @media screen and (max-width:480px) {
   
  }
 

 

 

CSS 적용 우선 순위 (Cascading Order)에 따라 나중에 선언된 스타일이 우선 적용된다. 

Non Mobile First 방식의 경우, max-width의 값이 큰 것부터 기술하여 한다.

 

일반적으로 Mobile-first 방식은 해상도가 작은 순서로, Non Mobile-first 방식은 해상도가 큰 순서로 기술한다.

 

 

2.1 Responsive Navigation Bar - Tablet
데스크탑 layout에서 화면이 작아질 때 header navigation bar가 header 영역 아래로 내려오는 현상이 발생하였다. 

이를 보완하기 위해 다음과 같이 태블릿에서의 layout을 정의한다.

1. viewport width가 800px 이하가 되면 header 영역을 2단(logo영역과 navigation bar영역)으로 구분하기 위하여 header 영역의 높이를 현재(60px)의 2배로 넓힌다.

2. logo image과 navigation bar를 centering한다.

3.  aside, section영역도 header의 height만큼 내려가야 한다.

 

 

 

 

2.2 Responsive Navigation Bar - Smartphone
태블릿 layout에서는 header 영역을 2단으로 분리하여 navigation bar는 header 하단 영역에 배치하였다. 

하지만 스마트폰의 viewport width는 가로로 나란히 정렬되어 있는 navigation bar를 모두 담기에는 너무 좁다. 

 

 

1. nav 요소 내에 클릭할 수 있는 navigation icon을 만들기 위한 html tag를 추가한다

 

- navigation 이라는 라벨로 우측 상단에 공간을 만들고 이를 checkbox 타입인 input 과 연결한다.

- input checkbox 요소의 id와 label 요소의 for를 일치시켜 연동하면 label 요소를 클릭하여도 input checkbox 요소가 클릭된다.


2. 라벨 내부의 span 태그안에 아이콘을 만들기 위해 navicon-bar을 만든다. (block 공간)

 

 

 
      <nav>
        <input class="nav-toggle" id="nav-toggle" type="checkbox">
        <label class="navicon" for="nav-toggle"><span class="navicon-bar"></span></label>
        <ul class="nav-item">
 

 

  .navicon {
  cursor: pointer;
  height: 60px;
  padding: 28px 15px;
  position: absolute;
  top: 0; right: 0;
  }

  .navicon-bar {
  display: block;
  width: 20px;
  height: 3px;
  background-color: #333;
}
 

 

 

 

 

3. 가상 요소 선택자 (Pseudo-Element Selector)를 사용하여 3줄로 만든다.

navicon영역안에 navicon-bar를 검은색 1줄 만든걸 가상선택자를 이용해서 before after 를 top 위치만 조절

 

.navicon-bar::before , .navicon-bar::after{
  background-color: #333;
  content: "";
  display: block;
  height: 100%;
  width: 100%;
  position: absolute;

}

.navicon-bar::before {
  top : -7px;
}
.navicon-bar::after {
  top : 7px;
}

 

 

4. navicon 을 클릭하면 클릭되었음을 사용자가 확인할 수 있도록 navicon 의 style을 변화시킨다.

1에서 귀찮게 checkbox 타입의 input 과 연결한 이유가 여기서 나오는데.. 클릭을 감지하기 위해서 사용했다고 볼 수 있다.

 

 

클릭시 checked 된 nav-toggle 와 형제인 navicon 을 찾고 

그 자식인 navicon-bar 를 찾아서 bg 를 상속받게 되면 기존의 색이 들어간 #333의 색이 사라지게 된다.

.nav-toggle:checked ~ .navicon > .navicon-bar {
  background: transparent;
}

 

 

그리고 나머지 2개의 before , after 에 대해서 각각 선택하고 top 0 으로 가운데로 모운 후 

rotate 를 활용하여 X 모양을 만들어준다.

 

.nav-toggle:checked ~ .navicon > .navicon-bar::before{
  top: 0;
  transform: rotate(45deg);
}
.nav-toggle:checked ~ .navicon > .navicon-bar::after{
  top: 0;
  transform: rotate(-45deg);
}

 

 

 

transition 효과를 부여하여 좀더 부드럽게 움직이도록 한다.

  transition-duration: 0.5s;

 


navigation icon을 클릭하면 의도하지 않게 이미지가 선택되는 현상이 발생할 수 있다고 한다. 

이 문제는 텍스트 선택을 차단하는 방법인 user-select: none; 프로퍼티를 지정하여 회피할 수 있다.

 

.navicon {
  cursor: pointer;
  height: 60px;
  padding: 28px 15px;
  position: absolute;
  top: 0; right: 0;

  -webkit-user-select: none;  /* Chrome all / Safari all */
  -moz-user-select: none;     /* Firefox all */
  -ms-user-select: none;      /* IE 10+ */
  user-select: none;          /* Likely future */
}

 

5. navigation icon과 checkbox input tag는 스마트폰 layout 이외의 경우, 화면에 표시되어서는 않된다. 

 

CSS 적용 우선 순위 (Cascading Order)를 고려하여 가장 마지막에 정의하는 것이 안전하다. 

일반적으로 media query를 가장 마지막에 정의하므로 media query 정의부 직전에 위치시킨다.

 

  .nav-toggle {
    display: none;
  }
  .navicon {
    display: none;
  }

 

 

tablet용 layout에서 header height를 2배로 하였으므로 mobile용 layout을 위해 다시 60px로 되돌린다.

콘텐츠 영역이 아직 tablet layout에 맞추어 아래로 내려가 있다. header 영역 바로 아래로 다시 끌어 올린다.

마지막으로 navigation icon을 클릭하면 navigation item이 표시되도록 한다.

 

    .nav-toggle:checked ~ .nav-item{
      display: block;
      width: 100%;
      background-color: #ffffff;
      box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(0, 0, 0, 0.05);
    }

    .nav-toggle:checked ~ .nav-item > li{
      display: block;
    }

 


 

3. Section & Aside & Footer

 

중간중간 다르게 한 부분이있어서 나머지 부분은 예제 코드를 따라가지 않고 대충 비슷하게 만들어봤다. 

 

 

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
  /* Reset CSS  */
  *{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
  }
  body {
      font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
      color: #58666e;
      background-color: #f0f3f4;
    }
    li {
      /* ul 기호 제거 */
      list-style: none;
    }
    a {
      /* 밑줄 제거 */
      text-decoration: none;
    }
  header{
    width: 100%;
    height: 60px;
    z-index: 100;
    background-color: #ffffff;
    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(0, 0, 0, 0.05);
    position: fixed;
    top: 0;
  }

  .logo{
    display: inline-block;
    height: 36px;
    margin: 12px 0 12px 25px;
  }

  .logo > img {
    height: 36px;
  }

  nav{
    float: right;
  }

  .nav-item li{
    display: inline-block;
  }

  .nav-item li > a {
    line-height: 60px;
    padding: 0 25px;
    color: gray;
  }

  .nav-item li > a:hover {
    color: black;
  }

  /* nav 추가 */
  .navicon {

  /* 마우스 커서 손모양 */
  cursor: pointer;

  height: 60px;
  padding: 28px 15px;
  position: absolute;
 
  /* 우측 상단 영역 */
  top: 0; right: 0;

  -webkit-user-select: none;  /* Chrome all / Safari all */
  -moz-user-select: none;     /* Firefox all */
  -ms-user-select: none;      /* IE 10+ */
  user-select: none;          /* Likely future */
 
  }

  .navicon-bar {
  display: block;
  width: 20px;
  height: 3px;
  background-color: #333;
  position: relative;
  transition-duration: 0.5s;
  }

  .navicon-bar::before , .navicon-bar::after{
    background-color: #333;
    content: "";
    display: block;
    height: 100%;
    width: 100%;
    position: absolute;
    transition-duration: 0.5s;

  }

  .navicon-bar::before {
    top : -7px;
  }
  .navicon-bar::after {
    top : 7px;
  }

  /* toggle */
  .nav-toggle:checked ~ .navicon > .navicon-bar {
    background: transparent;
  }
  .nav-toggle:checked ~ .navicon > .navicon-bar::before{
    top: 0;
    transform: rotate(45deg);
  }
  .nav-toggle:checked ~ .navicon > .navicon-bar::after{
    top: 0;
    transform: rotate(-45deg);
  }

  .nav-toggle {
    display: none;
  }
  .navicon {
    display: none;
  }


  /* content */
  #content-wrap{
    margin-top: 60px;
  }

  #content-wrap::after{
    content: "";
    display: block;
    clear: both;
  }

  aside{
    /* width: 20%;
    float: left; */
    width: 200px;
    position: fixed;
    padding-top: 25px;
    background-color: #333333;
    top : 60px;
    bottom: 0;
  }

  aside >ul{
    width: 200px;
  }

  aside >ul >li >a {
    display: block;
    color: #ffffff;
    padding: 10px 0 10px 20px;
  }

  aside > ul >li >a.active {
    background-color: #4CAF50;
  }

  aside >ul >li > a:hover:not(.active){
    background-color: #555555;
  }

  aside >h2 {
    padding: 20px 0 20px 20px;
    color: #ffffff;
  }

  section{
    /* width: 80%; */
    margin-left: 200px;
    float: right;

  }

  article{
    margin: 10px;
    padding: 25px;
    background-color: white;
  }

  article img {
    width: 300px;
    height: 210px;
    float: left;
  }

  article::after{
    content: "";
    display: block;
    clear: both;
  }

  article h2 {
    margin-bottom: 10px;
  }

  article p {
    /* 이미지 width + 원하는 패딩값 */
    padding-left: 310px;
  }


  footer{
    /* footer를 aside위에 올리기 위해 사용(부유객체) */
    position: absolute;
    height: 60px;
    width: 100%;
    padding: 0 25px;
    line-height: 60px;
    color: #8a8c8f;
    border-top: 1px solid #dee5e7;
    background-color: #f2f2f2;
  }

  /* for tablet ~ 800px */
  @media screen and (max-width:800px) {
    header {
      height: 120px;
      text-align: center;
    }
    #content-wrap{
      margin-top: 120px;
    }
    nav{
      float: none;
    }
    aside{
      top: 120px;
    }
    article img {
    float: none;
   
    }
    article p{
      padding-left: 0;
    }
   
  }

  /* for smartphone ~ 480px */
  @media screen and (max-width:480px) {
    header{
      height: 60px;
    }
    .navicon{
      display: block;
    }
    .nav-item{
      display: none;
    }

    .nav-toggle:checked ~ .nav-item{
      display: block;
      width: 100%;
      background-color: #ffffff;
      box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(0, 0, 0, 0.05);
    }

    .nav-toggle:checked ~ .nav-item > li{
      display: block;
    }

    aside{
      top: 60px;
      width: 100%;
      /* text-align: center; */
      bottom: initial;
      padding-top: 0;
      padding-bottom: 10px;
     
    }
    aside ul{
      width: 100%;
    }
   
    #content-wrap{
      margin-top: 300px;
    }
    section {
      margin-left: 0;
    }
  }

  </style>
</head>
<body>
  <div id="wrap">
    <header>
      <a class="logo" href="#">
        <img src="./img/logo.png" alt="logo">
      </a>
      <nav>
        <input class="nav-toggle" id="nav-toggle" type="checkbox">
        <label class="navicon" for="nav-toggle"><span class="navicon-bar"></span></label>
        <ul class="nav-item">
          <li><a href="#home">Home</a></li>
          <li><a href="#news">News</a></li>
          <li><a href="#Contact">Contact</a></li>
          <li><a href="#About">About</a></li>
        </ul>
      </nav>
    </header>
    <div id="content-wrap">
      <aside>
        <h2>Aside</h2>
        <ul>
          <li><a href="#seoul" class="active">서울</a></li>
          <li><a href="#daejeon">대전</a></li>
          <li><a href="#busan">부산</a></li>
          <li><a href="#gwanju">광주</a></li>
        </ul>
      </aside>
      <section>
        <article id="seoul">
          <h2>서울</h2>
          <p>대한민국의 수도인 서울은 현대적인 고층 빌딩, 첨단 기술의 지하철, 대중문화와 사찰, 고궁, 노점상이 공존하는 대도시입니다. 주목할 만한 명소로는 곡선으로 이루어진 외관과 옥상 공원을 특징으로 하는 초현대적 디자인의 컨벤션 홀인 동대문디자인플라자, 한때 7,000여 칸의 방이 자리하던 경복궁, 회화나무와 소나무 고목이 있는 조계사가 있습니다.</p>
        </article>
        <article id="daejeon">
          <h2>대전</h2>
          <img src="https://newsimg.hankookilbo.com/2016/10/21/201610211159329181_1.jpg" alt="대전이미지">
          <p>대전광역시는 대한민국의 중앙부에 있는 광역시이다. 경부고속철도, 경부선, 호남선 철도가 분기하고, 경부고속도로와 호남고속도로지선, 통영대전고속도로, 서산영덕고속도로 등 주요 고속도로가 연결되는 교통의 중심이다.</p>
        </article>
        <article id="busan">
          <h2>부산</h2>
          <img src="https://www.kgnews.co.kr/data/photos/20210729/art_16268494042003_59ee6d.jpg" alt="부산 이미지">
          <p>부산광역시는 한반도 남동부에 위치한 광역시이다. 대한민국의 제2의 도시이자 최대의 해양 도시이며, 부산항을 중심으로 해상 무역과 물류 산업이 발달하였다. 일본과는 대한해협을 사이에 두고 마주하고 있다. 시청 소재지는 연제구 연산동이며, 행정구역은 15구 1군이다</p>
        </article>
        <article id="gwanju">
          <h2>광주</h2>
          <p>광주광역시는 대한민국의 남서부에 있는 광역시이다. 남동쪽으로 전라남도 화순군, 북동쪽으로 전라남도 담양군, 서쪽으로 전라남도 함평군, 서남쪽으로 전라남도 나주시, 북쪽으로 전라남도 장성군과 접한다. 시청 소재지는 서구 치평동이고, 행정 구역은 5구 95동이다</p>
        </article>
      </section>
    </div>
    <!-- end of content wrap -->
    <footer>© Copyright 2024 thank you for poiemaweb</footer>

    <!-- end of wrap -->
  </div>
 
</body>
</html>

 

 


footer 부분에 다음과같이 검은색 aside 부분이 튀어나가는걸 볼 수 가 있다.. 

footer 의 border-top 을 지정하게 되면 같이 생기는거같다. 

border-top: 1px solid #dee5e7;

 

border 를 제거하던지

aside 의 bottom 을 1로 조절해서 일단 조치할 수 있을것 같다.

 

 

 

 


단순히 코드를 복붙하는게 아니라

코드 한줄씩 어떤 변화를 주는지 확인해보는게 좋은거같다.

 

js 나 외부 아이콘을 쓰지않고 css 만으로 네비게이션 아이콘 모양을 구현과 input checkbox 와 label 을 활용한 기능 구현이 굉장히 인상깊었다.