[TIL] 브라우저 동작 방식, 반응형 웹, 미디어쿼리, 반응형 웹 구현해보기

ㅜㅜ·2022년 11월 21일
1

Today I learn

목록 보기
56/77
post-thumbnail

브라우저

브라우저는 웹 서버에서 양방향으로 통신을 하며 HTML 문서 및 그림, 멀티미디어 등의 컨텐츠를 열람할 수 있게 해주는 GUI 기반 소프트웨어 프로그램이다.
브라우저는 페이지를 다운로드 하기 위해 응용계층의 대표적인 프로토콜인 HTTP를 통해서 송수신을 하게 된다.



브라우저의 동작 방식

브라우저는 사용자가 선택한 자원을 서버에 요청하고, 서버 응답을 받아 브라우저에 띄우는 방식으로 동작한다.

좀 더 자세히 웹 브라우저가 웹 사이트에 접속해 웹 페이지를 가져오는 과정을 살펴보면 아래와 같다.

사용자가 웹 브라우저를 통해 찾고 싶은 웹 페이지 URL 주소 입력
⇒ DNS 서버에서 사용자가 입력한 URL 주소 중 도메인 네임을 검색하고, 해당 도메인 네임에 해당하는 IP주소를 찾아 사용자가 입력한 URL 정보와 함께 전달.
⇒ HTTP 프로토콜 사용해 HTTP 요청 메시지를 생성하고, TCP 프로토콜을 사용해 인터넷을 거쳐 해당 IP 컴퓨터로 전송됨. (이 요청 메시지는 HTTP 프로토콜을 통해 웹 페이지 URL 정보로 다시 변환된다고 함.)
⇒ 웹 서버는 변환된 정보에 해당하는 데이터 검색, 찾아낸 뒤 HTTP 프로토콜 통해 HTTP 응답 메시지 생성.
⇒ 응답 메시지는 TCP 프로토콜 이용해 인터넷 거쳐 사용자 컴퓨터로 전송됨.
⇒ 사용자 컴퓨터에 도착된 HTTP 응답 메시지는 HTTP 프로토콜 사용해 웹 페이지 데이터로 변환되고, 변환된 데이터는 웹 브라우저 상에 출력되어 사용자가 볼 수 있게 됨.



브라우저 구조

  • 사용자 인터페이스(UI) : 사용자들과 가장 밀접하게 맞닿아 있는 부분
  • 브라우저 엔진(Browser Engine) : 사용자 인터페이스와 렌더링 엔진 사이 동작 제어. 주로 HTML 문서와 기타 자원 웹페이지를 사용자 장치에 시각 표현으로 변환. 문서 객체 모델(DOM) 자료 구조 구현.
  • 렌더링 엔진(Rendering Engine) : 요청한 콘텐츠 화면에 출력. HTML, CSS를 파싱해 최종적으로 화면에 그려줌.(XML, 이미지 표시도 가능.)
  • 통신(networking) : HTTP 요청과 같은 네트워크 호출에 사용.
  • 자바스크립트 해석기(JavaScript Interpreter) : 코드를 위에서 아래로 한 줄씩 읽을 때 사용. 대체적으로 웹 브라우저에서 사용되며, 브라우저마다 전용 엔진 탑재되어 있음.
  • UI 백엔드 : 렌더링 엔진이 분석한 Render Tree를 브라우저에 그리는 역할 담당.
  • 자료 저장소 : 모든 자원들을 하드 디스크에 저장할 필요가 있기 때문에 존재. 영구적 저장소인 로컬 스토리지와 임시적 저장소인 세션 스토리지가 따로 있어서 데이터의 지속성 구분 가능.


브라우저 랜더링

: HTML, CSS, Js 등 개발자가 작성한 문서가 브라우저에 출력되는 과정 의미. (브라우저는 기본적으로 렌더링을 수행하는 렌더링 엔진을 가지고 있음.)

  1. 사용자가 브라우저를 통해 웹 사이트에 접속함.
  2. 브라우저가 서버로부터 HTML, CSS, JavaScript와 같은 웹사이트에 필요한 리소스를 다운 받음.
  3. 렌더링 엔진이 전달받은 HTML 문서를 파싱(parsing)해 DOM(Document Object Model, 문서 객체 모델) 트리를 만듦.
  4. 다운 받은 외부 CSS 파일과 함께 포함된 스타일 요소를 파싱(parsing)해 CSSOM(CSS Object Model, CSS 객체 모델) 트리를 만듦.
  5. 만든 DOM 트리와 CSSOM 트리를 결합해 Render 트리를 구축.
  6. 레이아웃 과정을 통해 각 요소를 어디에 배치할 지 결정. (여기서 레이아웃 과정은 렌더트리를 기반으로 HTML 요소의 레이아웃(위치, 크기 등)을 계산하여 브라우저 화면 어디에 배치할 지 결정하는 과정.)
  7. 레이아웃 과정이 끝나면 UI 백엔드에서 Render 트리를 화면에 그림.(paint 과정)


리플로우 & 리페인트

어떤 웹 인터랙션(ex: 화면 크기를 늘리거나 줄이고, 다른 사이트로 이동하는 등 화면에 요소가 추가되거나 삭제, 아예 다른 요소들 불러와야 하는 상황 등)으로 인해

  • 렌더링 과정의 레이아웃을 반복해 수행하는 것을 리플로우,
  • 페인트 과정을 반복해 수행하는 것을 리페인트라고 함.

Ex : DOM 조작은 리플로우, 리페인트가 일어나는 대표적인 예시

const div = document.createElement('div');
document.body.append(div);
div.textContent = 'hello world';

JavaScript 조작을 통해 변경이 일어나면, DOM 트리를 다시 구성하는 것을 시작해 CSSOM과 합쳐 새 렌더 트리를 생성. 그리고 레이아웃과 페인트 과정을 또 다시 거쳐 화면에 보여주게 된다.



리플로우 & 리페인팅 최적화

리플로우 하는 과정은 배치를 위한 연산을 해야 해 CPU를 많이 차지하고,
리페인트는 페인트를 다시 하는 것이라 픽셀을 다시 화면에 찍어 그려야 하기 때문에 GPU를 많이 차지.
그렇기 때문에 프레임 드랍(Frame Drop) 현상과 직접적인 연관이 있습니다.

프레임 드랍
: 초당 60프레임으로 유지시키던 프레임의 수가 브라우저의 과부하로 인해 줄어드는 현상.
줄어들어 없어진 프레임은 렌더링 엔진이 인식할 수 없고, 유저는 해당 현상을 "화면이 멈춘다, 버벅인다" 라고 느끼게 됨. 프레임 드랍 현상이 생기면 유저 경험(UX)에 좋지 않기 때문에, 최적화를 고려해야함.

최적화 방법

  • 불필요한 레이아웃 과정 줄이기

  • CSS 속성 중 레이아웃 발생시키는 속성들 피하기
    (position, width, height, top, right, bottom 등 대개 위치, 너비와 관련된 속성이 많음)
    : 리플로우가 발생하는 속성보다는 리페인트만 발생하는 속성을 사용해주는 게 좋음. (페인트 과정은 유저에게 화면 보여주기 위한 필수적 과정이기 때문)

  • 영향 주는 노드 줄이기
    : JavaScript + CSS를 조합한 애니메이션이 많거나, 레이아웃 변화가 많은 요소의 경우 position을 absolute 또는 fixed를 사용해주면 영향을 받는 주변 노드들을 줄여줄 수 있음. fixed와 같이 영향을 받는 노드가 전혀 없는 경우 리플로우 과정이 필요 없기 때문에 리페인트 연산 비용만 들일 수 있다.





반응형 웹

여러 전자기기 발전으로 데스크탑 뿐 아니라 태블릿, 스마트폰 등 전자기기에서 웹에 접속할 수 있게 되었는데, 전자기기들의 화면 크기가 다양해 여러 버전 웹 페이지를 만들어야 하는 경우가 발생. 이 불편함 해결하기 위해 반응형 웹페이지 탄생.

what is 반응형 웹 ?
: 여러 장치의 다양한 특성에 대응하는 하나의 웹 문서 또는 사이트로 브라우저의 크기(스크린의 크기, 디바이스 종류)에 실시간으로 반응하여 크기에 따라 레이아웃이 변하는 웹 사이트.

mobile first(모바일 퍼스트)?
Luke Wroblewski가 주장한 철학이자 전략으로, 사용자경험(UX)을 디자인할 때 모바일일 경우 최우선으로 초점 맞춰 디자인하는 것.

반응형 웹 특징

  • 하나의 URL을 기반으로 화면이 바뀐다.

    (특정 장치에 최적화된 페이지로 연결되는 별도의URL이 존재한다면 반응형 웹이라고 하지 않음 ex : 네이버 모바일 버전은 주소 앞에 m.naver.com처럼 ‘m’을 붙여서 구분된 별도의 URL이 존재함)

  • 장점

    • 효율적 유지보수 : 하나의 콘텐츠에 오직 하나의 HTML만 있기 때문에
    • 검색엔진최적화 유리 : PC, 모바일용 URL 동일. 검색 포털 등 광고를 통한 사용자 접속 효율적 관리 가능.
  • 단점

    • 사이트 속도 저하 : 읽어들여야 할 소스가 많아 불필요하게 많은 데이터 소비하므로 사이트 속도 저하됨.
    • 웹 브라우저 호환성 문제 : 디자인 자유도 떨어지고, 100% 맞춤 디자인 어렵다.




미디어 쿼리

미디어 쿼리 적용법
CSS파일 HTML파일에 적용시키던 것처럼 head 태그 내 link 태그 위치시킴.
미디어 속성을 사용해 조건을 지정해서 미디어 속성 내 해당 조건 만족할 때에만 해당 css파일 불러오게 됨.

<link href="css파일이름.css" media="screen and (min-width: 400px) and (max-width: 1000px)" rel="stylesheet">
<style type="text/css" media="screen and (min-width: 400px) and (max-width: 1000px)">
/* 이 안에 css를 작성할 수도 있음. */
</style>

위의 두 방법 외에도 CSS파일 혹은 태그 안에서 직접 미디어 쿼리를 작성할 수도 있음.


미디어 쿼리 구문

  • 미디어 타입
    : 코드가 어떤 미디어를 위한 것인지 브라우저에 알려주기 위한 것 (all: 모든 미디어 타입, print : 프린터, screen : 컴퓨터 화면, speech: 스크린 리더 등의 타입이 존재)
  • 조건(너비 및 높이)
    : 지정한 창의 너비나 높이 기준 만족할 경우에 스타일 적용되고, 만족되지 않으면 적용되지 않도록 한다.
    가장 많이 사용하는 기능은 뷰포트 너비이며 max-width, min-width,width 등의 미디어 기능 활용해 뷰포트가 특정 너비 이상 or 이하인 경우 CSS 적용 가능.
@media 미디어 타입 (조건(너비 및 높이)) {
    (CSS 입력하는 부분)
}

--예제
@media screen (max-width: 400px) {
    body {
			color: red;
		}
}
  • 방향성
    : 세로 모드인지, 가로 모드인지 검사하여 CSS 스타일 줄 수 있음. orientation으로 검사할 수 있다.
//가로모드인 경우 

@media(orientation: landscape) {
			body {
					color: rebeccapurple;
				}
}

미디어 쿼리에 여러 조건 붙이기

  • 논리곱(and) 미디어 쿼리
//뷰포트 너비가 최소 400픽셀 이상이고 장치가 가로모드인 경우에 스타일 적용 

@media screen and (min-width: 400px) and (orientation: landscape) {
    body {
        color: blue;
    }
}
  • 논리합(or) 미디어 쿼리
//뷰포트 너비가 최소 400픽셀 이상이고 장치가 가로모드인 경우에 스타일 적용 

@media screen and (min-width: 400px) and (orientation: landscape) {
    body {
        color: blue;
    }
}
  • 부정(not) 미디어 쿼리
//방향이 세로인 경우에만 스타일 적용 
//원래 의미와 반대로 미디어 쿼리를 적용시킨다.

@media not all and (orientation: landscape) {
    body {
        color: blue;
    }
}




🫠 과제 : 반응형 웹 구현

오늘 과제는 미디어 쿼리로 반응형 웹을 구현하는 것이었고, 따로 테스트 케이스가 존재하지 않았다. 나는 고민하다가 내가 익숙하게 사용하는 페이지 중 29CM 페이지를 클론 코딩(?) 해봐야겠다고 생각했다.

구현하고자 한 기능

  1. 특정 너비가 되면 (아마 모바일 정도의 크기인듯) 가로로 정렬되어 있던 컴포넌트들이 세로로 정렬되는 것

  2. 마찬가지로 특정 너비가 되면 원래 헤더 위치에 존재하던 메뉴바가 사라지는 것

구현 결과

컴포넌트 구성

주어진 시간이 넉넉하지는 않았고, 반응형을 구현하는 것에 목적이 있었기 때문에 간단하게 만들 수 있는 부분은 간단하게 만들기도 타협했다.

컴포넌트 내부에 들어가는 광고문구나 사진들을 내가 일일이 만들기에는 시간이 너무 걸리므로... 이미지 파일들로 대체했다.

이미지 파일을 이렇게 많이 받아서 사용해야 했던 건 처음이었던 것 같은데, 처음에 맨 왼쪽줄의 배너 컴포넌트를 만들 때는 Content라는 styled Component를 하나하나 만들어서 사용했지만, 그 뒤로 중간줄과 맨 오른쪽 줄의 광고 피드 컴포넌트를 만들 때는 아예 dummy data를 만들어서 map을 통해 구현하기로 했다.

지금 보니 Content 컴포넌트도 그냥 map 함수를 돌리는 게 나았을지도 모르겠다는 생각이 든다...

//Render.js 

import styled from "styled-components";
import "../App.css";
import { data } from "../data/data.js";
import {
  banner1,
  banner2,
  banner3,
  banner4,
  banner5,
  rightbanner1,
  rightbanner2,
} from "../img/index.js";
//이 사진 파일들은 img라는 폴더 속에 존재하는데, 
//img 폴더 내부의 index.js 파일 속에서 해당 이름으로 모두 import & export 되어서 변수처럼 사용 가능하다 


const FeedContainer = styled.div`
  display: flex;
  flex-direction: column;
  @media only screen and (min-width: 600px) {
    flex-direction: row;
  }
`;

const FeedContentLeft = styled.ul`
  margin-right: 30px;
`;

const FeedContentsRight = styled.ul`
  display: flex;
  flex-direction: column;
  @media only screen and (min-width: 600px) {
    flex-wrap: wrap;
    max-height: 3000px;
    justify-content: center;
    align-items: center;
  }
`;

const Content = styled.li`
  max-height: 860px;
  height: 600px;
  width: 500px;
  object-fit: fill;

  img {
    height: 600px;
    width: 500px;
  }
`;

const RightContents = styled.img`
  display: flex;
  flex-direction: column;
  justify-content: space-evenly;
  align-items: center;
  max-width: 500px;
  height: 600px;
  @media screen and (min-width: 500) {
    margin: 10px;
    margin-left: 30px;
  }
`;


const Render = () => {
  return (
    <>
      <FeedContainer>
        <div className="feed-left">
          <FeedContentLeft>
            <Content>
              <img src={banner1}></img>
            </Content>
            <Content>
              <img src={banner2}></img>
            </Content>
            <Content>
              <img src={banner3}></img>
            </Content>
            <Content>
              <img src={banner4}></img>
            </Content>
            <Content>
              <img src={banner5}></img>
            </Content>
          </FeedContentLeft>
        </div>
        <div className="feed-right">
          <FeedContentsRight>
            {data.map((el) => {
              return <RightContents key={el.id} src={el.img}></RightContents>;
            })}
          </FeedContentsRight>
        </div>
      </FeedContainer>
    </>
  );
};

export default Render;

미디어 쿼리 적용 부분

//Render.js 파일 속 컴포넌트들 중 미디어 쿼리 적용된 것들 

//FeedContainer 컴포넌트 
const FeedContainer = styled.div`
  display: flex;
  flex-direction: column;
  @media only screen and (min-width: 600px) {
    flex-direction: row;
  }
`;

//FeedContentsRight 컴포넌트 
const FeedContentsRight = styled.ul`
  display: flex;
  flex-direction: column;
  @media only screen and (min-width: 600px) {
    flex-wrap: wrap;
    max-height: 3000px;
    justify-content: center;
    align-items: center;
  }
`;

//Header.js 파일 속에서 미디어 쿼리 적용된 부분 
const HeaderMenuContainer = styled.div`
  text-align: center;
  display: flex;
  flex-direction: row;
  justify-content: space-around;
  align-items: center;
  max-width: 1000px;
  margin-bottom: 50px;
  margin-left: 10px;
  @media screen and (max-width: 800px) {
    display: none;
  }
`;
  • 29cm 홈페이지에서 콘솔 엘리먼트들을 살펴보니 대부분 피드 속 콘텐츠들은 width 600px 정도를 기준으로 column으로 변화하는 것 같았다. 그래서 변화할 media 속성의 조건으로 min-width : 600px을 넣어서 flex-direction을 row로 주었다.
    FeedContainer는 width가 600px일때까지만 row가 될 것이고,
    FeedContentsRight은 width가 600px일 때까지만 flex-wrap이 작동하게 된다.

  • Header 컴포넌트 속에 있는 HeaderMenuContainer에도 미디어 쿼리를 적용해주었는데, 29cm 웹 페이지에서 width가 어느정도 작아지면 메뉴가 헤더에서 사라졌기 때문이다. 그래서 display: none을 max-width: 800px 조건에 걸어주었다. (600이 아니라 800인 이유는... 메뉴를 너무 대충 만들어서 조금만 width가 줄어도 메뉴들끼리 겹쳐졌기 때문...)

  • 사실 원래는 FeedContainer가 flex-direction이 column이 되면 RightContents들의 크기를 배너의 크기와 맞춰주고 싶었는데, 미디어 쿼리로는 적용 불가능한 부분이었던 것 같다. (혹시 아니라면 댓글로 정정 부탁드립니다..!) 그래서 그냥 max-width를 사용해서 비슷하게 구현했다. max 크기가 아닐 때 세세하게 width를 조정할 수 없는 것이 아쉽다.

아쉬운 부분

  • 아무래도 사진들만 가지고 피드 속 컨텐츠들을 구현하다 보니 비율이 맞지 않다거나 하는 아쉬움이 있다.(사실 너무 구림^^..)

  • 게다가 많은 사진들을 이용하려니 import, export도 엄청나게 해야해서 번거로웠다. 물론 img 폴더 속에서 index.js 파일을 만들어 변수처럼 만들어 import, export하는 방법도 있다는 걸 알게 되어서 좋긴 하지만 다음엔 좀 더 처음부터 구조를 잘 짜서 덜 번거롭게 만들어보고 싶다.

  • 컴포넌트 이름들이 대체적으로 좀... 조잡하고, 통일성 없다. 막 만들다가 중간에 필요하면 고치고, 이런식으로 만들어서 그런듯... 다음에는 손으로 치기 전에 머리로 생각을 하고 코드를 치자.

profile
다시 일어나는 중

0개의 댓글