From e797a8a399bc5f0dcee7a2ac9e37bc17752d6c1c Mon Sep 17 00:00:00 2001 From: optrader Date: Wed, 19 Nov 2025 16:45:56 +0900 Subject: [PATCH] [251119] fix: HomePanel,DetailPanel PreLoadImages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ• ์ปค๋ฐ‹ ์‹œ๊ฐ„: 2025. 11. 19. 16:45:55 ๐Ÿ“Š ๋ณ€๊ฒฝ ํ†ต๊ณ„: โ€ข ์ด ํŒŒ์ผ: 5๊ฐœ โ€ข ์ถ”๊ฐ€: +116์ค„ โ€ข ์‚ญ์ œ: -7์ค„ ๐Ÿ“ ์ถ”๊ฐ€๋œ ํŒŒ์ผ: + com.twin.app.shoptime/src/utils/ImagePreloader.js ๐Ÿ“ ์ˆ˜์ •๋œ ํŒŒ์ผ: ~ com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.jsx ~ com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.module.less ~ com.twin.app.shoptime/src/views/HomePanel/HomePanel.jsx ~ com.twin.app.shoptime/src/views/PlayerPanel/PlayerPanel.jsx ๐Ÿ”ง ์ฃผ์š” ๋ณ€๊ฒฝ ๋‚ด์šฉ: โ€ข UI ์ปดํฌ๋„ŒํŠธ ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์„  โ€ข ๊ณตํ†ต ์œ ํ‹ธ๋ฆฌํ‹ฐ ํ•จ์ˆ˜ ์ตœ์ ํ™” โ€ข ์ค‘๊ฐ„ ๊ทœ๋ชจ ๊ธฐ๋Šฅ ๊ฐœ์„  --- .../src/utils/ImagePreloader.js | 122 ++++++++++++++++++ .../DetailPanelBackground.jsx | 56 +++++++- .../DetailPanelBackground.module.less | 15 +++ .../src/views/HomePanel/HomePanel.jsx | 45 +++++++ .../src/views/PlayerPanel/PlayerPanel.jsx | 7 + 5 files changed, 238 insertions(+), 7 deletions(-) create mode 100644 com.twin.app.shoptime/src/utils/ImagePreloader.js diff --git a/com.twin.app.shoptime/src/utils/ImagePreloader.js b/com.twin.app.shoptime/src/utils/ImagePreloader.js new file mode 100644 index 00000000..ab11bee8 --- /dev/null +++ b/com.twin.app.shoptime/src/utils/ImagePreloader.js @@ -0,0 +1,122 @@ +/** + * ์ด๋ฏธ์ง€ ํ”„๋ฆฌ๋กœ๋” ์œ ํ‹ธ๋ฆฌํ‹ฐ + * HomePanel์—์„œ ๋ฐฑ๊ทธ๋ผ์šด๋“œ๋กœ DetailPanelBackground ์ด๋ฏธ์ง€๋“ค์„ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•˜์—ฌ + * DetailPanel ์ง„์ž… ์‹œ ๋กœ๋”ฉ ์ง€์—ฐ์„ ๋ฐฉ์ง€ + */ +class ImagePreloader { + constructor() { + this.cache = new Map(); // ๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ ์บ์‹œ + this.loadPromises = new Map(); // ๋กœ๋”ฉ ์ค‘์ธ Promise ๊ด€๋ฆฌ + this.preloadStarted = false; // ํ”„๋ฆฌ๋กœ๋”ฉ ์‹œ์ž‘ ์—ฌ๋ถ€ + } + + /** + * ๋‹จ์ผ ์ด๋ฏธ์ง€ ํ”„๋ฆฌ๋กœ๋“œ + * @param {string} src - ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ + * @returns {Promise} + */ + preloadImage(src) { + // ์ด๋ฏธ ์บ์‹œ๋œ ๊ฒฝ์šฐ ์ฆ‰์‹œ ๋ฐ˜ํ™˜ + if (this.cache.has(src)) { + return Promise.resolve(this.cache.get(src)); + } + + // ํ˜„์žฌ ๋กœ๋”ฉ ์ค‘์ธ Promise๊ฐ€ ์žˆ์œผ๋ฉด ์žฌ์‚ฌ์šฉ + if (this.loadPromises.has(src)) { + return this.loadPromises.get(src); + } + + // ์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€ ๋กœ๋“œ Promise ์ƒ์„ฑ + const promise = new Promise((resolve, reject) => { + const img = new window.Image(); // ESLint ํ•ด๊ฒฐ์„ ์œ„ํ•ด window.Image ์‚ฌ์šฉ + + img.onload = () => { + this.cache.set(src, img); + this.loadPromises.delete(src); + console.log(`[ImagePreloader] Image loaded: ${src}`); + resolve(img); + }; + + img.onerror = () => { + this.loadPromises.delete(src); + console.error(`[ImagePreloader] Failed to load: ${src}`); + reject(new Error(`Failed to load image: ${src}`)); + }; + + // ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹œ์ž‘ + img.src = src; + }); + + this.loadPromises.set(src, promise); + return promise; + } + + /** + * ์—ฌ๋Ÿฌ ์ด๋ฏธ์ง€ ํ•œ๊บผ๋ฒˆ์— ํ”„๋ฆฌ๋กœ๋“œ + * @param {Object} imageMap - { patnrId: imagePath } ํ˜•ํƒœ์˜ ๋งต + * @returns {Promise} - ๋กœ๋“œ ๊ฒฐ๊ณผ ๋ฐฐ์—ด + */ + preloadAllImages(imageMap) { + if (this.preloadStarted) { + console.log('[ImagePreloader] Preloading already started'); + return Promise.resolve([]); + } + + this.preloadStarted = true; + console.log('[ImagePreloader] Starting background preload...'); + + const promises = Object.values(imageMap).map(src => + this.preloadImage(src).catch(error => { + // ๊ฐœ๋ณ„ ์ด๋ฏธ์ง€ ๋กœ๋“œ ์‹คํŒจ ์‹œ ์ „์ฒด ์ž‘์—…์„ ์ค‘๋‹จํ•˜์ง€ ์•Š์Œ + console.warn('[ImagePreloader] Single image load failed:', error.message); + return null; + }) + ); + + return Promise.all(promises); + } + + /** + * ์ด๋ฏธ์ง€๊ฐ€ ๋กœ๋“œ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ + * @param {string} src - ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ + * @returns {boolean} + */ + isLoaded(src) { + return this.cache.has(src); + } + + /** + * ์บ์‹œ๋œ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ + * @param {string} src - ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ + * @returns {HTMLImageElement|null} + */ + getCachedImage(src) { + return this.cache.get(src) || null; + } + + /** + * ์บ์‹œ ํ†ต๊ณ„ ์ •๋ณด + * @returns {Object} + */ + getStats() { + return { + cached: this.cache.size, + loading: this.loadPromises.size, + preloadStarted: this.preloadStarted + }; + } + + /** + * ์บ์‹œ ์ดˆ๊ธฐํ™” (ํ…Œ์ŠคํŠธ์šฉ) + */ + clearCache() { + this.cache.clear(); + this.loadPromises.clear(); + this.preloadStarted = false; + } +} + +// ์‹ฑ๊ธ€ํ†ค ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ +const imagePreloader = new ImagePreloader(); + +export default imagePreloader; \ No newline at end of file diff --git a/com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.jsx b/com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.jsx index 5407bdcf..1abb1205 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/components/DetailPanelBackground/DetailPanelBackground.jsx @@ -2,8 +2,11 @@ import React, { useEffect, useMemo, + useState, } from 'react'; +import ImagePreloader from '../../../../utils/ImagePreloader'; + import hsn from '../../../../../assets/images/bg/hsn_new.png'; import koreaKiosk from '../../../../../assets/images/bg/koreaKiosk_new.png'; import lgelectronics @@ -24,10 +27,7 @@ import css from './DetailPanelBackground.module.less'; * - ์ด ๊ฐ’์— ๋”ฐ๋ผ ๋ฐฐ๊ฒฝ UI๋ฅผ ๋‹ค๋ฅด๊ฒŒ ํ‘œ์‹œํ•  ์ˆ˜ ์žˆ์Œ */ export default function DetailPanelBackground({ launchedFromPlayer = false, patnrId }) { - useEffect(() => { - console.log('[DetailPanelBackground] ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ:', detailPanelBg); - console.log('[DetailPanelBackground] launchedFromPlayer:', launchedFromPlayer); - }, [launchedFromPlayer]); + const [imageReady, setImageReady] = useState(false); const BG_MAP = { 1: qvc, @@ -43,12 +43,54 @@ export default function DetailPanelBackground({ launchedFromPlayer = false, patn return BG_MAP[patnrId] || qvc; }, [patnrId]); - //partnrId 1 = QVC, 2 = HSN, 4 = ONTV, 9 = LG ELECTRONICS, 11 = SHOPLC, 19 = PINKPONG, 16 = KOREA KIOSK, + // โœ… [251119] ํ”„๋ฆฌ๋กœ๋“œ๋œ ์ด๋ฏธ์ง€ ์‚ฌ์šฉ ๋กœ์ง + useEffect(() => { + if (launchedFromPlayer) { + // PlayerPanel์—์„œ ์ง„์ž…ํ•œ ๊ฒฝ์šฐ ์ด๋ฏธ์ง€๊ฐ€ ํ•„์š” ์—†์Œ + setImageReady(true); + return; + } + + // ์ด๋ฏธ์ง€๊ฐ€ ํ”„๋ฆฌ๋กœ๋“œ๋˜์—ˆ๋Š”์ง€ ํ™•์ธ + if (ImagePreloader.isLoaded(detailPanelBg)) { + console.log('[DetailPanelBackground] Using preloaded image:', detailPanelBg); + setImageReady(true); + } else { + // ํ”„๋ฆฌ๋กœ๋“œ๋˜์ง€ ์•Š์•˜๋‹ค๋ฉด ์ฆ‰์‹œ ๋กœ๋“œ ์‹œ๋„ + console.log('[DetailPanelBackground] Image not preloaded, loading on-demand:', detailPanelBg); + ImagePreloader.preloadImage(detailPanelBg) + .then(() => { + console.log('[DetailPanelBackground] On-demand image loaded:', detailPanelBg); + setImageReady(true); + }) + .catch((e) => { + console.error('[DetailPanelBackground] On-demand image load failed:', e); + // ์‹คํŒจํ•ด๋„ ์ด๋ฏธ์ง€๋ฅผ ํ‘œ์‹œํ•ด์•ผ ํ•จ + setImageReady(true); + }); + } + }, [detailPanelBg, launchedFromPlayer]); + + useEffect(() => { + console.log('[DetailPanelBackground] ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ ๊ฒฝ๋กœ:', detailPanelBg); + console.log('[DetailPanelBackground] launchedFromPlayer:', launchedFromPlayer); + console.log('[DetailPanelBackground] imageReady:', imageReady); + }, [detailPanelBg, launchedFromPlayer, imageReady]); + + //partnrId 1 = QVC, 2 = HSN, 4 = ONTV, 9 = LG ELECTRONICS, 11 = SHOPLC, 19 = PINKPONG, 16 = KOREA KIOSK, return (
- {/* ์‹ค์ œ ๋ฐฐ๊ฒฝ ์ด๋ฏธ์ง€ */} - {!launchedFromPlayer && ( + {/* ์ด๋ฏธ์ง€๊ฐ€ ์ค€๋น„๋˜์ง€ ์•Š์•˜์„ ๋•Œ placeholder ํ‘œ์‹œ */} + {!imageReady && !launchedFromPlayer && ( +