์ต๊ทผ์ ์ด๋ฏธ์ง๋ฅผ ํ๋ฆฌ๋ก๋ํ๋ ๊ธฐ๋ฅ์ ๋ง๋ค์๋ค. ํ์ด์ง๋ค์ด์ ์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ค๋ ์์ ์ ์ต์ํ์ง๋ง, ๋ค๋ฅธ ํ๋ฉด์์ ๋ฏธ๋ฆฌ ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฑด ์ฒ์์ด๋ผ ๊ตฌํํ๋ฉด์ ์๋ก ์๊ฒ ๋ ๋ถ๋ถ๋ค์ ๊ฐ๋จํ ์ ๋ฆฌํด ๋ณด์๋ค. ์ค์ ๋ก ์ ์ฉํ ๋ด์ฉ๋ ์๊ณ , ๊ฐ์ธ์ ์ผ๋ก ์ ๋ฆฌํด ๋ณธ ๋ด์ฉ๋ ์๋ค. ( +์ด๋ฒ ์์ ์ ์งํํ๋ฉด์ ๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฏธ์ง๋ฅผ ์ด๋ป๊ฒ ๋ก๋ํ๋์ง ๊ถ๊ธํด HTML Living Standard๋ฅผ ๋ง์ด ์ฐธ๊ณ ํ๋ค.)
1. ๋ธ๋ผ์ฐ์ ์ ์ด๋ฏธ์ง ์์ฒญ ์ฌ์ดํด
๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฏธ์ง๋ฅผ ํ๋ฉด์ ํ์ํ๊ธฐ๊น์ง์ ๋จ๊ณ๋ ๋ค์๊ณผ ๊ฐ๋ค.
Fetch → Decode → Paint → Composite
- Fetch: ๋คํธ์ํฌ/์บ์์์ ์ด๋ฏธ์ง ํ์ผ(์์ถ ๋ฐ์ดํฐ)์ ๊ฐ์ ธ์ด
- Decode: ๊ฐ์ ธ์จ ์์ถ ๋ฐ์ดํฐ๋ฅผ ํ๋ฉด์ ๋ฟ๋ฆด ์ ์๋ ๋นํธ๋งต์ผ๋ก ํ์ด๋
- Paint: ์์์ ๋นํธ๋งต์ ์น ํจ
- Composite: ์ฌ๋ฌ ๋ ์ด์ด๋ฅผ GPU์์ ํฉ์ฑํด์ ์ต์ข ํ๋ ์์ ๋ง๋ฆ
const img = new Image();
img.src = 'https://example.com/image.png';
์ด ํ ์ค์ด ์คํ๋๋ฉด ๋ธ๋ผ์ฐ์ ๋ ๋ด๋ถ์ ์ผ๋ก ‘image request’๋ผ๋ ์์ฒญ ๊ฐ์ฒด๋ฅผ ์์ฑํด ์บ์๋ฅผ ํ์ธํ๊ณ , ํ์์ ๋คํธ์ํฌ๋ฅผ ํตํด ์ด๋ฏธ์ง๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์์ํ๋ค. ์ด ์์ฒญ์ ์๋ฐ์คํฌ๋ฆฝํธ์ fetch()์๋ ๋ณ๊ฐ์ ์์คํ ์์ ์ฒ๋ฆฌ๋์ง๋ง, ์บ์ ํ์ธ, ์ฐ์ ์์ ์ค์ผ์ค๋ง, ์ค๋ฅ ์ฒ๋ฆฌ ๊ฐ์ ๋จ๊ณ๋ ์ ์ฌํ๊ฒ ๊ฑฐ์น๋ค.
img.src = URL
โโ ์ด๋ฏธ์ง ์
๋ฐ์ดํธ ์๊ณ ๋ฆฌ์ฆ ์คํ
โโ "list of available images" ํ์ธ
โโ HTTP ์บ์ ํ์ธ
โโ ๋คํธ์ํฌ ์์ฒญ ์์ (fetchPriority ๋ฐ์)
โโ ๋ฐ์ดํฐ ์์ ์๋ฃ
โโ load ์ด๋ฒคํธ ๋ฐ์ ← ๋ค์ด๋ก๋ ์๋ฃ, ๋์ฝ๋ฉ X
โ
โโ DOM ์ถ๊ฐ & Paint ํธ๋ฆฌ๊ฑฐ ์์
โโ ๋์ฝ๋ฉ ํ์ ํ์ธ
โ โโ decode() ํธ์ถํ์ผ๋ฉด → ์บ์๋ ๋นํธ๋งต ์ฌ์ฉ
โ โโ ์ ํ์ผ๋ฉด → ์ง๊ธ ๋๊ธฐ ๋์ฝ๋ฉ (๋ฉ์ธ ์ค๋ ๋ ๋ธ๋กํน!)
โโ Paint (๋นํธ๋งต์ ๋ ์ด์ด์ ๊ทธ๋ฆผ)
โโ Composite (GPU ํฉ์ฑ)
+ "list of available images"๋ HTML ํ์ค์ ์ ์๋ ๋ธ๋ผ์ฐ์ ๋ด๋ถ ์บ์ ๊ตฌ์กฐ๋ก, ์ด๋ฏธ ์์ฒญ๋ ์ด๋ฏธ์ง์ ์ํ(๋ค์ด๋ก๋ ์ค, ์๋ฃ, ์คํจ ๋ฑ)๋ฅผ ์ถ์ ํ๋ค. ๊ฐ์ URL์ ์ด๋ฏธ์ง๋ฅผ ๋ค์ ์์ฒญํ๋ฉด ๋คํธ์ํฌ ์์ด ์ด ๋ฆฌ์คํธ์์ ์ฌ์ฌ์ฉํ๋ค.
2. ๋์ฝ๋ฉ๊ณผ ํ์ — decoding vs decode()
์ด๋ฏธ์ง๋ ๋ค์ด๋ก๋ ํ ํ๋ฉด์ ํ์๋๊ธฐ ์ ์ ๋์ฝ๋ฉ๊ณผ์ ์ด ํ์ํ๋ค. ์ด๋ ์์ถ๋ ๋ฐ์ด๋๋ฆฌ๋ฅผ GPU๊ฐ ๋ ๋๋ง ๊ฐ๋ฅํ ๋นํธ๋งต์ผ๋ก ๋ณํํ๋ ๋จ๊ณ๋ค.
- img.decoding = 'async'
→ ๋ธ๋ผ์ฐ์ ์ “๋น๋๊ธฐ๋ก ๋์ฝ๋ฉํด๋ ๋๋ค”๋ ํํธ๋ฅผ ์ค๋ค. - await img.decode()
→ ๋์ฝ๋ฉ์ด ์๋ฃ๋ ๋๊น์ง ๋ช ์์ ์ผ๋ก ๊ธฐ๋ค๋ฆฐ๋ค.
์ฆ, decoding์ ํํธ์ด๊ณ decode()๋ ์คํ ์๋ฏธ๋ก ์ดํดํ๋ฉด ๋๋ค. ์๋ ํจํด์ ์ฐ๋ฉด ๋ ๋๋ง ํ๋์ ์์ด ๋ถ๋๋ฝ๊ฒ ์ด๋ฏธ์ง๋ฅผ ํ์ํ ์ ์๋ค.
const img = new Image();
img.decoding = 'async';
img.src = '/image.png';
await img.decode();
document.body.appendChild(img); // ์์ ํ ๋์ฝ๋ฉ๋ ์ํ์์ ํ์
3. ๋คํธ์ํฌ ์ฐ์ ์์ - fetchPriority
fetchPriority ์์ฑ์ ๋ธ๋ผ์ฐ์ ์ ๋คํธ์ํฌ ์ค์ผ์ค๋ฌ์๊ฒ ๋ฆฌ์์ค ์ฐ์ ์์ ํํธ๋ฅผ ์ค ์ ์๋ค.
img.fetchPriority = 'low';
img.fetchPriority = 'high';
- low: ์ฐ์ ์์๋ฅผ ๋ฎ์ถฐ ๋ค๋ฅธ ๋ฆฌ์์ค ๋ก๋ฉ์ ๋ฐฉํดํ์ง ์์
- high: ์ด๊ธฐ ๋ ๋๋ง์ ์ค์ํ ๋ฆฌ์์ค
- auto: ๋ธ๋ผ์ฐ์ ์ ํ๋จ์ ๋ฐ๋ฆ
4. ๋ธ๋ผ์ฐ์ ์ ์ ํด ์๊ฐ ํ์ฉ — requestIdleCallback
๋ธ๋ผ์ฐ์ ๋ ๋ณดํต ์ด๋น 60 ํ๋ ์ ์๋๋ก ํ๋ฉด์ ๊ทธ๋ฆฌ๋๋ฐ, ๋ง์ฝ ์ด๋ค ํ๋ ์์์ ๋ ๋๋ง ์์ ์ด ์ผ์ฐ ๋๋๋ฉด, ๋จ๋ ์๊ฐ์ด ์๊ธด๋ค. ๊ทธ ์งง์ ์ฌ์ ๊ตฌ๊ฐ์ด ๋ฐ๋ก ๋ธ๋ผ์ฐ์ ์ ์ ํด ์๊ฐ์ด๋ค. requestIdleCallback()์ ๋ฐ๋ก ์ด ๋จ๋ ์๊ฐ ์ฌ๋กฏ์ ํ์ฉํ ์ ์๊ฒ ํด์ฃผ๋ API๋ค.
requestIdleCallback((deadline) => {
while (deadline.timeRemaining() > 0) {
// idle ์๊ฐ ์์์๋ง ์คํํ ์์
}
});
์ฌ๊ธฐ์ deadline.timeRemaining()์ ํ์ฌ ํ๋ ์์ด ๋๋๊ธฐ ์ ๊น์ง ๋จ์ ์๊ฐ์ ๋ฐ๋ฆฌ์ด ๋จ์๋ก ์๋ ค์ค๋ค.
๋ธ๋ผ์ฐ์ ์ idle ์ค์ผ์ค๋ง ๋ฐฉ์
- idle time์ ํ๋ ์๋ง๋ค ๋ค๋ฅด๋ค (๋ ๋๋ง ๋ถํ์ ๋ฐ๋ผ 0~10ms ์์ค)
- idle ์ฝ๋ฐฑ์ ๋จ๋ ์๊ฐ์ด ์์ ๋๋ง ์คํ๋๋ค
- idle ์๊ฐ์ด ์์ผ๋ฉด ์ฝ๋ฐฑ์ ๋ค์ ํ๋ ์์ผ๋ก ๋ฏธ๋ค์ง๋ค
๋ง์ฝ ๋ธ๋ผ์ฐ์ ๊ฐ ์ค๋ซ๋์ idle ์ํ์ ๋ค์ด๊ฐ์ง ์์ผ๋ฉด, { timeout: 2000 } ์ต์ ์ผ๋ก "2์ด ์์๋ ๋ฐ๋์ ์คํํด๋ผ" ๊ฐ์ ์กฐ๊ฑด์ ์ถ๊ฐํ ์ ์๋ค.
requestIdleCallback(preloadImages, { timeout: 2000 });
๋ค๋ง, ํด๋น API๋ ์์ง ๋ชจ๋ ๋ธ๋ผ์ฐ์ ๋ฒ์ ์์ ์ง์๋๋ ๊ฒ์ ์๋๋ค. Electron์ ๋ด์ฅ๋ Chromium ๋ฒ์ ์ ๋ฐ๋ฅด๋๋ฐ, ํ์ฌ ์ฌ์ฉ ์ค์ธ Electron์ด requestIdleCallback์ ์ง์ํ๋ Chromium์ ํฌํจํ๊ณ ์์ด ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์์๋ค.
5. ์ฒญํฌ ๋จ์ ํ๋ฆฌ๋ก๋ฉ
์ด๋ฏธ์ง๋ฅผ ํ๊บผ๋ฒ์ ๋ชจ๋ ์์ฒญํ๋ฉด ๋คํธ์ํฌ๊ฐ ํญ์ฃผํ๊ณ , ๋ ๋๋ง์ ํ์ํ ๋ฆฌ์์ค๊น์ง ๋ฐ๋ฆด ์ ์๋ค. ๊ทธ๋์ ๋ณดํต ์ฒญํฌ ๋จ์๋ก ์ชผ๊ฐ์ด ์ ์ ๋ณ๋ ฌ๋๋ง ์ ์งํ๋ฉด์ ์์ฐจ์ ์ผ๋ก ํ๋ฆฌ๋ก๋ฉํ๊ธฐ ๋๋ฌธ์ ๋ ๋ํ ๋์ผํ๊ฒ ์ ์ฉํ์๋ค. (์๋๋ ๊ฐ๋จํ๊ฒ ์์ฑํด ๋ดค๋ ์์ ์ฝ๋๋ค.)
const PRELOAD_CONFIG = {
// // ํ ๋ฒ์ ์์ฒญํ ์ด๋ฏธ์ง ๊ฐ์
IMAGE_CHUNK_SIZE: 5,
};
async function preloadImageChunks(imageUrls: string[]): Promise<void> {
let failed: string[] = [];
for (let i = 0; i < imageUrls.length; i += PRELOAD_CONFIG.IMAGE_CHUNK_SIZE) {
const chunk = imageUrls.slice(i, i + PRELOAD_CONFIG.IMAGE_CHUNK_SIZE);
await Promise.all(
chunk.map(
url =>
new Promise<void>(resolve => {
const img = new Image();
img.onload = async () => {
// ์ง์๋๋ค๋ฉด ๋์ฝ๋ฉ ์๋ฃ๊น์ง ๋ณด์ฅ
try {
await img.decode?.();
} catch {}
resolve();
};
img.onerror = () => {
// ์คํจํ ์ด๋ฏธ์ง url ์ ๋ฐ๋ก ์ ์ฅ
failed.push(url);
resolve();
};
img.src = url;
}),
),
);
}
}
'๐ WIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
| ๋์์ธ ํ ํฐ ์ค๋ณต ์์ ๊ธฐ (0) | 2025.11.08 |
|---|---|
| ์ด๋ฒ ์ฃผ์ ์๊ฒ ๋ ๊ฒ๋ค (0) | 2025.11.02 |
| @property (1) | 2025.10.18 |
| i18next-parser ์ฌ์ฉํด์ ๋ฒ์ญ ํ๋ก์ธ์ค ๊ฐ์ ํ๊ธฐ (2) | 2025.09.20 |
| Storybook v8 preset ์๋ฌ ํธ๋ฌ๋ธ์ํ (0) | 2025.08.31 |