์ด๋ฏธ์ง€ ํ”„๋ฆฌ๋กœ๋”ฉ์„ ๊ตฌํ˜„ํ•˜๋ฉฐ ์•Œ๊ฒŒ ๋œ ๊ฒƒ๋“ค

์ตœ๊ทผ์— ์ด๋ฏธ์ง€๋ฅผ ํ”„๋ฆฌ๋กœ๋“œํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๋งŒ๋“ค์—ˆ๋‹ค. ํŽ˜์ด์ง€๋„ค์ด์…˜์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ž‘์—…์€ ์ต์ˆ™ํ–ˆ์ง€๋งŒ, ๋‹ค๋ฅธ ํ™”๋ฉด์—์„œ ๋ฏธ๋ฆฌ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฑด ์ฒ˜์Œ์ด๋ผ ๊ตฌํ˜„ํ•˜๋ฉด์„œ ์ƒˆ๋กœ ์•Œ๊ฒŒ ๋œ ๋ถ€๋ถ„๋“ค์„ ๊ฐ„๋‹จํžˆ ์ •๋ฆฌํ•ด ๋ณด์•˜๋‹ค. ์‹ค์ œ๋กœ ์ ์šฉํ•œ ๋‚ด์šฉ๋„ ์žˆ๊ณ , ๊ฐœ์ธ์ ์œผ๋กœ ์ •๋ฆฌํ•ด ๋ณธ ๋‚ด์šฉ๋„ ์žˆ๋‹ค. ( +์ด๋ฒˆ ์ž‘์—…์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ด๋ฏธ์ง€๋ฅผ ์–ด๋–ป๊ฒŒ ๋กœ๋“œํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•ด 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;
          }),
      ),
    );
  }
}