ํ๋ก ํธ์๋ ์ฑ๋ฅ ์ต์ ํ ๋ฐฉ๋ฒ์ ๋ก๋ฉ ์ต์ ํ, ๋ ๋๋ง ์ต์ ํ ๊ด์ ์์ ์ ๋ฆฌํด๋ณด์๋ค.
๐๋ก๋ฉ ์ต์ ํ ๋ฐฉ๋ฒ
๐ํ๋ฉด์ด ๊ทธ๋ ค์ง๊ธฐ๊น์ง ๊ณผ์
domContentLoaded
domContentEventLoaded๋ ์น ํ์ด์ง์ DOM ํธ๋ฆฌ๊ฐ ์์ ํ ๋ก๋๋๊ณ ํ์ฑ๋์์ ๋ ๋ฐ์ํ๋ ์ด๋ฒคํธ์ด๋ค. domContentEventLoaded ์ด๋ฒคํธ๋ ์ด๋ฏธ์ง, ์คํ์ผ์ํธ ๋ฐ ํ์ ํ๋ ์ ๋ก๋ฉ์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ๋ฐ์ํ๋ฉฐ, ์น ํ์ด์ง์ ์ด๊ธฐํ ๋ฐ ์ค์ ์์ ์ ์ํํ๊ธฐ ์ ์ ํ ์์ ์ด๋ค.
load
load ์ด๋ฒคํธ๋ ์น ํ์ด์ง์ ๊ทธ ์์ ์๋ ๋ชจ๋ ๋ฆฌ์์ค(์ด๋ฏธ์ง, ์คํ์ผ์ํธ, ์คํฌ๋ฆฝํธ ๋ฑ)๊ฐ ์์ ํ ๋ก๋๋์์ ๋ ๋ฐ์ํ๋ค. ํ์ด์ง๊ฐ ์์ ํ ๋ก๋๋ ํ์๋ง ์คํ๋์ด์ผ ํ๋ ์์ ์ด ์๋ ๊ฒฝ์ฐ์ ์ ์ฉํ๋ค.
๐๋ธ๋ก ๋ฆฌ์์ค
๋ธ๋ก ๋ฆฌ์์ค๋ ์น ํ์ด์ง์ ๋ ๋๋ง์ ์ฐจ๋จํ๊ณ ํ์ด์ง์ ๋ก๋ ์๋๋ฅผ ๋๋ฆฌ๊ฒ ๋ง๋๋ ๋ฆฌ์์ค๋ฅผ ๋งํ๋ค. ์ฃผ๋ก CSS, JavaScript ํ์ผ, ์ด๋ฏธ์ง ํฐํธ ๋ฑ์ด ํด๋น๋๋ค.
โ๏ธ๋ธ๋ก ๋ฆฌ์์ค ์ต์ ํ ๋ฐฉ๋ฒ
- JavaScript ๋ก๋ ์์ ์ด์ ํ
- <body> ํ๊ทธ ํ๋จ ๋ฐฐ์น: HTML ํ์ฑ์ด ๋จผ์ ์ด๋ฃจ์ด์ง๊ณ , ์๋ฐ์คํฌ๋ฆฝํธ ๋ก๋ฉ์ด ๋ง์ง๋ง์ ์ด๋ฃจ์ด์ ธ ํ์ด์ง ๋ก๋ฉ ์๊ฐ์ ๋จ์ถํ ์ ์๋ค.
- async์ defer ์ฌ์ฉ: <head> ํ๊ทธ ๋ด์ ์คํฌ๋ฆฝํธ๋ฅผ ์์น์ํค๋, async๋ defer ์์ฑ์ ์ฌ์ฉํ์ฌ ๋ก๋ฉ ๋ฐฉ์์ ์ต์ ํํ๋ค. async๋ ์คํฌ๋ฆฝํธ๊ฐ ๋น๋๊ธฐ์ ์ผ๋ก ๋ก๋๋๋ฉฐ, defer๋ HTML ํ์ฑ ํ, DOMContentLoaded ์ด๋ฒคํธ ๋ฐ๋ก ์ ์ ์คํ๋๋ค.
- CSS ํ์ผ๊ณผ ์ธ๋ผ์ธ ์คํ์ผ
- ์ธ๋ผ์ธ ์คํ์ผ ์ฌ์ฉ: ์ธ๋ถ ์คํ์ผ์ํธ ๋์ HTML ๋ฌธ์ ๋ด์ ์ง์ CSS๋ฅผ ํฌํจ์ํค๋ฉด HTTP ์์ฒญ ์๋ฅผ ์ค์ฌ ๋ก๋ฉ ์๊ฐ์ ๋จ์ถํ ์ ์์ต๋๋ค. ์ธ๋ถ ์คํ์ผ ์ํธ(@import)๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ธ๋ผ์ฐ์ ๋ ์คํ์ผ์ํธ๋ฅผ ๋ณ๋ ฌ๋ก ๋ค์ด๋ก๋ํ ์ ์ ์ ๋๋ฌธ์ ๋ก๋ ์๊ฐ์ด ๋์ด๋ ์ ์๋ค.
- ๋ฏธ๋์ด ์ฟผ๋ฆฌ ์ฌ์ฉ: ํน์ ์กฐ๊ฑด์์๋ง ํ์ํ CSS๊ฐ ์์ ๋ ๋ฏธ๋์ด ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ถํ์ํ ๋ธ๋กํน์ ๋ฐฉ์งํ ์ ์๋ค. ์๋ฅผ ๋ค์ด, ๋ค์๊ณผ ๊ฐ์ด ํ์ด์ง๋ฅผ ์ธ์ํ๊ฑฐ๋(print.css) ํ๋ฉด์ด ์ธ๋ก ๋ชจ๋์ผ ๋(portrait.css) ์ฌ์ฉํ๋ CSS๊ฐ ์์ ๋, ํด๋น ์คํ์ผ์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ์๋ง ๋ก๋ํ ์ ์๋๋ก <script> ํ๊ทธ์ media ์์ฑ์ ๋ช ์ํ์ฌ ์ฌ์ฉํ๋ค.(์ถ์ฒ: ์ฑ๋ฅ ์ต์ ํ | TOAST UI :: Make Your Web Delicious! )
๐๋ฆฌ์์ค ์์ฒญ ์ ์ค์ด๊ธฐ: ์ด๋ฏธ์ง ์คํ๋ผ์ดํธ
์ด๋ฏธ์ง ์คํ๋ผ์ดํธ๋ ์ฌ๋ฌ ๊ฐ์ ์ด๋ฏธ์ง๋ฅผ ํ๋๋ก ๋ง๋ค๊ณ , CSS background-position ์์ฑ์ ์ฌ์ฉํด ๋ถ๋ถ ์ด๋ฏธ์ง๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ด๋ค. ์ด๋ฏธ์ง ์คํ๋ผ์ดํธ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ๋ฆฌ์์ค ์์ฒญ ์๊ฐ ์ค์ด๋ค์ด ๋ก๋ฉ ์๊ฐ์ ๋น ๋ฅด๊ฒ ํ ์ ์๋ค.
๊ทธ ๋ฐ์๋ CSS, ์๋ฐ์คํฌ๋ฆฝํธ ๋ฒ๋คํ๊ธฐ / ๋ด๋ถ ์คํ์ผ์ํธ ์ฌ์ฉํ๊ธฐ ๋ฑ์ผ๋ก ๋ฆฌ์์ค ์์ฒญ ์๋ฅผ ์ค์ผ ์ ์๋ค.
๐๋ ๋๋ง ์ต์ ํ ๋ฐฉ๋ฒ
ํ๋ก ํธ์๋์์ ์ต์ ํ๋ ๋ ๋๋ง ๊ธฐ๋ฒ์ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๊ณ ์น ์ฌ์ดํธ์ ์ดํ๋ฅ ์ ๋ฎ์ถ๋ ํต์ฌ์ ์ธ ์ญํ ์ ์ํํ๋ฏ๋ก ์ค์ํ๋ค. ํนํ, ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๋ฅผ ์ํ ์ฑ๋ฅ ์ต์ ํ ๋ฐฉ๋ฒ์ผ๋ก๋ ๊ฐ์ ์คํฌ๋กค๋ง, ํ์ด์ง๋ค์ด์ ๋ฐ ๋ฌดํ ์คํฌ๋กค ๊ตฌํ์ด ์๋ค.
๐๋์ฉ๋ ๋ฐ์ดํฐ๊ฐ ์น ํ์ด์ง์ ๊ทธ๋ ค์ง๊ธฐ๊น์ง์ ๊ณผ์ : ๋ก๋์ ๋ ๋๋ง
๋ก๋
๋ก๋๋ ์น ํ์ด์ง์ ๋ฆฌ์์ค(HTML, CSS, JavaScript ํ์ผ ๋ฑ)์ด ์๋ฒ๋ก๋ถํฐ ์ฌ์ฉ์์ ๋ธ๋ผ์ฐ์ ๋ก ์ ์ก๋๋ ๊ณผ์ ์ด๋ค. ๋ก๋ ๊ณผ์ ์ด ์๋ฃ๋์ด์ผ ์น ํ์ด์ง๊ฐ ์ ์์ ์ผ๋ก ๋ ๋๋ง๋ ์ ์๋ค.
๋ ๋๋ง
๋ ๋๋ง์ ๋ก๋๋ ์น ํ์ด์ง์ ๋ฆฌ์์ค๋ฅผ ์ฌ์ฉํ์ฌ ์ค์ ๋ก ํ๋ฉด์ ํ์ด์ง์ ๊ทธ๋ฆฌ๋ ๊ณผ์ ์ด๋ค.
⇒ ๊ฐ์ ์คํฌ๋กค๋ง, ํ์ด์ง๋ค์ด์ ๋ฐ ๋ฌดํ ์คํฌ๋กค ๊ตฌํ์ ๋ ๋๋ง ์ฑ๋ฅ ์ต์ ํ ๋ฐฉ๋ฒ์ด๋ค.
๐Virtual Sroll
Virtual Scroll์ ๋ง์ ๋์ฉ๋ ๋ฐ์ดํฐ์์ ์ฌ์ฉ์๊ฐ ๋ณผ ์ ์๋ ์์ญ๋ด์์ ๋ณผ ์ ์์๋งํผ์ ๋ฐ์ดํฐ๋ง ํ์ํ์ฌ DOM์ ๊ทธ๋ฆฌ๋๋ฐ ์ฌ์ฉ๋๋ ๋ฆฌ์์ค๋ฅผ ์ค์ด๋ ๊ธฐ๋ฒ์ ์ด๋ค. ์ ์ฒด ๋ฐ์ดํฐ๋ฅผ ๋ ๋๋งํ๋ ๊ฒ์ด ์๋๋ผ ํ์ฌ ์ฌ์ฉ์๊ฐ ๋ณด๊ณ ์๋ ํ๋ฉด์ ์คํฌ๋กค ์์น๋ฅผ ๊ณ์ฐํ์ฌ ํด๋น ์์น์ ๋ง๋ ๋ฐ์ดํฐ๋ง ๊ทธ๋ ค์ฃผ๋ ์๋ฆฌ๋ค.
ํ์ฌ ์ฌ์ฉ์๊ฐ ๋ณด๊ณ ์๋ ํ๋ฉด์ ๋ฐ์ดํฐ๋ง ํ์ํ๊ธฐ ์ํด์ scroll ์ด๋ฒคํธ๋ฅผ ์ ์ฉํด์ผ ํ๋ค. ๋ค์๊ณผ ๊ฐ์ ๊ตฌ์กฐ๋ฅผ ํ์ฉํ์.
useEffect(()=>{
//...๋ก์ง ์ํ
return () =>{
//์ปดํฌ๋ํธ ์ธ๋ง์ดํธ ์ ์คํ๋ ๋ก์ง
}
},[]); //์์กด์ฑ ๋ฐฐ์ด์ด ๋น์ด ์์ผ๋ฏ๋ก, ์ปดํฌ๋ํธ๊ฐ ๋ง์ดํธ๋ ๋๋ง ์คํ๋๋ค.
โป์ฝ๋
import React from 'react';
import { useState, useEffect } from 'react';
import styles from "./Search.module.css";
function Search2() {
const [searchItems, setSearchItems] = useState([]);
const [scrollPos, setScorllPos] = useState(0);
const windowHeight = window.innerHeight;
const CONTENT_HEIGHT = 400;
const NODE_PADDING = 2;
function onScroll(){
setScorllPos(window.scrollY);
}
useEffect(()=>{
fetch('์์ฒญURL')
.then((res)=>res.json())
.then((data)=>setSearchItems(data?.collection?.items));
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
}
},[]);
return (
<div>
<h1>Hello, World!</h1>
{searchItems.map((searchItem, i)=>(
<div className={styles.card} key={i}>
{scrollPos < CONTENT_HEIGHT * (i+1+NODE_PADDING)
&& (scrollPos + windowHeight) > CONTENT_HEIGHT * (i-1-NODE_PADDING) ?
<div className={styles.imgBox}><img className={styles.cardImg} src={searchItem?.links?.[0]?.href} /></div>
: <div className={styles.imgBox}></div>}
{scrollPos < CONTENT_HEIGHT * (i+1+NODE_PADDING)
&& (scrollPos + windowHeight) > CONTENT_HEIGHT * (i-1-NODE_PADDING) ?
<div className={styles.contentBox}><p>{searchItem?.data[0]?.title}</p></div>
: <div className={styles.contentBox}>null</div>}
</div>
))}
</div>
);
}
export default Search2;
๐๋ฌดํ์คํฌ๋กค
ํ์ด์ง๋ค์ด์ ๊ณผ ๋น๊ตํ ๋ฌดํ์คํฌ๋กค์ ์ฅ์ ์ ์ฌ์ฉ์ ์ ์ฅ์์ ์ถ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ณด๊ณ ์ถ์ ๋ ์ถ๊ฐ ํ๋์ ํ ํ์๊ฐ ์๋ค๋ ์ ์ด๋ค. ์ด๋ ์ํฉ์ ๋ฐ๋ผ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํฌ ์ ์๋ค.
โ๏ธ๊ธฐ์กด scorll ์ด๋ฒคํธ์ ๋ฌธ์
๋ฌดํ์คํฌ๋กค์ ๊ตฌํํ๊ธฐ ์ํด์๋ virtual scroll๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก scroll ์ด๋ฒคํธ๋ฅผ ๊ฐ์งํด์ผ ํ๋ค. scroll ์ด๋ฒคํธ๋ ์ฌ์ฉ์์ ์คํฌ๋กค์ ๋ฐ๋ผ ์งง์ ์๊ฐ์ ๋ฌด์ํ ๋ง์ ์์ ์ด๋ฒคํธ๊ฐ ๋๊ธฐ์ ์ผ๋ก ๋ฐ์ํ๊ธฐ ๋๋ฌธ์ ๋ฉ์ธ ์ค๋ ๋(Main Thread)์ ์ํฅ์ ์ค๋ค. ํ ํ์ด์ง ๋ด์ ์ฌ๋ฌ scroll ์ด๋ฒคํธ(๋ฌดํ ์คํฌ๋กค, ๊ด๊ณ ๋ฐฐ๋, ์ ๋๋ฉ์ด์ ๋ฑ)๊ฐ ๋ฑ๋ก๋์ด ์์ ๊ฒฝ์ฐ, ๊ฐ ์๋ฆฌ๋จผํธ๋ง๋ค ์ด๋ฒคํธ๊ฐ ๋ฑ๋ก๋์ด ์๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์๊ฐ ์คํฌ๋กคํ ๋๋ง๋ค ์ด๋ฅผ ๊ฐ์งํ๋ ์ด๋ฒคํธ๊ฐ ๊ณ์ ํธ์ถ๋๋ค. ์ด๋ฅผ debounce์ throttle๋ก ์ผ๋ถ ๊ฐ์ ํ ์ ์์ง๋ง, ์ฌ์ ํ ์ฑ๋ฅ ๋ถํ๊ฐ ์์ ์ ์๋ค.
๋ํ ํน์ ์ง์ ์ ๊ด์ฐฐํ๊ธฐ ์ํด์๋ getBoundingClientRect()ํจ์๋ฅผ ์ฌ์ฉํด์ผ ํ๋๋ฐ, ์ด ํจ์๋ reflow๋ฅผ ๋ฐ์์ํฌ ์ ์๋ค. reflow๋ ๋ธ๋ผ์ฐ์ ๊ฐ ์น ํ์ด์ง์ ์ผ๋ถ ๋๋ ์ ์ฒด๋ฅผ ๋ค์ ๊ทธ๋ ค์ผ ํ๋ ๊ฒฝ์ฐ ๋ฐ์ํ๋ฉฐ, ๋ธ๋ผ์ฐ์ ๊ฐ ๋ฌธ์ ๋ด ์์๋ค์ ์์น์ ๊ธฐํํ์ ํํ๋ฅผ ๋ค์ ๊ณ์ฐํ์ฌ ๋ ์ด์์์ด๋ ์ฝํ ์ธ ์ ๋ณ๊ฒฝ์ ๋ง๊ฒ ์กฐ์ ํ๋ ๊ณผ์ ์ ์๋ฏธํ๋ค.
โ๏ธIntersection Observer API
Intersection Observer API๋ฅผ ์ฌ์ฉํ์ฌ ์์ ๊ฐ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋ค. Intersecion Observer API๋ ๋น๋๊ธฐ์ ์ผ๋ก ์คํ๋๊ธฐ ๋๋ฌธ์ ๋ฉ์ธ ์ค๋ ๋์ ์ํฅ์ ์ฃผ์ง ์์ผ๋ฉด์ ๋ณ๊ฒฝ ์ฌํญ์ ๊ด์ฐฐํ ์ ์๋ค. ๋ํ IntersectionObserverEntry ์์ฑ์ ํ์ฉํด getBoundingClientRect()๋ฅผ ํธ์ถํ ๊ฒ๊ณผ ๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ์ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฐ๋ก getBoundingClientRect() ํจ์๋ฅผ ํธ์ถํ ํ์๊ฐ ์์ด ๋ฆฌํ๋ก์ฐ ํ์์ ๋ฐฉ์งํ ์ ์๋ค.
const intersectionObserver = new IntersectionObserver(callback[, options])
โป์ฝ๋
import React from 'react';
import { useState, useEffect } from 'react';
import styles from "./Search.module.css";
function Search3() {
const [searchItems, setSearchItems] = useState([]);
const [page, setPage] = useState(0);
const [isLoading, setIsLoading] = useState(false);
useEffect(()=>{
console.log("page: ",page);
fetch(`https://images-api.nasa.gov/search?q=apollo&page=${page}`)
.then((res)=>res.json())
.then((data)=>{
setSearchItems((prevData)=>[...prevData, ...data?.collection?.items]);
setIsLoading(false);
});
},[page]);
const handleObserver = (entries) => {
const target = entries[0];
if (target.isIntersecting && !isLoading) {
setPage((prevPage) => prevPage + 1);
setIsLoading(true);
}
};
useEffect(()=>{
const observer = new IntersectionObserver(handleObserver, {
threshold: 0, // Intersection Observer์ ์ต์
, 0์ผ ๋๋ ๊ต์ฐจ์ ์ด ํ ๋ฒ๋ง ๋ฐ์ํด๋ ์คํ, 1์ ๋ชจ๋ ์์ญ์ด ๊ต์ฐจํด์ผ ์ฝ๋ฐฑ ํจ์๊ฐ ์คํ.
});
// ์ตํ๋จ ์์๋ฅผ ๊ด์ฐฐ ๋์์ผ๋ก ์ง์ ํจ
const observerTarget = document.getElementById("observer");
// ๊ด์ฐฐ ์์
if (observerTarget) {
observer.observe(observerTarget);
}
},[]);
return (
<div>
<h1>Hello, World!</h1>
{searchItems.map((searchItem, i)=>(
<div className={styles.card} key={i}>
<div className={styles.imgBox}><img className={styles.cardImg} src={searchItem?.links?.[0]?.href} /></div>
<div className={styles.contentBox}><p>{searchItem?.data[0]?.title}</p></div>
</div>
))}
<div id="observer" style={{ backgroundColor:"pink", height: "10px" }}></div>
</div>
);
}
export default Search3;
'๐ป์น(Web)' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[React]์ด๊ธฐ์ธํ (0) | 2024.07.18 |
---|---|
MVC, MVVM, Flux ํจํด (1) | 2024.07.15 |
[React]React Router (0) | 2024.06.28 |
์ผ๋ฐ์ ์ผ๋ก GET ์์ฒญ์๋ Request Body๋ฅผ ์์ฒญํ ์ ์๋ค! (0) | 2023.08.24 |
์ธ์ ๊ธฐ๋ฐ ์ธ์ฆ, ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ, JWT๋ฅผ ํตํ ์ธ์ฆ ์ ์ฐจ(with Access Token, Refresh Token) (0) | 2023.03.26 |