From a6fb936fead828c4e0bb014a87d249feb2791481 Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Sun, 27 Sep 2020 15:04:23 +0200 Subject: [PATCH] Cleanup and adding preload --- package-lock.json | 5 +++++ package.json | 1 + src/routes/player/PreviewBar.tsx | 19 +++++++++--------- src/routes/player/PreviewViewer.tsx | 17 ++++++++++++++-- src/routes/player/SeekBar.tsx | 16 +++++++++------ src/util/head/HeadPortal.tsx | 6 ++++++ src/util/offset/useOffset.ts | 6 ++---- src/util/offset/useWidth.ts | 31 +++++++++++++++++------------ 8 files changed, 67 insertions(+), 34 deletions(-) create mode 100644 src/util/head/HeadPortal.tsx diff --git a/package-lock.json b/package-lock.json index fc55f1e..24eba99 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11415,6 +11415,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.0.tgz", diff --git a/package.json b/package.json index cc1d043..c90d4d7 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react-router-dom": "^5.2.0", "react-scripts": "3.4.3", "react-sweet-state": "^2.3.1", + "resize-observer-polyfill": "^1.5.1", "typescript": "^3.7.5" }, "scripts": { diff --git a/src/routes/player/PreviewBar.tsx b/src/routes/player/PreviewBar.tsx index 3d1710a..fd72e87 100644 --- a/src/routes/player/PreviewBar.tsx +++ b/src/routes/player/PreviewBar.tsx @@ -7,9 +7,10 @@ interface Props { previewTrack: HTMLTrackElement | null, duration: number, position: number, + hidden: boolean, } -export function PreviewBar({previewTrack, duration, position}: Props) { +export function PreviewBar({previewTrack, duration, position, hidden}: Props) { const classes = useStyles(); const [previewBarRef, previewHeadRef, offset] = useOffsetRef(position); @@ -23,7 +24,8 @@ export function PreviewBar({previewTrack, duration, position}: Props) { ref={previewHeadRef} className={classes.previewHead} style={{ - transform: `translate3d(${offset}px, 0, 0)` + transform: `translate3d(${offset}px, 0, 0)`, + opacity: hidden ? 0 : 1, }} > <PreviewViewer @@ -32,22 +34,21 @@ export function PreviewBar({previewTrack, duration, position}: Props) { /> </div> </div> - ), [classes.previewHead, classes.previewBar, duration, offset, position, previewBarRef, previewHeadRef, previewTrack]); + ), [previewBarRef, classes.previewBar, classes.previewHead, previewHeadRef, offset, hidden, previewTrack, position, duration]); } const useStyles = createUseStyles({ previewBar: { position: "relative", width: "40rem", - height: "6rem", + display: "flex", + flexDirection: "row", + alignContent: "stretch", background: "#7c7", }, previewHead: { - position: "absolute", - top: 0, - bottom: 0, - left: "-5rem", - width: "10rem", + maxWidth: "136rem", + maxHeight: "9rem", display: "flex", flexDirection: "column-reverse", background: "#f00", diff --git a/src/routes/player/PreviewViewer.tsx b/src/routes/player/PreviewViewer.tsx index 550508a..9928f52 100644 --- a/src/routes/player/PreviewViewer.tsx +++ b/src/routes/player/PreviewViewer.tsx @@ -1,5 +1,6 @@ import React, {useMemo} from "react"; import {createUseStyles} from "react-jss"; +import {HeadPortal} from "../../util/head/HeadPortal"; import {useImage} from "../../util/media/useImage"; import {useTextTrackCues} from "../../util/media/useTextTrackCues"; import {parseImageSprite} from "../../util/sprite/parseImageSprite"; @@ -19,9 +20,21 @@ export function PreviewViewer({previewTrack, position}: Props) { const imageSprite = useMemo(() => parseImageSprite(activeUrl), [activeUrl]); const image = useImage(imageSprite?.src || null); const sprite = useImageSprite(imageSprite, image); + const sources = useMemo(() => previewTrack === null ? null : Array.from(new Set(cues.map(it => { + const url = new URL(it.text, previewTrack.src); + url.hash = ""; + return url.toString(); + }))), [cues, previewTrack]); return useMemo(() => ( - <img alt="" className={classes.preview} src={sprite || undefined}/> - ), [sprite]); + <React.Fragment> + <HeadPortal> + {sources?.map(it => ( + <link key={it} rel="preload" href={it} as="image" crossOrigin="anonymous"/> + ))} + </HeadPortal> + <img alt="" className={classes.preview} src={sprite || undefined}/> + </React.Fragment> + ), [classes.preview, sources, sprite]); } const useStyles = createUseStyles({ diff --git a/src/routes/player/SeekBar.tsx b/src/routes/player/SeekBar.tsx index 61913ae..ed5ffb0 100644 --- a/src/routes/player/SeekBar.tsx +++ b/src/routes/player/SeekBar.tsx @@ -60,11 +60,16 @@ export function SeekBar({video, previewTrack, duration, position}: Props) { }} /> </div> - ), [classes.seekBar, classes.seekHead, isVisible, offset, onClick, onMouseLeave, onMouseMove, seekBarRef, seekHeadRef]); + ), [classes, isVisible, offset, onClick, onMouseLeave, onMouseMove, seekBarRef, seekHeadRef]); return ( <React.Fragment> - <PreviewBar previewTrack={previewTrack} duration={duration} position={seekPosition || 0}/> + <PreviewBar + previewTrack={previewTrack} + duration={duration} + position={seekPosition || 0} + hidden={seekPosition === null} + /> {seekBar} </React.Fragment> ) @@ -74,14 +79,13 @@ const useStyles = createUseStyles({ seekBar: { position: "relative", width: "40rem", + display: "flex", + flexDirection: "row", + alignContent: "stretch", height: "5rem", background: "#77c", }, seekHead: { - position: "absolute", - top: 0, - bottom: 0, - left: "-0.05rem", width: "0.1rem", background: "#f00", } diff --git a/src/util/head/HeadPortal.tsx b/src/util/head/HeadPortal.tsx new file mode 100644 index 0000000..8b70c22 --- /dev/null +++ b/src/util/head/HeadPortal.tsx @@ -0,0 +1,6 @@ +import React from "react"; +import ReactDOM from "react-dom"; + +export function HeadPortal({children}: React.PropsWithChildren<{}>) { + return ReactDOM.createPortal(children, document.head); +} diff --git a/src/util/offset/useOffset.ts b/src/util/offset/useOffset.ts index 05cdc57..811ed38 100644 --- a/src/util/offset/useOffset.ts +++ b/src/util/offset/useOffset.ts @@ -4,8 +4,6 @@ export const useOffset = <T extends HTMLElement, U extends HTMLElement>(parent: const parentWidth = useWidth(parent); const childWidth = useWidth(child); - const offset = parentWidth * position; - const minOffset = childWidth / 2; - const maxOffset = parentWidth - minOffset; - return Math.max(minOffset, Math.min(offset, maxOffset)); + const offset = parentWidth * position - childWidth / 2; + return Math.max(0, Math.min(offset, parentWidth - childWidth)); } diff --git a/src/util/offset/useWidth.ts b/src/util/offset/useWidth.ts index 6d53d44..1321cb5 100644 --- a/src/util/offset/useWidth.ts +++ b/src/util/offset/useWidth.ts @@ -1,21 +1,26 @@ import {useEffect, useState} from "react"; +import ResizeObserver from 'resize-observer-polyfill'; export const useWidth = <T extends HTMLElement>(it: T | null) => { const [width, setWidth] = useState<number>(0); useEffect(() => { - let animationFrame: number | null = null; - const listener = () => { - animationFrame = window.requestAnimationFrame(() => { - animationFrame = null; - setWidth(it?.offsetWidth || 0); - }); - }; - listener(); - window.addEventListener("resize", listener); - return () => { - window.removeEventListener("resize", listener); - if (animationFrame !== null) { - window.cancelAnimationFrame(animationFrame); + if (it !== null) { + let animationFrame: number | null = null; + const listener = () => { + }; + listener(); + const observer = new ResizeObserver(([element]) => { + animationFrame = window.requestAnimationFrame(() => { + animationFrame = null; + setWidth(element.contentRect.width || 0); + }); + }) + observer.observe(it); + return () => { + observer.unobserve(it); + if (animationFrame !== null) { + window.cancelAnimationFrame(animationFrame); + } } } }, [it]); -- GitLab