From 1166311e4b2dca51ea6e2913ac4b2604e229af08 Mon Sep 17 00:00:00 2001 From: optrader Date: Wed, 12 Nov 2025 15:28:33 +0900 Subject: [PATCH] [251112] feat: ProductVideroV2 Continuous Video Playing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ• ์ปค๋ฐ‹ ์‹œ๊ฐ„: 2025. 11. 12. 15:28:31 ๐Ÿ“Š ๋ณ€๊ฒฝ ํ†ต๊ณ„: โ€ข ์ด ํŒŒ์ผ: 2๊ฐœ โ€ข ์ถ”๊ฐ€: +67์ค„ โ€ข ์‚ญ์ œ: -31์ค„ ๐Ÿ“ ์ˆ˜์ •๋œ ํŒŒ์ผ: ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.module.less ~ com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx ๐Ÿ”ง ์ฃผ์š” ๋ณ€๊ฒฝ ๋‚ด์šฉ: โ€ข ์†Œ๊ทœ๋ชจ ๊ธฐ๋Šฅ ๊ฐœ์„  --- .../ProductVideo/ProductVideo.module.less | 10 + .../ProductVideo/ProductVideo.v2.jsx | 389 ++++++++++-------- 2 files changed, 236 insertions(+), 163 deletions(-) diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.module.less b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.module.less index c14be034..9ffffb01 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.module.less +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.module.less @@ -143,6 +143,16 @@ pointer-events: auto; } +.hideWhileFullscreen { + visibility: hidden; + pointer-events: none; +} + +.videoPortalHost { + width: 100%; + height: 100%; +} + // ์ „์ฒดํ™”๋ฉด container (Portal, body์— ํ•ญ์ƒ ์กด์žฌ) .fullscreenContainer { position: fixed; diff --git a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx index 7afbd9e3..8fe246c3 100644 --- a/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx +++ b/com.twin.app.shoptime/src/views/DetailPanel/ProductContentSection/ProductVideo/ProductVideo.v2.jsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import React, { useCallback, useEffect, useLayoutEffect, useMemo, useState, useRef } from 'react'; import ReactDOM from 'react-dom'; import { useDispatch, useSelector } from 'react-redux'; import Spottable from '@enact/spotlight/Spottable'; @@ -24,7 +24,11 @@ import { stopMediaAutoClose, resetMediaAutoClose, } from '../../../../actions/mediaOverlayActions'; -import { pauseFullscreenVideo, resumeFullscreenVideo, clearAllVideoTimers } from '../../../../actions/playActions'; +import { + pauseFullscreenVideo, + resumeFullscreenVideo, + clearAllVideoTimers, +} from '../../../../actions/playActions'; import css from './ProductVideo.module.less'; const SpottableComponent = Spottable('div'); @@ -80,6 +84,16 @@ export function ProductVideoV2({ const videoPlayerWrapperRef = useRef(null); const normalContainerRef = useRef(null); const fullscreenContainerRef = useRef(null); + const videoPortalHostRef = useRef(null); + + const ensurePortalHost = useCallback(() => { + if (!videoPortalHostRef.current && typeof document !== 'undefined') { + const host = document.createElement('div'); + host.className = css.videoPortalHost; + videoPortalHostRef.current = host; + } + return videoPortalHostRef.current; + }, []); // ๋น„๋””์˜ค ์žฌ์ƒ ๊ฐ€๋Šฅ ์—ฌ๋ถ€ ์ฒดํฌ const canPlayVideo = useMemo(() => { @@ -149,33 +163,36 @@ export function ProductVideoV2({ }, [canPlayVideo, isPlaying]); // ์ธ๋„ค์ผ ํด๋ฆญ ํ•ธ๋“ค๋Ÿฌ - ๋น„๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘ + Redux dispatch + MediaPlayer ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ - const handleThumbnailClick = useCallback((e) => { - console.log('๐ŸŽฌ [handleThumbnailClick] ์ธ๋„ค์ผ ํด๋ฆญ๋จ', { - canPlayVideo, - isPlaying, - eventType: e?.type, - target: e?.target?.className, - bubbles: e?.bubbles, - timestamp: new Date().getTime(), - }); - if (canPlayVideo && !isPlaying) { - // console.log('[BgVideo] ProductVideoV2 - Starting video playback'); - console.log('๐ŸŽฌ [handleThumbnailClick] โœ… ๋น„๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘'); - setIsPlaying(true); + const handleThumbnailClick = useCallback( + (e) => { + console.log('๐ŸŽฌ [handleThumbnailClick] ์ธ๋„ค์ผ ํด๋ฆญ๋จ', { + canPlayVideo, + isPlaying, + eventType: e?.type, + target: e?.target?.className, + bubbles: e?.bubbles, + timestamp: new Date().getTime(), + }); + if (canPlayVideo && !isPlaying) { + // console.log('[BgVideo] ProductVideoV2 - Starting video playback'); + console.log('๐ŸŽฌ [handleThumbnailClick] โœ… ๋น„๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘'); + setIsPlaying(true); - // ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ „์ฒดํ™”๋ฉด ๋น„๋””์˜ค ์ผ์‹œ์ •์ง€ - // console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video'); - dispatch(pauseFullscreenVideo()); + // ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ „์ฒดํ™”๋ฉด ๋น„๋””์˜ค ์ผ์‹œ์ •์ง€ + // console.log('[BgVideo] ProductVideoV2 - Pausing background fullscreen video'); + dispatch(pauseFullscreenVideo()); - // Redux: mediaOverlay ์ƒํƒœ ์ดˆ๊ธฐํ™” (MediaPlayer ์ „์šฉ) - dispatch(setMediaControlShow()); - dispatch(startMediaAutoClose(3000)); - // MediaPlayer ์ง์ ‘ ์ œ์–ด: controls ํ‘œ์‹œ - setTimeout(() => { - videoPlayerRef.current?.showControls?.(); - }, 100); - } - }, [canPlayVideo, isPlaying, dispatch]); + // Redux: mediaOverlay ์ƒํƒœ ์ดˆ๊ธฐํ™” (MediaPlayer ์ „์šฉ) + dispatch(setMediaControlShow()); + dispatch(startMediaAutoClose(3000)); + // MediaPlayer ์ง์ ‘ ์ œ์–ด: controls ํ‘œ์‹œ + setTimeout(() => { + videoPlayerRef.current?.showControls?.(); + }, 100); + } + }, + [canPlayVideo, isPlaying, dispatch] + ); // ๋น„๋””์˜ค ์ข…๋ฃŒ ํ•ธ๋“ค๋Ÿฌ - ์ธ๋„ค์ผ๋กœ ๋ณต๊ท€ + Redux cleanup + MediaPlayer ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ const handleVideoEnded = useCallback(() => { @@ -355,7 +372,7 @@ export function ProductVideoV2({ // FullScreen ๋ชจ๋“œ์—์„œ์˜ MediaPlayer Click ํ•ธ๋“ค๋Ÿฌ const handleVideoPlayerClick = useCallback( - (e) => { + (e) => { if (!isPlaying) return; if (!isFullscreen) { @@ -363,7 +380,7 @@ export function ProductVideoV2({ e.stopPropagation?.(); toggleFullscreen(); return; - } + } // fullscreen: overlay toggle e.preventDefault?.(); e.stopPropagation?.(); @@ -372,10 +389,10 @@ export function ProductVideoV2({ const isCurrentlyVisible = videoPlayerRef.current?.areControlsVisible?.(); if (isCurrentlyVisible) { videoPlayerRef.current?.hideControls?.(); - dispatch(setMediaControlHide()); // MediaOverlay Redux ์ƒํƒœ ๋™๊ธฐํ™” + dispatch(setMediaControlHide()); // MediaOverlay Redux ์ƒํƒœ ๋™๊ธฐํ™” } else { videoPlayerRef.current?.showControls?.(); - dispatch(setMediaControlShow()); // MediaOverlay Redux ์ƒํƒœ ๋™๊ธฐํ™” + dispatch(setMediaControlShow()); // MediaOverlay Redux ์ƒํƒœ ๋™๊ธฐํ™” } }, [isPlaying, isFullscreen, toggleFullscreen, dispatch, mediaOverlayState.controls?.visible] @@ -394,34 +411,40 @@ export function ProductVideoV2({ }, [mediaOverlayState.controls?.visible, dispatch]); // ์ „์ฒด ํ™”๋ฉด ์ปจํ…Œ์ด๋„ˆ์šฉ ๋งˆ์šฐ์Šค ๋‹ค์šด ํ•ธ๋“ค๋Ÿฌ (Capture Phase) - const handleFullscreenContainerMouseDownCapture = useCallback((e) => { - if (!isPlaying || !isFullscreen) return; + const handleFullscreenContainerMouseDownCapture = useCallback( + (e) => { + if (!isPlaying || !isFullscreen) return; - e.preventDefault(); - e.stopPropagation(); - // toggleOverlayVisibility(); - }, [isPlaying, isFullscreen, toggleOverlayVisibility]); - - // ์ „์ฒด ํ™”๋ฉด ํ‚ค๋ณด๋“œ ํ•ธ๋“ค๋Ÿฌ - const handleFullscreenKeyDown = useCallback((e) => { - if (!isPlaying || !isFullscreen) return; - - // ESC ํ‚ค๋งŒ ์˜ค๋ฒ„๋ ˆ์ด ํ† ๊ธ€๋กœ ์ฒ˜๋ฆฌ - if (e.key === 'Escape' || e.keyCode === 27) { - console.log('๐Ÿ–ฅ๏ธ [Fullscreen Container] ESC ํ‚ค - ์˜ค๋ฒ„๋ ˆ์ด ํ† ๊ธ€ ์‹คํ–‰'); e.preventDefault(); e.stopPropagation(); - toggleOverlayVisibility(); - return; - } + // toggleOverlayVisibility(); + }, + [isPlaying, isFullscreen, toggleOverlayVisibility] + ); - // Enter ํ‚ค๋Š” ๊ธฐ๋ณธ ๋™์ž‘ ํ—ˆ์šฉ (ํฌ์ปค์Šค๋œ ์š”์†Œ์˜ ๋™์ž‘ ์ˆ˜ํ–‰) - if (e.key === 'Enter' || e.keyCode === 13) { - console.log('๐Ÿ–ฅ๏ธ [Fullscreen Container] Enter ํ‚ค - ํฌ์ปค์Šค๋œ ์š”์†Œ ๋™์ž‘ ํ—ˆ์šฉ'); - // Enter ํ‚ค๋Š” preventDefaultํ•˜์ง€ ์•Š๊ณ  ๊ธฐ๋ณธ ๋™์ž‘ ํ—ˆ์šฉ - return; - } - }, [isPlaying, isFullscreen, toggleOverlayVisibility]); + // ์ „์ฒด ํ™”๋ฉด ํ‚ค๋ณด๋“œ ํ•ธ๋“ค๋Ÿฌ + const handleFullscreenKeyDown = useCallback( + (e) => { + if (!isPlaying || !isFullscreen) return; + + // ESC ํ‚ค๋งŒ ์˜ค๋ฒ„๋ ˆ์ด ํ† ๊ธ€๋กœ ์ฒ˜๋ฆฌ + if (e.key === 'Escape' || e.keyCode === 27) { + console.log('๐Ÿ–ฅ๏ธ [Fullscreen Container] ESC ํ‚ค - ์˜ค๋ฒ„๋ ˆ์ด ํ† ๊ธ€ ์‹คํ–‰'); + e.preventDefault(); + e.stopPropagation(); + toggleOverlayVisibility(); + return; + } + + // Enter ํ‚ค๋Š” ๊ธฐ๋ณธ ๋™์ž‘ ํ—ˆ์šฉ (ํฌ์ปค์Šค๋œ ์š”์†Œ์˜ ๋™์ž‘ ์ˆ˜ํ–‰) + if (e.key === 'Enter' || e.keyCode === 13) { + console.log('๐Ÿ–ฅ๏ธ [Fullscreen Container] Enter ํ‚ค - ํฌ์ปค์Šค๋œ ์š”์†Œ ๋™์ž‘ ํ—ˆ์šฉ'); + // Enter ํ‚ค๋Š” preventDefaultํ•˜์ง€ ์•Š๊ณ  ๊ธฐ๋ณธ ๋™์ž‘ ํ—ˆ์šฉ + return; + } + }, + [isPlaying, isFullscreen, toggleOverlayVisibility] + ); // ๋งˆ์šฐ์Šค ๋‹ค์šด (ํด๋ฆญ) ์ด๋ฒคํŠธ - capture phase์—์„œ ์ฒ˜๋ฆฌ const handleVideoPlayerMouseDown = useCallback( @@ -466,20 +489,24 @@ export function ProductVideoV2({ useEffect(() => { const handleDocumentClick = (e) => { // ProductVideoV2 ๊ด€๋ จ ์š”์†Œ์ธ ๊ฒฝ์šฐ๋งŒ ๋กœ๊น… - const isVideoElement = e.target?.closest('[class*="videoContainer"]') || - e.target?.closest('[class*="videoPlayer"]') || - e.target?.closest('[class*="videoThumbnail"]'); + const isVideoElement = + e.target?.closest('[class*="videoContainer"]') || + e.target?.closest('[class*="videoPlayer"]') || + e.target?.closest('[class*="videoThumbnail"]'); if (isVideoElement) { console.log('๐Ÿ“„ [Document Level] ์ „์—ญ ํด๋ฆญ ๊ฐ์ง€๋จ', { eventPhase: e.eventPhase, bubbles: e.bubbles, target: e.target?.className, - eventPath: e.composedPath?.().slice(0, 6).map(el => ({ - tag: el.tagName, - className: el.className, - id: el.id - })) + eventPath: e + .composedPath?.() + .slice(0, 6) + .map((el) => ({ + tag: el.tagName, + className: el.className, + id: el.id, + })), }); } }; @@ -490,6 +517,36 @@ export function ProductVideoV2({ }; }, []); + useLayoutEffect(() => { + if (!isPlaying) return; + + const host = ensurePortalHost(); + const target = isFullscreen ? fullscreenContainerRef.current : normalContainerRef.current; + + if (host && target && host.parentNode !== target) { + target.appendChild(host); + } + }, [isPlaying, isFullscreen, ensurePortalHost]); + + useEffect(() => { + if (isPlaying) return; + + const host = videoPortalHostRef.current; + if (host?.parentNode) { + host.parentNode.removeChild(host); + } + }, [isPlaying]); + + useEffect(() => { + return () => { + const host = videoPortalHostRef.current; + if (host?.parentNode) { + host.parentNode.removeChild(host); + } + videoPortalHostRef.current = null; + }; + }, []); + // autoPlay ๊ธฐ๋Šฅ: ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ํ›„ 500ms ํ›„ ์ž๋™ ์žฌ์ƒ useEffect(() => { if (autoPlay && canPlayVideo && !isPlaying) { @@ -595,17 +652,10 @@ export function ProductVideoV2({ const containerProps = useMemo(() => { const baseProps = { - onClick: handleVideoContainerClick, // ๋ชจ๋“  ์ƒํƒœ์—์„œ ๋™์ผํ•˜๊ฒŒ ์ ์šฉ + onClick: handleVideoContainerClick, }; - if (isFullscreen) { - return { - ...baseProps, - spotlightRestrict: 'self-only', // ํฌ์ปค์Šค๊ฐ€ ๋ฐ–์œผ๋กœ ๋‚˜๊ฐ€์ง€ ์•Š๋„๋ก - spotlightId: 'product-video-v2-fullscreen', - onSpotlightDown: handleSpotlightDown, // ์ „์ฒดํ™”๋ฉด์—์„œ๋„ Down ํ‚ค ๋™์ž‘ - }; - } else if (isPlaying) { + if (isPlaying) { return { ...baseProps, spotlightId: 'product-video-v2-playing', @@ -615,70 +665,89 @@ export function ProductVideoV2({ } else { return baseProps; } - }, [isFullscreen, isPlaying, handleVideoContainerClick, handleSpotlightDown, handleContainerKeyDown]); + }, [ + isFullscreen, + isPlaying, + handleVideoContainerClick, + handleSpotlightDown, + handleContainerKeyDown, + ]); // โš ๏ธ ๊ฐ„๋‹จํ•œ ํ•ด๊ฒฐ์ฑ…: ๋น„๋””์˜ค ์˜์—ญ ํด๋ฆญ ์‹œ ์ง์ ‘ ๋™์ž‘ ์‹คํ–‰ - const handleContainerClickFallback = useCallback((e) => { - const isThumbnailArea = e.target?.closest('[class*="videoThumbnail"]') || - e.target?.closest('[class*="playButton"]'); + const handleContainerClickFallback = useCallback( + (e) => { + const isThumbnailArea = + e.target?.closest('[class*="videoThumbnail"]') || + e.target?.closest('[class*="playButton"]'); - const isVideoPlayerArea = e.target?.closest('[class*="videoPlayer"]') || - e.target?.closest('[class*="VideoPlayer"]'); + const isVideoPlayerArea = + e.target?.closest('[class*="videoPlayer"]') || e.target?.closest('[class*="VideoPlayer"]'); - // ์ธ๋„ค์ผ ํด๋ฆญ: ๋น„๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘ - if (isThumbnailArea && !isPlaying) { - console.log('๐ŸŽฌ [handleContainerClickFallback] ์ธ๋„ค์ผ ํด๋ฆญ โ†’ ๋น„๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘'); - handleThumbnailClick(e); - } + // ์ธ๋„ค์ผ ํด๋ฆญ: ๋น„๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘ + if (isThumbnailArea && !isPlaying) { + console.log('๐ŸŽฌ [handleContainerClickFallback] ์ธ๋„ค์ผ ํด๋ฆญ โ†’ ๋น„๋””์˜ค ์žฌ์ƒ ์‹œ์ž‘'); + handleThumbnailClick(e); + } - // ๋น„๋””์˜ค ์žฌ์ƒ ์ค‘ ํด๋ฆญ: ์ „์ฒดํ™”๋ฉด ํ† ๊ธ€ - if (isVideoPlayerArea && isPlaying && !isFullscreen) { - console.log('๐ŸŽฌ [handleContainerClickFallback] ๋น„๋””์˜ค ํด๋ฆญ โ†’ ์ „์ฒดํ™”๋ฉด ํ† ๊ธ€'); - toggleFullscreen(); - } - }, [isPlaying, isFullscreen, handleThumbnailClick, toggleFullscreen]); + // ๋น„๋””์˜ค ์žฌ์ƒ ์ค‘ ํด๋ฆญ: ์ „์ฒดํ™”๋ฉด ํ† ๊ธ€ + if (isVideoPlayerArea && isPlaying && !isFullscreen) { + console.log('๐ŸŽฌ [handleContainerClickFallback] ๋น„๋””์˜ค ํด๋ฆญ โ†’ ์ „์ฒดํ™”๋ฉด ํ† ๊ธ€'); + toggleFullscreen(); + } + }, + [isPlaying, isFullscreen, handleThumbnailClick, toggleFullscreen] + ); // โš ๏ธ ํ•ต์‹ฌ: TScrollerDetail์ด capture phase์—์„œ ์ด๋ฒคํŠธ๋ฅผ ๋ง‰์œผ๋ฏ€๋กœ, // ProductVideoV2 ์ปจํ…Œ์ด๋„ˆ ์ „์ฒด์—์„œ capture phase๋ฅผ ๋‹ค์‹œ ์—ด์–ด์คŒ (Hooks๋Š” early return ์ „์—) - const handleContainerMouseDownCapture = useCallback((e) => { - // ๋น„๋””์˜ค ์˜์—ญ์ธ์ง€ ํ™•์ธ - const isVideoArea = e.currentTarget === containerRef.current; + const handleContainerMouseDownCapture = useCallback( + (e) => { + // ๋น„๋””์˜ค ์˜์—ญ์ธ์ง€ ํ™•์ธ + const isVideoArea = e.currentTarget === containerRef.current; - console.log('๐Ÿ”“ [ProductVideoV2 Container] onMouseDownCapture - TScrollerDetail ์ฐจ๋‹จ ์šฐํšŒ', { - isVideoArea, - target: e.target?.className, - isPlaying, - isFullscreen, - }); + console.log('๐Ÿ”“ [ProductVideoV2 Container] onMouseDownCapture - TScrollerDetail ์ฐจ๋‹จ ์šฐํšŒ', { + isVideoArea, + target: e.target?.className, + isPlaying, + isFullscreen, + }); - // ์ „์ฒด ํ™”๋ฉด ๋ชจ๋“œ๊ฐ€ ์•„๋‹ ๋•Œ๋งŒ toggleFullscreen ํ˜ธ์ถœ - if (!isFullscreen) { - toggleFullscreen(); + // ์ „์ฒด ํ™”๋ฉด ๋ชจ๋“œ๊ฐ€ ์•„๋‹ ๋•Œ๋งŒ toggleFullscreen ํ˜ธ์ถœ + if (!isFullscreen) { + toggleFullscreen(); - // TScrollerDetail์˜ onClick ์‹คํ–‰์„ ๋ง‰๊ธฐ ์œ„ํ•ด preventDefault ํ˜ธ์ถœ - if (isPlaying) { - console.log('๐Ÿ”“ [ProductVideoV2] TScrollerDetail onClick ์‹คํ–‰ ์ฐจ๋‹จ'); - e.preventDefault(); - e.stopPropagation(); + // TScrollerDetail์˜ onClick ์‹คํ–‰์„ ๋ง‰๊ธฐ ์œ„ํ•ด preventDefault ํ˜ธ์ถœ + if (isPlaying) { + console.log('๐Ÿ”“ [ProductVideoV2] TScrollerDetail onClick ์‹คํ–‰ ์ฐจ๋‹จ'); + e.preventDefault(); + e.stopPropagation(); + } + } else { + // ์ „์ฒด ํ™”๋ฉด ๋ชจ๋“œ์—์„œ๋Š” ์˜ค๋ฒ„๋ ˆ์ด(์ปจํŠธ๋กค)๋งŒ ํ† ๊ธ€ + console.log('๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-Toggle] ์ „์ฒด ํ™”๋ฉด ๋ชจ๋“œ - ์˜ค๋ฒ„๋ ˆ์ด ํ† ๊ธ€ ์‹œ์ž‘'); + console.log( + '๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-Toggle] ํ˜„์žฌ ์˜ค๋ฒ„๋ ˆ์ด ์ƒํƒœ:', + overlayState.controls?.visible + ); + + try { + const result = dispatch(toggleControls()); + console.log( + '๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-Toggle] toggleControls ์•ก์…˜ ๋””์ŠคํŒจ์น˜ ๊ฒฐ๊ณผ:', + result + ); + + // ๋””์ŠคํŒจ์น˜ ํ›„ ์ƒํƒœ ๋ณ€ํ™” ํ™•์ธ (setTimeout์œผ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ) + setTimeout(() => { + console.log('๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-Toggle] ์•ก์…˜ ๋””์ŠคํŒจ์น˜ ํ›„ ์ƒํƒœ ํ™•์ธ ํ•„์š”'); + }, 10); + } catch (error) { + console.error('๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-ERROR] toggleControls ๋””์ŠคํŒจ์น˜ ์—๋Ÿฌ:', error); + } } - } else { - // ์ „์ฒด ํ™”๋ฉด ๋ชจ๋“œ์—์„œ๋Š” ์˜ค๋ฒ„๋ ˆ์ด(์ปจํŠธ๋กค)๋งŒ ํ† ๊ธ€ - console.log('๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-Toggle] ์ „์ฒด ํ™”๋ฉด ๋ชจ๋“œ - ์˜ค๋ฒ„๋ ˆ์ด ํ† ๊ธ€ ์‹œ์ž‘'); - console.log('๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-Toggle] ํ˜„์žฌ ์˜ค๋ฒ„๋ ˆ์ด ์ƒํƒœ:', overlayState.controls?.visible); - - try { - const result = dispatch(toggleControls()); - console.log('๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-Toggle] toggleControls ์•ก์…˜ ๋””์ŠคํŒจ์น˜ ๊ฒฐ๊ณผ:', result); - - // ๋””์ŠคํŒจ์น˜ ํ›„ ์ƒํƒœ ๋ณ€ํ™” ํ™•์ธ (setTimeout์œผ๋กœ ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ) - setTimeout(() => { - console.log('๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-Toggle] ์•ก์…˜ ๋””์ŠคํŒจ์น˜ ํ›„ ์ƒํƒœ ํ™•์ธ ํ•„์š”'); - }, 10); - } catch (error) { - console.error('๐Ÿ–ฅ๏ธ [ProductVideoV2.OVERLAY-ERROR] toggleControls ๋””์ŠคํŒจ์น˜ ์—๋Ÿฌ:', error); - } - } - }, [isPlaying, isFullscreen, dispatch, toggleControls]); + }, + [isPlaying, isFullscreen, dispatch, toggleControls] + ); // Early return: ๋น„๋””์˜ค ์žฌ์ƒ ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ if (!canPlayVideo) return null; @@ -687,17 +756,16 @@ export function ProductVideoV2({ // ์ „์ฒดํ™”๋ฉด: SpotlightContainer (self-only) // ์ผ๋ฐ˜ ์žฌ์ƒ: SpottableComponent (ํฌ์ปค์Šค ๊ฐ€๋Šฅ) // ์ธ๋„ค์ผ: div - const ContainerComponent = isFullscreen - ? SpotlightContainer - : isPlaying - ? SpottableComponent - : 'div'; + const ContainerComponent = isPlaying ? SpottableComponent : 'div'; // VideoPlayer ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ํ•จ์ˆ˜ const renderVideoPlayer = () => { if (!isPlaying) return null; - return ( + const host = ensurePortalHost(); + if (!host) return null; + + return ReactDOM.createPortal(
{ // โš ๏ธ ํ•ต์‹ฌ: VideoPlayer ๋‚ด๋ถ€(์‹ค์ œ ๋น„๋””์˜ค ์˜์—ญ)์—์„œ๋งŒ ์ด๋ฒคํŠธ๋ฅผ ์‚ด๋ฆฌ๊ณ , // ๊ทธ ์™ธ wrapper ์˜์—ญ์—์„œ๋งŒ preventDefault - const isVideoElement = e.target?.closest('[class*="videoPlayer"]') || - e.target?.closest('video') || - e.target?.closest('[class*="react-player"]'); + const isVideoElement = + e.target?.closest('[class*="videoPlayer"]') || + e.target?.closest('video') || + e.target?.closest('[class*="react-player"]'); console.log('๐ŸŽฌ [videoPlayerWrapper] onMouseDownCapture ์‹คํ–‰๋จ', { isFullscreen, @@ -777,7 +846,8 @@ export function ProductVideoV2({ )} -
+ , + host ); }; @@ -787,7 +857,7 @@ export function ProductVideoV2({ @@ -818,8 +888,7 @@ export function ProductVideoV2({ - ) : !isFullscreen ? ( - // ์ผ๋ฐ˜ ์žฌ์ƒ ๋ชจ๋“œ: VideoPlayer๋ฅผ ์ง์ ‘ ๋ Œ๋”๋ง + ) : (
{ - // โš ๏ธ ํ•ต์‹ฌ: VideoPlayer ์˜์—ญ ๋‚ด์—์„œ๋งŒ ์ด๋ฒคํŠธ๋ฅผ ์‚ด๋ฆฌ๊ณ , - // ๊ทธ ์™ธ ์˜์—ญ(์Šคํฌ๋กค ์˜์—ญ)์—์„œ๋งŒ preventDefault - const isVideoPlayerArea = e.target?.closest('[class*="videoPlayer"]') || - e.target?.closest('[class*="VideoPlayer"]'); + const isVideoPlayerArea = + e.target?.closest('[class*="videoPlayer"]') || + e.target?.closest('[class*="VideoPlayer"]'); console.log('๐ŸŽฌ [normalContainerRef] onMouseDownCapture ์‹คํ–‰๋จ', { isPlaying, @@ -845,39 +913,34 @@ export function ProductVideoV2({ target: e.target?.className, }); - // VideoPlayer๊ฐ€ ์•„๋‹Œ ์˜์—ญ(์Šคํฌ๋กค ์˜์—ญ)์—์„œ๋งŒ preventDefault if (!isVideoPlayerArea) { e.preventDefault(); console.log('๐Ÿ›‘ [normalContainerRef] preventDefault - ์Šคํฌ๋กค ์˜์—ญ์—์„œ์˜ ํด๋ฆญ'); } else { - // VideoPlayer ์˜์—ญ์ด๋ฉด ์ด๋ฒคํŠธ๋ฅผ ์ „ํŒŒ์‹œ์ผœ click์ด ์ •์ƒ ๋ฐœ์ƒํ•˜๋„๋ก console.log('โœ… [normalContainerRef] ์ด๋ฒคํŠธ ์ „ํŒŒ ํ—ˆ์šฉ - VideoPlayer ์˜์—ญ'); } }} - > - {renderVideoPlayer()} -
- ) : null} + /> + )}
{/* ์ „์ฒดํ™”๋ฉด container (Portal) */} - {isPlaying && isFullscreen && ReactDOM.createPortal( - -
- {renderVideoPlayer()} -
-
, - document.body - )} +
+ , + document.body + )} + + {renderVideoPlayer()} ); }