본문 바로가기

카테고리 없음

리액트 네이티브 (2) - 화면 Layout

웹앱을 구성하는 강의가 있어서 쭈욱 따라해 보고 있다.

( 현재 주간에 교육받는 과정에서 첫번째 프로젝트가 있었던 탓에, 거의 클론 코딩 수준으로 따라해보는 중이다)

 

1. 모바일 기기에서 사이즈 표현 

휴대용 모바일 기기의 사양한 사이즈 - 기기별로 비율늘리기(대각선 방향 비율늘리기)

태블릿 - 적정 폰트사이즈 이상인 경우 input등은 가로늘리기 ( 사진등은 여러 개를 표시 )
데스크톱 - 휴대용 모바일용 화면에서 가로 늘리기와 여백 늘리기를 통해 대응

 

1) 비율 늘리기 ( 상대 단위 활용 : em, rem, vw, vh, %, 절대 단위 : 1px(0.026cm))

   - em : 해당 단위가 사용되고 있는 요소의 font-size에 연동 (폰트 사이즈에 설정에 따라 변경 ) 

   - rem : 루트(html)요소의 font-size에 연동 (default 16px) 

                html의 font-size를 조정할 경우 동적 사이즈가 달라짐 

 

예) 디자인에서 16px의 폰트로 표본 디자인이의 핸드폰 가로길이를 360px인 경우

       특정 모바일 휴대기기의 vw(100%)를 360과 비교, 폰트사이즈 = 100vw / 360 * 16

//Next의 Global CSS에 font-size기준설정

html {
   font-size: calc ((40 / 9) * 1vw)
}

 

2. 헤더 구성 

- 화면 상단에 Top Layer로 구성하여 콘텐츠와 겹치게 구성 (fixed로 위치고정)

- 아래에 콘텐츠로 인해 스크롤이 있는 경우 해당층에 헤더와 같은 크기의 공간을 확보

   ( 헤더가 콘텐츠와 서로 의존하지 않도록 함 )  

- 콘텐츠가 지도나 사진인 경우 투명하게 처리 ( underlayer만들 필요없음 )

 

(1) 글로벌 헤더  

- 전체 페이지에 통용되는 헤더 구성, 설정 영역의 컴퍼넌트를 구성해주고 children(구조분해할당으로)을 props로 할당

** 헤더/푸터와 함께 구현하여 전체 레이아웃을 표현

 

(2) 로컬 헤더

- 헤더가 페이지의 톡특한 특성을 반영하도록 구성하여 유지/보수 편의성 확보

- unique한 값 ( 예: 통신을 통해 받아온 value나 동적페이지 구성 ) 혹은 헤더에 검색이나 북마트 등의 연동작업이 있는 경우

 

(3) 특수한 헤더구성

- 지도, 맵등의 경우 투명한 헤더 구성 ( 불투명한 헤더에 하단의 layer에 공간을 구성하여 콘텐츠 영역과 헤더가 간섭하는 것을 방지 )

 

3. 푸터 구성 

- 통상 콘텐츠가 짧으면 바닥, 콘텐츠 길면 스크롤 하단배치

- 콘텐츠가 짧은 경우 푸터와의 사이에 길이 조정이 가능한 요소를 배치(푸터를 내려줌 )

  화면 전체를 하나의 객체로 구성해서 flex-direction설정

  ➡ 화면의 전체 height설정 (기기의 vh를 minHeight로 하여 스크롤도 가능하도록 설정) 

   div추가 (flex 설정, 텐츠와 푸터사이의 공간이 생기는 경우 푸터를 아래로 밀어냄) 

** Layout에 Footer를 배치할 때 추가된 div와 footer가 특정 요소로 감싸지지 않고

    전체 화면의 flex적용을 받을수 있도록 주의 

 

프로젝트 전체의 레이아웃 (layout.tsx) 표시해 줄 페이지의 레이아웃 설정(commons/index.tsx) 

//app폴더 내 기본레이아웃 구성, Layout.tsx

import type { Metadata } from "next";
... 기타 구성
import LayoutFooter from "@/commons/layout/02-05-layout-footer";

.. 폰트구성 

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <LayoutFooter>{children}</LayoutFooter>
      </body>
    </html>
  );
}

//LayoutFooter구성 (헤더와 푸터를 구성해서 전체 레이아웃 구성)
import { HeaderGlobal } from "./header-2-4";

export default function LayoutFooter({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <>
      <div
        style={{
          display: "flex",
          flexDirection: "column",
          minHeight: "100vh",
          width: "100vw",
        }}
      >
        <HeaderGlobal />
        {children}
      </div>
    </>
  );
}

 

 

4. 페이지별 헤더와 푸터의 옵션 (commons/constants)

헤더(commons/headers) - Global헤더와 Local헤더 구분(동적인 페이지)

푸터(commons/footer) - 콘텐츠의 길이와 관계없이 화면의 하단에 배치 

//헤더와 푸터의 페이지별 표시 옵션 작성
import { IHeaderOptions } from "@/commons/types/types";

export const HEADER_OPTIONS = (params: {
  id: string | null;
}): IHeaderOptions => ({
  GLOBAL: {
    "/section02/02-02-layout-header-global": {
      hasLogo: true,
      hasBack: false,
      title: "게시글 등록",
    },
    "/section02/02-04-layout-header-transparent": {
      hasLogo: true,
      hasBack: true,
      title: "사진이 보여요",
      isTransparent: true,
    },
    "/section02/02-04-layout-header-opaque": {
      hasLogo: true,
      hasBack: false,
      title: "게시글 등록",
      isTransparent: false, //생략가능
    },
    "/section02/02-05-layout-footer": {
      hasLogo: true,
      hasBack: true,
      title: "등록페이지",
      isTransparent: false, //생략가능
    },
  },
  LOCAL: {
    [`/section02/02-03-layout-header-local/${params.id}`]: {
      hasLogo: true,
      hasBack: true,
      title: "",
    },
  },
});


//헤더 표시 부분 작성 
"use client";

import { usePathname } from "next/navigation";
import { HEADER_OPTIONS } from "./constants";

export default function HeaderGlobal() {
  const pathname = usePathname();
  const options = HEADER_OPTIONS.GLOBAL[pathname];

  return (
    <>
      <header
        style={{
          width: "100vw",
          height: "3.125",
          backgroundColor: "yellow",
          display: "flex",
          gap: "0.3125rem",
        }}
      >
        {options.hasLogo && <div>로고</div>}
        {options.hasBack && <div>[뒤로가기버튼]</div>}
        {options.title && <div>{options.title}</div>}
      </header>
    </>
  );
}

//헤더 구성, Header Global내에 Local Header와 Glocal Header
"use client";

import { useParams, usePathname } from "next/navigation";
import { HEADER_OPTIONS } from "./constants2-5";
import { IHeaderOption } from "@/commons/types/types";
import { ReactNode } from "react";

const HeaderBase = ({
  hasLogo,
  hasBack,
  title,
  isTransparent,
  children,
}: IHeaderOption) => {
  console.log(hasLogo, hasBack, title, isTransparent);
  return (
    <>
      <header
        style={{
          width: "100vw",
          height: "3.125rem",
          display: "flex",
          flexDirection: "row",
          gap: "0.3125rem",
          backgroundColor: isTransparent ? "transparent" : "yellow",
          position: "fixed",
        }}
      >
        {hasLogo && <div>헤더로고</div>}
        {hasBack && <div>[뒤로가기 버튼]</div>}
        {title && <div>제목: {title}</div>}
        {children && <div>{children}</div>}
      </header>
      {isTransparent ? (
        <></>
      ) : (
        <div
          style={{
            width: "100vw",
            height: "3.125rem",
          }}
        ></div>
      )}
    </>
  );
};

export function HeaderGlobal() {
  const pathname = usePathname();
  const params = useParams<{ id: string }>();
  const options = HEADER_OPTIONS(params).GLOBAL[pathname];

  return (
    <div style={{ display: options ? "block" : "none" }}>
      <HeaderBase {...options} />
    </div>
  );
}

export function Header({ children, ...rest }: { children: ReactNode }) {
  const pathname = usePathname();
  const params = useParams<{ id: string }>();
  const options = HEADER_OPTIONS(params).LOCAL[pathname];

  return (
    <div style={{ display: options ? "block" : "none" }}>
      <HeaderBase {...options} {...rest}>
        {children}
      </HeaderBase>
    </div>
  );
}

//푸터 구성 (commons/footer)
import { ReactNode } from "react";

export default function Footer({ children }: { children: ReactNode }) {
  return (
    <>
      <div style={{ flex: 1 }}></div>
      <footer
        style={{
          display: "flex",
          flexDirection: "column",
          width: "100vw",
          height: "3.125rem",
          backgroundColor: "cyan",
        }}
      >
        <div>footer</div>
        {children}
      </footer>
    </>
  );
}

 

4. 웹 페이지 내용 구성 

- 콘텐츠가 짧은 경우 푸터가 아래에 표시 ( 버튼을 클릭하면 요소가 추가, 내용이 화면의 크기를 초과하면 하단 스크롤 표시 )

"use client";
import Footer from "@/commons/layout/02-05-layout-footer/footer";
import { useState } from "react";

export default function LayOutHeaderTransparent() {
  const [islong, setIsLong] = useState(false);
  const onClickToggle = () => {
    setIsLong((prev) => !prev);
  };
  const arrayLong = new Array(30).fill(1);

  return (
    <>
      <main>
        <button
          onClick={onClickToggle}
          style={{ borderWidth: "1px", borderColor: "gray" }}
        >
          숏컨텐츠 / 롱컨텐츠 (토글)버튼
        </button>
        <hr />
        {islong &&
          arrayLong.map(() => (
            <>
              제목 : <input type="text" />
              <br />
              내용 : <input type="text" />
              <br />
              작성자 : <input type="text" />
              <br />
            </>
          ))}
        제목 : <input type="text" />
        <br />
        내용 : <input type="text" />
        <br />
        작성자 : <input type="text" />
        <br />
      </main>
      <Footer>
        <button>등록하기 </button>
      </Footer>
    </>
  );
}

 

 

** 페이지 렌더링 결과 (아이폰 13)

- 헤더는 윗부분에 fix하여 층을 달리하여 띄우고 하단에 div를 배치하여 스크롤에 밀리지 않게구성

- 푸터는 콘텐츠가 짧은 경우에도 UI하단에 배치되도록 구성 

콘텐츠가 짧은 경우 버튼처리, 콘텐츠 길어지면 스크롤 처리

 

___________________________________________

https://nekocalc.com/px-to-rem-converter