ファイル
btrc-hub/frontend/src/components/NicoViewer.tsx
T
2025-08-23 18:40:03 +09:00

112 行
3.0 KiB
TypeScript

import { useRef, useLayoutEffect, useEffect, useState } from 'react'
type Props = { id: string,
width: number,
height: number,
style?: CSSProperties }
import type { CSSProperties, FC } from 'react'
export default ((props: Props) => {
const { id, width, height, style = { } } = props
const iframeRef = useRef<HTMLIFrameElement> (null)
const [screenWidth, setScreenWidth] = useState<CSSProperties['width']> ()
const [screenHeight, setScreenHeight] = useState<CSSProperties['height']> ()
const [landscape, setLandscape] = useState<boolean> (false)
const [fullScreen, setFullScreen] = useState<boolean> (false)
const src = `https://embed.nicovideo.jp/watch/${id}?persistence=1&oldScript=1&referer=&from=0&allowProgrammaticFullScreen=1`;
const styleFullScreen: CSSProperties = fullScreen ? {
top: 0,
left: landscape ? 0 : '100%',
position: 'fixed',
width: screenWidth,
height: screenHeight,
zIndex: 2147483647,
maxWidth: 'none',
transformOrigin: '0% 0%',
transform: landscape ? 'none' : 'rotate(90deg)',
WebkitTransformOrigin: '0% 0%',
WebkitTransform: landscape ? 'none' : 'rotate(90deg)' } : {};
const margedStyle = {
border: 'none',
maxWidth: '100%',
...style,
...styleFullScreen }
useEffect (() => {
const onMessage = (event: MessageEvent<any>) => {
if (!(iframeRef.current)
|| (event.source !== iframeRef.current.contentWindow))
return
if (event.data.eventName === 'enterProgrammaticFullScreen')
setFullScreen (true)
else if (event.data.eventName === 'exitProgrammaticFullScreen')
setFullScreen (false)
}
addEventListener ('message', onMessage)
return () => removeEventListener ('message', onMessage)
}, [])
useLayoutEffect(() => {
if (!(fullScreen))
return
const initialScrollX = scrollX
const initialScrollY = scrollY
let timer: NodeJS.Timeout
let ended = false
const pollingResize = () => {
if (ended)
return
const landscape = innerWidth >= innerHeight
const windowWidth = `${landscape ? innerWidth : innerHeight}px`
const windowHeight = `${landscape ? innerHeight : innerWidth}px`
setLandscape (landscape)
setScreenWidth (windowWidth)
setScreenHeight (windowHeight)
timer = setTimeout (startPollingResize, 200)
}
const startPollingResize = () => {
if (requestAnimationFrame)
requestAnimationFrame (pollingResize)
else
pollingResize ()
}
startPollingResize ()
return () => {
clearTimeout (timer)
ended = true
scrollTo (initialScrollX, initialScrollY)
}
}, [fullScreen])
useEffect (() => {
if (!(fullScreen))
return
scrollTo (0, 0)
}, [screenWidth, screenHeight, fullScreen])
return (
<iframe ref={iframeRef}
src={src}
width={width}
height={height}
style={margedStyle}
allowFullScreen
allow="autoplay"/>)
}) satisfies FC<Props>