본문 바로가기

개발 스터디/css

19. 반응형 레이아웃

화면 가로폭 줄이거나 모바일로 접근 시 사용 불편 > 해결 방법 반응형 웹디자인

화면 해상도에 따라 가로폭/배치 변경하여 가독성 높임

 

1.1 viewport meta tag

viewport란 웹페이지의 가시 영역

디바이스의 특성과 화면 크기 고려해 최적화된 웹페이지 제공해야 

viewport meta tag는 브라우저의 화면 설정과 관련된 정보 제공

출처 : 포이마웹

meta tag에서는 px 단위 사용, 단위 표현 생략 

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

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

가로폭을 디바이스의 가로폭에 맞추고 초기화면 배율을 100으로 설정

 

1.2 @media

서로 다른 미디어타입(print, screen ...) 따라 각각 style 지정 가능

일반화면과 인쇄장치 별 다른 스타일 지정

 

@media(조건){

     스타일(일반적인 css 코드)

}

조건 부분이 만족될 때 스타일 적용, 아닐 때는 무시됨

 

<!DOCTYPE html>
<html>
    <head>
        <style>
            @media (max-width: 800px) {
                 .small-tomato {
                 background-color: tomato;
                      }
                  }
        </style>
    </head>
    <body>
        <div class="small-tomato">좁은 화면에서는 배경색이 토마토 색이 됩니다.</div>
    </body>
</html>

화면이 800px 이하일 때만 .small-tomato 클래스의 배경이 토마토색이됨

반대로 800px 이상일떄 > min-width: 800px 로 지정하면됨

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
    @media screen{
        *{color:red;}
    }
    @media print{
        *{color:blue;}
    }

반응형 웹디자인에 사용되는 핵심 기술 - @media

@media 사용해서 미디어 별로 style 지정하는 것 Media Query라고 함

디바이스 크기/비율까지 구분 가능

- Media Query의 문법

 

@media not|only mediatype and (expressions:조건문){ CSS-Code; : 실행문 }

 

- mediatype

all : 모든 장치 (기본값)

print : 인쇄

screen : 화면

 

-논리연산자

=

and : 'AND' 연산 수행해 앞과 뒤 조건 모두 만족해야한다

not : 해당 장치가 아닐때만 참 반환

only : 미디어 쿼리를 지원하지 않는 브라우저가 주어진 스타일을적용해버리는 것 방지

 , : 하나의 쿼리만 참이어도 스타일 적

 

요소위에 호버할 수 있으면 스타일을 적용하는 예

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

- Media Query의 표현식에서 사용할 수 있는 프로퍼티

 

width viewport 너비

height viewport 높이

device-width 디바이스의 물리적 너비

device-height 디바이스의 물리적 높이

orientation 디바이스의 방향 (가로:landscape, 세로:protrait)

device-aspect-ratio 디바이스의 물리적 width/height 비율

color 디바이스에서 표현 가능한 최대 색상 비트수

monochrome 흑백 디바이스의 픽셀 당 비트수

resolution 디바이스 해상도

 

orientation을 제외한 모든 프로퍼티는 min/max 접두사 사용 가능

https://www.w3.org/TR/mediaqueries-3/#media1

일반적으로 반응형 웹디자인은 viewport 너비를 기준으로 함

viewport의 width 프로퍼티 이용하여 viewport 너비에 따라 반응하는 범위(breakpoint) 지정 가능

 

<style>
    /*==========  Mobile First Method  ==========*/
/* All Device */

/* Custom, iPhone Retina : 320px ~ */
@media only screen and (min-width : 320px) {

}
/* Extra Small Devices, Phones : 480px ~ */
@media only screen and (min-width : 480px) {

}
/* Small Devices, Tablets : 768px ~ */
@media only screen and (min-width : 768px) {

}
/* Medium Devices, Desktops : 992px ~ */
@media only screen and (min-width : 992px) {

}
/* Large Devices, Wide Screens : 1200px ~ */
@media only screen and (min-width : 1200px) {

}

/*==========  Non-Mobile First Method  ==========*/
/* All Device */

/* Large Devices, Wide Screens : ~ 1200px */
@media only screen and (max-width : 1200px) {

}
/* Medium Devices, Desktops : ~ 992px */
@media only screen and (max-width : 992px) {

}
/* Small Devices, Tablets : ~ 768px */
@media only screen and (max-width : 768px) {

}
/* Extra Small Devices, Phones : ~ 480px */
@media only screen and (max-width : 480px) {

}
/* Custom, iPhone Retina : ~ 320px */
@media only screen and (max-width : 320px) {

}
</style>
<!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>

~480px : 빨강

481~800px : 파랑

801px ~ : 검정

 

화면이 세로/가로 구분하는 예제

device-width(기기의 물리적인 너비) 지정해야 (스마트폰/데스크탑 구분하기 위해)

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
@media screen
and (max-device-width:760px) /* 디바이스가 모바일일때*/
and(orientation:landscape){ /*가로*/
    *{color:blue;}
}

  </style>
  </head>
  <body>
    <h1>@media practice: orientation</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>

2. Responsive Navigation Bar

앞선 예제 responsive web design 맞추어 수정

 /*미디어 쿼리 - 스타일 코드 가장 하단에 위치*/

            /* for Desktop: 801px ~ */

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

            }

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

            }

스마트폰, 태블릿, 데스크탑 3단계로 구분하여 breakpoint 정의

Non Mobile First Method로 정의했기 때문에 Media Query로 정의하지 않은 스타일은 데스크탑 그룹을 위한 코드가 됨

 

CSS 적용 우선 순위에 따라 나중에 선언된 스타일이 우선 적용됨

만일 스마트폰용 스타일을 태블릿보다 먼저 기술 > 최종적으로 태블릿용 스타일이 적용됨

따라서 Non Mobile First 방식의 경우 > max-width값 큰 것부터 기술

 

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

 

2.1 Responsive Navigation Bar - Tablet

 

데스크탑에서 화면 작아질 때 네비게이션 바가 헤더 아래로 내려오는 현상 발생 > 이를 보완하기 위해 정의

viewport width가 800px 이하 > header 영역을 2단으로 구분하기 위해 header영역 높이 2배로 넓힘

그리고 logo image와 navigation bar centering

 

  @media screen and(max-width:800px){
                header{
                    height:120px;
                    text-align: center;
                }
            }

이때 aside, section 영역도 header의 height만큼 내려가야

 @media screen and(max-width:800px){
                header{
                    height:120px;
                    text-align: center;
                }
                #wrap {
                    /* margin-top = header height */
                    margin-top: 120px;
                }
                aside{
                    top:120px;
                }
            }
@media screen and(max-width:800px){
                header{
                    height:120px;
                    text-align: center;
                }
                nav{
                    float:none;
                }
                #wrap {
                    /* margin-top = header height */
                    margin-top: 120px;
                }
                aside{
                    top:120px;
                }
            }

2.2 스마트폰

nav 요소 내 클릭할 수 있는 navigation icon 만들기 위한 태그 추가

label tag의 for 프로퍼티 값과 input tag의 id 프로퍼티값 일치해야

<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-items">

* label for="" 와 input id="" 은 그룹을 형성

label 태그는 input 태그를 도와주는 역할

input 태그가 디자인하기 힘들 때 label 태그로 연결해서 쉽게 디자인하거나 클릭 편의성을 높일 수 있음

 

위의 코드는 checkbox의 기본 외관을 사용하지 않고 커스텀 navicon을 사용하기 위한 방법

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

header의 우측의 절대 위치에 배치되어야 > absolute

absolute는 부모 또는 가장 가까이 있는 조상 요소(static 제외) 기준으로 좌표만큼 이동

이 경우, navicon은 body 기준으로 위치하면됨 > 별도처리X 

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

label tag내의 span 태그의 style정의

 

가상요소 선택자 사용하여 navicon 내부 막대 앞뒤 공간에 내부 막대 추가

위 태그 추가시 내부 막대 1개가 표기

.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;
}

 내부막대 앞뒤 공간에 내부막대 추가

content="" 는 ::before, ::after와 함께 쓰이며 가상요소가 만들어지는 필수 속성

::before 선택한 요소 앞에 가상 콘텐츠 삽입

:: after 선택한 요소 뒤에 가상 콘텐츠 삽입

 

input checkbox tag의 가상 클래스 선택자 checked 이용하여 클릭되었을때(input:checked)와 그렇지 않을때 구분 가능

.nav-toggle:checked ~ .navicon>.navicon-bar{ /*중간에 위치한 막대 없애기*/
    /*A~B는 A태그 옆의 B태그만 선택*/
    background: transparent;
}
/*상하 막대 45도 회전 > X 모양 만들기*/
.nav-toggle:checked ~ .navicon>.navicon-bar::before{
    transform: rotate(45deg);
    top:0;
}
.nav-toggle:checked ~ .navicon>.navicon-bar::after{
    transform: rotate(-45deg);
    top:0;
}
.navicon-bar::before,
.navicon-bar::after{
    background-color: #333;
    content:"";
    display: block;
    height: 100%;
    width: 100%;
    position: absolute;
    transition: all .2s ease-out; /*트랜지션으로 부드럽게*/
}

navigation icon 클릭 시 의도하지 않게 이미지(nudge) 선택되는 현상 > user-select:none;(텍스트 선택을 차단)으로 회피

이는 css에 기본 포함X > 벤더 프리픽스로 사용

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

    -webkit-user-select: none;/* Chrome all / Safari all */
}

navicon과 checkbox 는 스마트폰 레이아웃에서만 표시되어야

> display:none;으로 표시되지 않게 (해당 공간조차 점유X)

*visibility:hidden은 공간은 남아있고 표시만 안됨

 

css 적용 순위를 고려개 가장 마지막에 정의하는 것이 안전

일반적으로 media query 가장 마지막에 정의 > media 정의부 직전에 위치시킴

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

tablet용 레이아웃에서 header height 2배하였으므로 moblie에서 다시 60px로 되돌려야

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

navigation bar가 초기 상태에서 비표시, navicon은 표시되어야 

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

콘텐츠 영역이 tablet layout에 맞추어져 아래로 내려가있음

header 영역 바로 아래로 다시 끌어올림

#wrap{
                    /* margin-top = header height */
                    margin-top: 60px;
                }
                aside{
                    top:60px;
                }

*근데 왜 wrap 영역의 margin top으로 영역 확보? 

.navi-toggle:checked ~ .nav-items{
                    display: block;
                    width:100px;
                    background-color: #fff;
                    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(0, 0, 0, 0.05);;
                }
               
                .nav-items>li{
                    display: block;
                }
                .nav-items>li>a{
                    line-height: 50px;
                }

navigation icon 클릭하면 navigation item 표시되게

 

전체코드 (모바일 - 네비게이션 바 클릭시 메뉴 나오는 것 구현이 안됨)

<!DOCTYPE html>
<html>
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
       
        <style>
           
            *{
                margin: 0;
                padding: 0;
                box-sizing: border-box;
            }
            body{
                font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
                color: #58666e;
                background-color: #f0f3f4;
                -webkit-font-smoothing: antialiased;
                -webkit-text-size-adjus: 100%;  /* iphone font size 변경 방지 */
            }
            li{
                list-style: none;
            }
            a{
                text-decoration: none;
            }
            h1, h2, h3, h4, h5, h6, p {
              margin: 10px 5px;
             }
            h1 { font-size: 1.8em; }
            #wrap {
                width: 100%;
                /* margin-top = header height */
                margin-top: 60px;
             }  
            header{
                width:100%;
                height:60px;
                z-index: 2000;
                background-color: #fff;
                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-items{
    margin-right:20px;
}
.nav-items > li {
  display: inline-block;
}
.nav-items > li > a {
    line-height: 60px;
    padding: 0 30px;
    color:rgba(0,0,0,0.4);
}

.nav-items > li > a:hover{
    color:rgba(0,0,0,0.8);
}
.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{
    background-color: #333;
    display: block;
    position: relative;

    transition: background-color .2s ease-out;
    width:20px;
    height:3px;
}

.navicon-bar::before,
.navicon-bar::after{
    background-color: #333;
    content:"";
    display: block;
    height: 100%;
    width: 100%;
    position: absolute;
    transition: all .2s ease-out; /*트랜지션으로 부드럽게*/
   
}
.navicon-bar::before{ /*위에 작대기 삽입*/
    top:-7px;
}
.navicon-bar::after{ /*아래에 작대기 삽입*/
    top:7px;
}

.nav-toggle:checked ~ .navicon > .navicon-bar{ /*중간에 위치한 막대 없애기*/
    /*A~B는 A태그 옆의 모든 B태그만 선택*/
    background: transparent;
}
/*상하 막대 45도 회전 > X 모양 만들기*/
.nav-toggle:checked ~ .navicon > .navicon-bar::before{
    transform: rotate(45deg);
    top:0;
}
.nav-toggle:checked ~ .navicon > .navicon-bar::after{
    transform: rotate(-45deg);
    top:0;
}
#content-wrap:after{
    content:"";
    display: block;
    clear:both;
}

aside{
    position:fixed;
    top:60px;
    bottom: 0;

    width: 200px;
    padding-top: 25px;
    background-color: #333;
}
aside > ul{
    width: 200px;
}
aside > ul > li > a {
    display: block;
    color: #fff;
    padding: 10px 0 10px 20px;
}
aside > ul > li > a.active{
    background-color: #4caf50;
}
aside > ul > li > a:hover:not(.active){
    background-color: #555;
}
aside > h1{
    padding:20px 0 20px 20px;
    color:#fff;
}
section{
    float: right;
    margin-left: 200px;
}

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


footer{
    position: absolute;
    left:0;
    bottom: 0;
    height: 60px;
    width: 100%;
    padding: 0 25px;
    line-height: 60px;
    color: #8a8c8f;
    border-top:1px solid #dee5e7;
    background-color: #f2f2f2;
}



.nav-toggle{
    display: none;
}
.navicon{
    display: none;
}
 /*미디어 쿼리*/

            /* for Desktop: 801px ~ */

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

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

                #wrap{
                    /* margin-top = header height */
                    margin-top: 60px;
                }
                aside{
                    top:60px;
                }
                /*view navigation item*/
                .navi-toggle:checked ~ .nav-items {
                    display: block;
                    width:100%;
                    background-color: #fff;
                    box-shadow: 0 2px 2px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(0, 0, 0, 0.05);
                }
               
                .nav-items > li {
                    display: block;
                }
                .nav-items > li > a {
                    line-height: 50px;
                }
            }


    </style>
    </head>
    <body>
        <div id="wrap">
            <header>
                <a class="logo" href="#home">
                    <img src="https://poiemaweb.com/img/logo.png" >
                </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-items">
                        <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>

        <div id="content-wrap">
            <aside>
                <h1>Aside</h1>
                <ul>
                    <li><a href="#" class="active">London</a></li>
                    <li><a href="#" >Paris</a></li>
                    <li><a href="#" >Tokyo</a></li>
                    <li><a href="#" >Newyork</a></li>
                </ul>
            </aside>
            <section>
                <article id="london">
                    <h1>London</h1>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing 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>
                </article>
                <article id="paris">
                    <h1>Paris</h1>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing 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>
                </article>
                <article id="tokyo">
                    <h1>Tokyo</h1>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing 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>
                </article>
                <article id="newyork">
                    <h1>Newyork</h1>
                    <p>Lorem ipsum dolor sit amet, consectetur adipiscing 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>
                </article>
            </section>
        </div>

        <footer>© Copyright 2016 ungmo2</footer>
    </body>
</html>

'개발 스터디 > css' 카테고리의 다른 글

18. 레이아웃  (0) 2023.08.04
17. 웹디자인 타이포그래피  (0) 2023.08.03
16. 트랜스폼  (0) 2023.08.03
15. 애니메이션  (0) 2023.08.03
14. 트랜지션  (0) 2023.07.30