React, 모바일 웹 만들기 (모바일 화면 크기, 헤더, 네비게이션 바, 기타 유용한 정보)

Jihu Kim·2023년 11월 5일
7

React

목록 보기
3/4

React로 모바일 환경에 맞는 웹 만들기

우리는 프로젝트를 진행하면서 고객계정으로 접속했을 때는 모바일 환경에 맞는 웹을 보여주고, 판매자 계정으로 접속했을 때는 일반 웹 환경으로 보여주도록 해야했다. 프로젝트를 진행하면서 구현과정에서 알게되었던 것들에 대해서 해당 블로그에 기록해두겠다. 우리와 같은 구현을 해야하는 분들에게 조금이나마 도움이 되길 바란다.

그 전에 React가 무엇인지에 대해서 간단하게 집고 넘어가겠다.

React란?

React는 페이스북이 개발하고 관리하는 자바스크립트 라이브러리로, 사용자 인터페이스를 구축하는데 사용됩니다. React는 웹 애플리케이션의 복잡성을 관리하기 위해 "컴포넌트"라는 개념을 도입했습니다. 컴포넌트는 재사용 가능한 코드 조각으로, 각각의 컴포넌트가 애플리케이션의 독립적인 부분을 담당하게 됩니다.

  • React의 주요 특징들은 다음과 같습니다:
    • 컴포넌트 기반 아키텍처: React는 컴포넌트를 이용해 사용자 인터페이스를 구성하는 컴포넌트 기반 아키텍처를 사용합니다. 이를 통해 코드의 재사용성을 높이고, 유지 보수를 용이하게 합니다.
    • 가상 DOM: React는 가상 DOM (Virtual DOM)이라는 개념을 도입했습니다. 이는 실제 DOM을 직접 조작하는 대신, 가상 DOM을 조작하여 성능을 향상시킵니다. React는 가상 DOM과 실제 DOM의 차이를 찾아내고 (diffing), 이 차이를 최소한의 연산으로 실제 DOM에 반영하는 (reconciling) 과정을 거칩니다.
    • 단방향 데이터 흐름: React는 부모 컴포넌트에서 자식 컴포넌트로의 단방향 데이터 흐름을 사용합니다. 이는 애플리케이션의 상태를 예측 가능하게 만들어 줍니다.
    • React는 웹뿐만 아니라 모바일 애플리케이션 개발에도 사용될 수 있습니다. React Native라는 프레임워크를 통해 iOS와 Android 애플리케이션을 React와 동일한 방식으로 개발할 수 있습니다.

1. 모바일 화면 크기 적용하기

네이버를 예로 들어 설명을 진행하겠다. 우리는 웹 환경에서 네이버에 접속했을 때와 모바일 환경에서 네이버에 접속했을 때 보여지는 화면이 다르다는 것을 알 수 있다. 이는 화면의 비율 차이로 인해서 보여지는 화면을 다르게 해야해서 그렇다.

  • 일반적인 컴퓨터의 화면 비율(가로:세로): 16:9
  • 일반적인 모바일 환경의 화면(가로:세로): 9:20

위는 대게 사용되는 화면비율이고, 장치에 따라 그 비율은 천차만별이다. 모바일은 세로가 가로에 비해 2배가량 긴 형태이고, 컴퓨터는 가로가 세로에 비해 2배가량 긴 형태라는 것만 유의해주길 바란다.

1.1. 프로젝트에서 사용한 모바일 화면 크기 적용하기(.css)

.home,
.barcode,
.map,
.search,
.user,
.user-edit,
.chatting-list,
.cart-page,
.item,
.store {
  max-width: 400px;
  margin-left: auto;
  margin-right: auto;
  background-color: #f2f2f2;
}

위는 해당 프로젝트의 index.css에서 정의한 css이다. 이를 설명하기에 앞서 초기 React프로젝트를 생성했을 때의 구조에 대해서 집고 넘어가겠다.

  • 초기 React 프로젝트 생성시 구조
    • src폴더의 App.js가 메인페이지
      • public폴더의 index.html이 실질 메인페이지 (App.js -> index.js -> public/index.html)
      • node_modules폴더는 Node.js 및 npm을 사용하여 관리되는 패키지 및 모듈을 저장하는 디렉토리
      • build 프로덕션 배포용으로 최적하된 애플리케이션 버전을 포함하는 디렉토리, 개발 중에는 사용하지 않음

따라서 index.js에 적용시키는 css파일이 최종적인 css를 적용시키는데 사용된다.

우리는 최종적인 css를 적용시키는 css파일에 모바일에서 사용되는 컴포넌트의 화면비율을 최대 너비 max-width: 400px;로 적용을 시켰고, 컴퓨터로 보았을 경우에도 이질감이 없도록하기 위해 margin-left: auto; margin-right: auto; 양 옆에 마진을 동일하게 적용시켜주었다. 또한, 해당 컴포넌트의 배경색을 지정해 background-color: #f2f2f2; 분리감을 주었다.

다음은 만들어진 메인화면의 이미지이다. 이어서 각 컴포넌트들을 어떻게 구현했는지 설명하겠다.

2. Header 만들기

  • Header란?

    웹사이트에서 '헤더(header)'란 웹 페이지의 상단에 위치하는 부분을 가리킵니다. 이는 일반적으로 사이트의 로고, 네비게이션 메뉴, 로그인/로그아웃 버튼, 검색 바 등과 같은 중요한 요소들을 포함하며, 웹사이트의 주요 섹션으로 이동하는 링크를 제공합니다.

2.1. 프로젝트에서 사용한 Header 컴포넌트 코드(.jsx)

import { faAngleLeft, faCartShopping } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import React from "react";
import { Link, useLocation, useNavigate } from "react-router-dom";
import "./Header.css";

function Header() {
  const locationNow = useLocation();
  let navigate = useNavigate();

  let goBack = () => {
    navigate(-1);
  };

  const userId = sessionStorage.getItem("email");
  const userRole = sessionStorage.getItem("role");
  const storeId = sessionStorage.getItem("storeid");
  const token = sessionStorage.getItem("token");

  let url;

  const handleConnectCart = () => {
    // 장바구니아이콘 클릭 시 로그인 상태 체크 후 라우팅 진행.
    if (userId === null) {
      navigate("/login");
    } else if (userId !== null) {
      navigate("/cart");
    }
  };

  return (
    <header className="header">
      <div className="back">
        <FontAwesomeIcon
          icon={faAngleLeft}
          className="back-icon"
          onClick={goBack}
        />
      </div>

      <Link to="/" className="link">
        <h1 className="title">On&Off</h1>
      </Link>

      <div to="/cart" className="link" onClick={handleConnectCart}>
        <div className="cart">
          <FontAwesomeIcon
            icon={faCartShopping}
            className={
              locationNow.pathname === "/cart"
                ? "cart-icon active-cart-icon"
                : "cart-icon"
            }
          />
        </div>
      </div>
    </header>
  );
}

export default Header;

2.2. 프로젝트에서 사용한 Header 컴포넌트 css(.css)

Header는 항상 화면의 상단에 고정되어 다른 컴포넌트의 위에 표시되어야 한다. 그렇게 하기위해서는 position: sticky를 선언하고, 위치를 조정해야한다. 이 외의 설명은 중요한 부분은 아니니 생략하도록 하겠다.

.header {
  height: 6vh;
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  background-color: white;
  color: white;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1px 20px;
  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
  z-index: 100; /* 다른 요소 위에 나타나도록 높은 z-index 값을 설정 */
}

.link {
  text-decoration: none;
  color: white;
  text-align: center;
}

.title {
  font-size: 20px;
  color: black;
  font-weight: bold;
}

.cart {
  align-items: center;
  color: black;
}

.cart-icon {
  font-size: 20px;
  margin-right: 5px;
  color: rgba(128, 128, 128, 0.623);
}

.active-cart-icon {
  color: black;
}

.back-icon {
  font-size: 20px;
  margin-right: 5px;
  color: rgba(128, 128, 128, 0.623);
}

.back-icon:active {
  color: black;
  transform: scale(1.1);
}

2.3. 설명

Header 컴포넌트는 모든 페이지에서 공통으로 보여주는 부분이기에 해당 컴포넌트를 <Routes>의 바깥에 선언해주면 된다. 라우팅에 대한 내용은 여기에서는 자세히 설명하지 않겠다. Header 컴포넌트는 큰 어려움 없이 구현을 하였다. 다음은 여기에서 유심히 보아야할 코드이다.

const handleConnectCart = () => {
    // 장바구니아이콘 클릭 시 로그인 상태 체크 후 라우팅 진행.
    if (userId === null) {
      navigate("/login");
    } else if (userId !== null) {
      navigate("/cart");
    }
  };

해당 프로젝트에서는 Cart아이콘을 클릭시 로그인이 되어있는 상태일 경우 "/cart"로 이동하고, 로그인이 되어있지 않은 상태일 경우 "/login"으로 이동하도록 구현했다.


3. 네비게이션 바 만들기

  • 네비게이션 바란?

하단에 위치한 네비게이션 바(Bottom Navigation Bar)는 주로 모바일 앱이나 반응형 웹사이트에서 주요 기능에 쉽게 접근할 수 있도록 도와주는 사용자 인터페이스 요소입니다.

  • 이는 일반적으로 화면의 하단에 위치하며, 앱의 주요 섹션을 나타내는 아이콘 또는 레이블을 포함합니다. 사용자가 이 아이콘 또는 레이블을 탭하면, 해당 섹션으로 바로 이동하게 됩니다.

3.1. 프로젝트에서 사용한 네비게이션바 컴포넌트 코드(.jsx)

해당 프로젝트에서 네비게이션바를 구현할 때, 특정 페이지에서만 네비게이션바가 표시되도록 구현하기 위해서 useLocation를 사용했다.

useLocation의 locationNow.pathname === "/"를 사용해 현재 페이지 주소를 가져와 특정 주소에서만 네비게이션바가 표시되도록 했다.

import React from "react";

import { Link, useLocation, useNavigate } from "react-router-dom";
import "./BottomNav.css";
// 사용할 아이콘 import
import "./FontAwesome";
// FontAwesomIcon 컴포넌트를 사용하기 위해 import
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";

const BottomNav = () => {
  // 현재 선택된 아이콘을 관리하는 state
  const locationNow = useLocation();

  const userId = sessionStorage.getItem("email");
  const userRole = sessionStorage.getItem("role");
  const storeId = sessionStorage.getItem("storeid");
  const token = sessionStorage.getItem("token");

  let url;

  const navigate = useNavigate();

  const handleConnectUser = () => {
    // 유저버튼 클릭 시 로그인 상태 체크 후 라우팅 진행.
    if (userId === null) {
      navigate("/login");
    } else if (userId !== null) {
      navigate("/user");
    }
  };

  if (
    locationNow.pathname === "/" ||
    locationNow.pathname === "/search" ||
    locationNow.pathname === "/user" ||
    locationNow.pathname === "/map" ||
    locationNow.pathname === "/barcode"
  ) {
    return (
      <nav className="nav-wrapper">
        {/* 하단 네비게이션 최상위 태그 */}
        <Link to="/map" className="nav-link">
          <div>
            <FontAwesomeIcon
              icon="map-location-dot"
              className={
                locationNow.pathname === "/map"
                  ? "nav-item active-nav-item"
                  : "nav-item"
              }
            />
            {/* 네비게이션을 구성하고 있는 하나의 버튼 */}
          </div>
        </Link>
        <Link to="/barcode" className="nav-link">
          <div>
            <FontAwesomeIcon
              icon="barcode"
              className={
                locationNow.pathname === "/barcode"
                  ? "nav-item active-nav-item"
                  : "nav-item"
              }
            />
          </div>
        </Link>
        <Link to="/" className="nav-link">
          <div>
            <FontAwesomeIcon
              icon="home"
              className={
                locationNow.pathname === "/"
                  ? "nav-item active-nav-item"
                  : "nav-item"
              }
            />
          </div>
        </Link>
        <Link to="/search" className="nav-link">
          <div>
            <FontAwesomeIcon
              icon="magnifying-glass"
              className={
                locationNow.pathname === "/search"
                  ? "nav-item active-nav-item"
                  : "nav-item"
              }
            />
          </div>
        </Link>

        <div onClick={handleConnectUser} className="nav-link">
          <FontAwesomeIcon
            icon="user"
            className={
              locationNow.pathname === "/user"
                ? "nav-item active-nav-item"
                : "nav-item"
            }
          />
        </div>
      </nav>
    );
  } else {
    return null;
  }
};

export default BottomNav;

3.2. 프로젝트에서 사용한 네비게이션바 컴포넌트 css(.css)

네비게이션바가 항상 하단에 고정되도록 하고, 기타 css를 통해 네비게이션바를 꾸몄다. 다음은 프로젝트에서 사용한 네비게이션바의 css코드이다.

/* 네비바 하단 고정 밑 세로 길이 설정 */
.nav-wrapper {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 6vh;
  background-color: #fff;
  padding: 1px 0px;
  box-shadow: 0 0px 10px rgba(0, 0, 0, 0.2);
  max-width: 400px;
  margin-left: auto;
  margin-right: auto;
}

/* nav태그 아래의 자식들을 수평정렬하기 위한 설정 */
nav {
  overflow: hidden;
  /* 네비게이션 바의 상단에 선 표현 */
  /* border-top: 2px solid rgba(128, 128, 128, 0.623); */
}
/* nav태그 아래의 div태그들을 수평정렬 및 세로길이 설정 */
.nav-link {
  /* 수평정렬, 5개의 button을 각각 20% width만큼 할당 */
  float: left;
  width: 20%;
  text-align: center;

  /* 세로길이 설정 */
  height: 50px;
  line-height: 50px;
}

/* 하단 네비바의 메뉴를 눌렀을 때 색상 변경*/
.nav-item {
  color: rgba(128, 128, 128, 0.623);
}

/* 하단 네비바의 메뉴를 눌렀을 때 색상 변경*/
.active-nav-item {
  color: black;
}

4. 일정 시간이 지나면 다음 이미지로 넘어가는 광고 컴포넌트 만들기

프로젝트를 진행하면서 광고를 표시하는 컴포넌트를 구현했다. 다음은 해당 컴포넌트에 대한 코드이다.

4.1. 프로젝트에서 사용한 광고 컴포넌트 코드(.jsx)

import React, { useEffect, useState } from "react";
import "./ImageSlider.css";

const AdComponent = ({ images }) => {
  const [currentIndex, setCurrentIndex] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setCurrentIndex((prevIndex) =>
        prevIndex === images.length - 1 ? 0 : prevIndex + 1
      );
    }, 6000);

    return () => {
      clearInterval(interval);
    };
  }, [currentIndex, images]);

  return (
    <div className="ad-image-container">
      <div className="slider-image-container">
        <img
          key={images[currentIndex].id}
          src={images[currentIndex].src}
          alt={`Ad ${images[currentIndex].id}`}
          className="ad-image"
        />
        <div className="slider-image-index">
          {Array.from({ length: images.length }, (_, i) => (
            <div
              key={images[i].id}
              className={`slider-circle ${i === currentIndex ? "active" : ""}`}
            ></div>
          ))}
        </div>
      </div>
    </div>
  );
};

export default AdComponent;

5. 모달창 디자인으로 컴포넌트 디자인하기

로그인화면과 같은 특정 페이지에서 컴포넌트 안에 모달창 디자인을 사용해 로그인 화면을 디자인했다.

아래에는 사용한 login화면의 css파일이다.

5.1. 프로젝트에서 사용한 로그인 컴포넌트 css(.css)

login-container는 모달창을 제외한 화면이다. login-container의 안에 로그인을 할 수 있는 컴포넌트(login-form)를 모달창형식으로 디자인 했다.

/* login */
.login-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background: linear-gradient(
    to bottom,
    rgb(83, 27, 27),
    #01172f
  ); /* 배경 그라데이션 추가 */
  color: white; /* 텍스트 색상을 흰색으로 변경 */
}

.login-form {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 300px;
  background-color: white;
  color: black;
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); /* 그림자 수정 */
  padding: 20px;
  border-radius: 20px;
  background-color: #fff; /* 백그라운드 색상을 흰색으로 변경 */
}

.login-form label {
  margin-bottom: 10px;
  color: #3a1919; /* 레이블 텍스트 색상을 레드블랙으로 변경 */
}

.login-form input {
  padding: 10px;
  margin-bottom: 10px;
  border: 1px solid #ccc; /* 보통 테두리로 돌아갑니다. */
  border-radius: 10px; /* 입력 필드 테두리를 둥글게 만듭니다. */
  background-color: #f5f5f5; /* 배경색을 자연스럽게 회색 톤으로 변경합니다. */
  color: #3a1919; /* 텍스트 색상을 어두운 레드로 유지합니다. */
  box-shadow: 0 0 5px rgba(0, 0, 0, 0.2); /* 약간의 그림자를 추가하여 입체감을 줍니다. */
  transition: background-color 0.2s, border 0.2s; /* 부드러운 전환 효과를 적용합니다. */
}

.login-form input:focus {
  background-color: white; /* 포커스 시 입력 필드 배경을 하얀색으로 변경하여 자연스러운 강조를 줍니다. */
  border: 1px solid red; /* 포커스 시 테두리 색상 변경 */
}

.login-form button {
  font-size: 1rem;
  padding: 10px;
  background-color: rgb(255, 100, 100);
  color: white;
  border: none;
  cursor: pointer;
  border-radius: 10px; /* 버튼을 둥글게 만듭니다. */
  font-weight: bold; /* 텍스트를 굵게 만듭니다. */
}

.login-form button:active {
  background-color: rgb(188, 46, 46);
  transform: scale(0.9);
}

.signup-link {
  margin-top: 10px;
  text-align: center;
}

.signup-link a {
  color: rgb(255, 100, 100);
  text-decoration: none;
  font-weight: bold; /* 링크 텍스트를 굵게 만듭니다. */
}

.signup-link a:active {
  color: darkred;
}

마치며

프로젝트를 진행하며 구현과정에서 알게된, 알아두면 유용하다고 생각하는 것들을 위주로 다루어보았다. 추가로 진행하며 좋은 것들이 있으면 업로드하도록 하겠다.

profile
Jihukimme

0개의 댓글