From 118894a339923c60abb6b015a2b57d8b24335ea8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=84=ED=98=84?= <77152650+Creative-Lee@users.noreply.github.com> Date: Thu, 14 Sep 2023 12:25:12 +0900 Subject: [PATCH] =?UTF-8?q?Refactor/#408=20SongDetailListPage=EC=9D=98=20Y?= =?UTF-8?q?outube=20Player=20=EC=8A=A4=ED=81=AC=EB=A6=BD=ED=8A=B8=20?= =?UTF-8?q?=EB=A1=9C=EB=94=A9=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?(#409)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: youtube player 생성 시점을 intersection observer로 제어 1. loading상태에 따른 조건부 preview 이미지 랜더 및 옵저버 트리거로 사용 2. useEffect 제거 및 callbackRef로 대체 3. 옵저버 메모리 누수 방지를 위한 observer ref 생성 * refactor: paint 전 단계에 스크롤 이동 하도록 개선 노래 목록의 첫번째 요소의 player 로드가 되는 버그가 있었음. 첫번째 요소의 preview 이미지가 랜더된 후 스크롤 이동하기 때문에 발생한 문제. paint 되기 전, 스크롤을 이동하도록 하여 수정 --- .../features/youtube/components/Youtube.tsx | 48 ++++++++++++------- frontend/src/pages/SongDetailListPage.tsx | 4 +- 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/frontend/src/features/youtube/components/Youtube.tsx b/frontend/src/features/youtube/components/Youtube.tsx index d489ad32f..f8aebc5a5 100644 --- a/frontend/src/features/youtube/components/Youtube.tsx +++ b/frontend/src/features/youtube/components/Youtube.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/display-name */ -import { useEffect } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { styled } from 'styled-components'; +import createObserver from '@/shared/utils/createObserver'; import useVideoPlayerContext from '../hooks/useVideoPlayerContext'; interface YoutubeProps { @@ -9,9 +10,11 @@ interface YoutubeProps { } const Youtube = ({ videoId, start = 0 }: YoutubeProps) => { - const { videoPlayer, initPlayer, bindUpdatePlayerStateEvent } = useVideoPlayerContext(); + const { initPlayer, bindUpdatePlayerStateEvent } = useVideoPlayerContext(); + const [loading, setLoading] = useState(true); + const observerRef = useRef(); - useEffect(() => { + const createPlayerOnObserve: React.RefCallback = useCallback((domNode) => { const createYoutubePlayer = async () => { try { new YT.Player(`yt-player-${videoId}`, { @@ -23,6 +26,7 @@ const Youtube = ({ videoId, start = 0 }: YoutubeProps) => { onReady: (e) => { bindUpdatePlayerStateEvent(e); initPlayer(e); + setLoading(false); }, }, }); @@ -32,20 +36,24 @@ const Youtube = ({ videoId, start = 0 }: YoutubeProps) => { } }; - createYoutubePlayer(); + if (domNode !== null) { + observerRef.current = createObserver(createYoutubePlayer); + observerRef.current.observe(domNode); + return; + } - const clonePlayerRef = videoPlayer; - - return () => { - if (!clonePlayerRef.current) return; - - clonePlayerRef.current.destroy(); - clonePlayerRef.current = null; - }; - }, [bindUpdatePlayerStateEvent, initPlayer, start, videoId, videoPlayer]); + observerRef.current?.disconnect(); + }, []); return ( + {loading && ( + + )} ); @@ -54,12 +62,16 @@ const Youtube = ({ videoId, start = 0 }: YoutubeProps) => { export default Youtube; export const YoutubeWrapper = styled.div` - aspect-ratio: auto 16 / 9; + position: relative; + aspect-ratio: 16 / 9; width: 100%; - - /* @media (max-width: ${({ theme }) => theme.breakPoints.xxs}) { - width: 90%; - } */ `; export const YoutubeIframe = styled.div``; + +const Preview = styled.img` + position: absolute; + aspect-ratio: 16 / 9; + width: 100%; + object-fit: cover; +`; diff --git a/frontend/src/pages/SongDetailListPage.tsx b/frontend/src/pages/SongDetailListPage.tsx index 757a3530d..a649198c4 100644 --- a/frontend/src/pages/SongDetailListPage.tsx +++ b/frontend/src/pages/SongDetailListPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef } from 'react'; +import { useEffect, useLayoutEffect, useRef } from 'react'; import { useParams } from 'react-router-dom'; import { styled } from 'styled-components'; import SongDetailItem from '@/features/songs/components/SongDetailItem'; @@ -64,7 +64,7 @@ const SongDetailListPage = () => { return () => nextObserver.disconnect(); }, [fetchExtraNextSongDetails, songDetailEntries]); - useEffect(() => { + useLayoutEffect(() => { itemRef.current?.scrollIntoView({ behavior: 'instant', block: 'start' }); }, [songDetailEntries]);