From fa547bc62f31a27bb46b2ffdf1888d3e7a627284 Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 25 May 2025 03:36:56 +0900 Subject: [PATCH] =?UTF-8?q?#8=20=E3=81=A8=E3=82=8A=E3=81=82=E3=81=B8?= =?UTF-8?q?=E3=81=9A=E3=81=AF=E5=AE=8C=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/NicoViewer.tsx | 114 +++++++++++++++++++++++++ frontend/src/pages/PostDetailPage.tsx | 21 +++-- 2 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 frontend/src/components/NicoViewer.tsx diff --git a/frontend/src/components/NicoViewer.tsx b/frontend/src/components/NicoViewer.tsx new file mode 100644 index 0000000..8f1aee1 --- /dev/null +++ b/frontend/src/components/NicoViewer.tsx @@ -0,0 +1,114 @@ +import React, { useRef, useLayoutEffect, useEffect, useState, CSSProperties } from 'react' +import axios from 'axios' +import { Link, useNavigate, useLocation } from 'react-router-dom' +import { API_BASE_URL } from '../config' + +type Props = { id: string, + width: number, + height: number, + style?: CSSProperties } + + +const NicoViewer: React.FC = (props: Props) => { + const { id, width, height, style = { } } = props + + const iframeRef = useRef (null) + const [screenWidth, setScreenWidth] = useState () + const [screenHeight, setScreenHeight] = useState () + const [isLandscape, setIsLandScape] = useState (false) + const [isFullScreen, setIsFullScreen] = useState (false) + + const src = `https://embed.nicovideo.jp/watch/${id}?persistence=1&oldScript=1&referer=&from=0&allowProgrammaticFullScreen=1`; + + const styleFullScreen: CSSProperties = isFullScreen ? { + top: 0, + left: isLandscape ? 0 : '100%', + position: 'fixed', + width: screenWidth, + height: screenHeight, + zIndex: 2147483647, + maxWidth: 'none', + transformOrigin: '0% 0%', + transform: isLandscape ? 'none' : 'rotate(90deg)', + WebkitTransformOrigin: '0% 0%', + WebkitTransform: isLandscape ? 'none' : 'rotate(90deg)', + } : {}; + + const margedStyle = { + border: 'none', + maxWidth: '100%', + ...style, + ...styleFullScreen, + }; + + useEffect(() => { + const onMessage = (event: MessageEvent) => { + if (!iframeRef.current || event.source !== iframeRef.current.contentWindow) return; + if (event.data.eventName === 'enterProgrammaticFullScreen') { + setIsFullScreen(true); + } else if (event.data.eventName === 'exitProgrammaticFullScreen') { + setIsFullScreen(false); + } + }; + + window.addEventListener('message', onMessage); + + return () => { + window.removeEventListener('message', onMessage); + }; + }, []); + + useLayoutEffect(() => { + if (!isFullScreen) return; + + const initialScrollX = window.scrollX; + const initialScrollY = window.scrollY; + let timer: NodeJS.Timeout; + let ended = false; + + const pollingResize = () => { + if (ended) return; + + const isLandscape = window.innerWidth >= window.innerHeight; + const windowWidth = `${isLandscape ? window.innerWidth : window.innerHeight}px`; + const windowHeight = `${isLandscape ? window.innerHeight : window.innerWidth}px`; + + setIsLandScape(isLandscape); + setScreenWidth(windowWidth); + setScreenHeight(windowHeight); + timer = setTimeout(startPollingResize, 200); + } + + const startPollingResize = () => { + if (window.requestAnimationFrame) { + window.requestAnimationFrame(pollingResize); + } else { + pollingResize(); + } + } + + startPollingResize(); + + return () => { + clearTimeout(timer); + ended = true; + window.scrollTo(initialScrollX, initialScrollY); + }; + }, [isFullScreen]); + + useEffect(() => { + if (!isFullScreen) return; + window.scrollTo(0, 0); + }, [screenWidth, screenHeight, isFullScreen]); + + return