Skip to content
Snippets Groups Projects
Verified Commit b0de5480 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

Introduce play head

parent f40ad565
No related branches found
No related tags found
No related merge requests found
import React, {useMemo} from "react"; import React, {useMemo} from "react";
import {createUseStyles} from "react-jss"; import {createUseStyles} from "react-jss";
import {useOffsetRef} from "../../util/offset/useOffsetRef"; import {MousePosition} from "../../util/mouse/MousePosition";
import {useOffsetAbsoluteRef} from "../../util/offset/useOffsetAbsoluteRef";
import {PreviewViewer} from "./PreviewViewer"; import {PreviewViewer} from "./PreviewViewer";
interface Props { interface Props {
previewTrack: HTMLTrackElement | null, previewTrack: HTMLTrackElement | null,
duration: number, duration: number,
position: number, position: MousePosition | null,
hidden: boolean, hidden: boolean,
} }
export function PreviewBar({previewTrack, duration, position, hidden}: Props) { export function PreviewBar({previewTrack, duration, position, hidden}: Props) {
const classes = useStyles(); const classes = useStyles();
const [previewBarRef, previewHeadRef, offset] = useOffsetRef(position); const [previewBarRef, previewHeadRef, offset] = useOffsetAbsoluteRef(position?.absolute || 0);
return useMemo(() => ( return useMemo(() => (
<div <div
...@@ -30,7 +31,7 @@ export function PreviewBar({previewTrack, duration, position, hidden}: Props) { ...@@ -30,7 +31,7 @@ export function PreviewBar({previewTrack, duration, position, hidden}: Props) {
> >
<PreviewViewer <PreviewViewer
previewTrack={previewTrack} previewTrack={previewTrack}
position={position * duration} position={(position?.relative || 0) * duration}
/> />
</div> </div>
</div> </div>
...@@ -40,11 +41,12 @@ export function PreviewBar({previewTrack, duration, position, hidden}: Props) { ...@@ -40,11 +41,12 @@ export function PreviewBar({previewTrack, duration, position, hidden}: Props) {
const useStyles = createUseStyles({ const useStyles = createUseStyles({
previewBar: { previewBar: {
position: "relative", position: "relative",
width: "40rem", width: "45rem",
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
alignContent: "stretch", alignContent: "stretch",
background: "#7c7", background: "#7c7",
marginLeft: 60,
}, },
previewHead: { previewHead: {
maxWidth: "136rem", maxWidth: "136rem",
......
import React, {MouseEvent, useCallback, useMemo, useState} from "react"; import React, {MouseEvent, useCallback, useMemo, useState} from "react";
import {createUseStyles} from "react-jss"; import {createUseStyles} from "react-jss";
import {getMousePosition} from "../../util/mouse/getMousePosition"; import {getMousePosition} from "../../util/mouse/getMousePosition";
import {useOffsetRef} from "../../util/offset/useOffsetRef"; import {MousePosition} from "../../util/mouse/MousePosition";
import {useOffsetAbsolute} from "../../util/offset/useOffsetAbsolute";
import {useOffsetRelative} from "../../util/offset/useOffsetRelative";
import {PreviewBar} from "./PreviewBar"; import {PreviewBar} from "./PreviewBar";
interface Props { interface Props {
...@@ -14,61 +16,74 @@ interface Props { ...@@ -14,61 +16,74 @@ interface Props {
export function SeekBar({video, previewTrack, duration, position}: Props) { export function SeekBar({video, previewTrack, duration, position}: Props) {
const classes = useStyles(); const classes = useStyles();
const [seekPosition, setSeekPosition] = useState<number | null>(null); const [mousePosition, setMousePosition] = useState<MousePosition | null>(null);
const isVisible = seekPosition !== null; const isVisible = mousePosition !== null;
const [seekBarRef, seekHeadRef, offset] = useOffsetRef(seekPosition || 0); const [seekBarRef, setSeekBarRef] = useState<HTMLDivElement | null>(null);
const [seekHeadRef, setSeekHeadRef] = useState<HTMLDivElement | null>(null);
const [playHeadRef, setPlayHeadRef] = useState<HTMLDivElement | null>(null);
const seekHeadOffset = useOffsetAbsolute(seekBarRef, seekHeadRef, mousePosition?.absolute || 0);
const playHeadOffset = useOffsetRelative(seekBarRef, playHeadRef, position / duration);
const onMouseLeave = useCallback(() => { const onMouseLeave = useCallback(() => {
setSeekPosition(null) setMousePosition(null)
}, [setSeekPosition]); }, []);
const onClick = useCallback((event: MouseEvent<HTMLDivElement>) => { const onClick = useCallback((event: MouseEvent<HTMLDivElement>) => {
const position = getMousePosition(event); const position = getMousePosition(event);
if (position === null) { setMousePosition(position);
return; if (video && position) {
}
if (video) {
video.currentTime = position.relative * duration; video.currentTime = position.relative * duration;
} }
}, [duration, video]); }, [duration, video]);
const onMouseMove = useCallback((event: MouseEvent<HTMLDivElement>) => { const onMouseMove = useCallback((event: MouseEvent<HTMLDivElement>) => {
const position = getMousePosition(event); const position = getMousePosition(event);
if (position === null) { setMousePosition(position);
return;
}
window.requestAnimationFrame(() => {
setSeekPosition(position.relative)
})
}, []); }, []);
const seekHead = useMemo(() => (
<div
ref={setSeekHeadRef}
className={classes.seekHead}
style={{
opacity: isVisible ? 1 : 0,
transform: `translate3d(${seekHeadOffset}px, 0, 0)`
}}
/>
), [classes.seekHead, isVisible, seekHeadOffset]);
const playHead = useMemo(() => (
<div
ref={setPlayHeadRef}
className={classes.playHead}
style={{
transform: `translate3d(${playHeadOffset}px, 0, 0)`
}}
/>
), [classes.playHead, playHeadOffset]);
const seekBar = useMemo(() => ( const seekBar = useMemo(() => (
<div <div
ref={seekBarRef} ref={setSeekBarRef}
className={classes.seekBar} className={classes.seekBar}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
onClick={onClick} onClick={onClick}
onMouseMove={onMouseMove} onMouseMove={onMouseMove}
> >
<div {seekHead}
ref={seekHeadRef} {playHead}
className={classes.seekHead}
style={{
opacity: isVisible ? 1 : 0,
transform: `translate3d(${offset}px, 0, 0)`
}}
/>
</div> </div>
), [classes, isVisible, offset, onClick, onMouseLeave, onMouseMove, seekBarRef, seekHeadRef]); ), [classes.seekBar, onClick, onMouseLeave, onMouseMove, playHead, seekHead]);
return ( return (
<React.Fragment> <React.Fragment>
<PreviewBar <PreviewBar
previewTrack={previewTrack} previewTrack={previewTrack}
duration={duration} duration={duration}
position={seekPosition || 0} position={mousePosition}
hidden={seekPosition === null} hidden={mousePosition === null}
/> />
{seekBar} {seekBar}
</React.Fragment> </React.Fragment>
...@@ -81,12 +96,22 @@ const useStyles = createUseStyles({ ...@@ -81,12 +96,22 @@ const useStyles = createUseStyles({
width: "40rem", width: "40rem",
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
alignContent: "stretch", alignContent: "center",
alignItems: "center",
height: "5rem", height: "5rem",
background: "#77c", background: "#77c",
}, },
seekHead: { seekHead: {
width: "0.1rem", width: "0.1rem",
background: "#f00", background: "#f00",
position: "absolute",
height: "1rem",
},
playHead: {
width: "1rem",
background: "#f00",
position: "absolute",
height: "1rem",
borderRadius: "100%",
} }
}); });
...@@ -4,9 +4,10 @@ import {MousePosition} from "./MousePosition"; ...@@ -4,9 +4,10 @@ import {MousePosition} from "./MousePosition";
export function getMousePosition(event: MouseEvent<HTMLDivElement>): MousePosition | null { export function getMousePosition(event: MouseEvent<HTMLDivElement>): MousePosition | null {
const position = event.clientX - event.currentTarget.offsetLeft; const position = event.clientX - event.currentTarget.offsetLeft;
const width = event.currentTarget.offsetWidth; const width = event.currentTarget.offsetWidth;
if (position < 0) return null;
if (position > width) return null; if (position > width) return null;
return { return {
absolute: position, absolute: event.clientX,
relative: position / width relative: position / width
}; };
} }
export interface BoundingRect {
readonly x: number;
readonly y: number;
readonly width: number;
readonly height: number;
readonly top: number;
readonly right: number;
readonly bottom: number;
readonly left: number;
}
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import ResizeObserver from 'resize-observer-polyfill'; import ResizeObserver from 'resize-observer-polyfill';
import {BoundingRect} from "./boundingRect";
export const useWidth = <T extends HTMLElement>(it: T | null) => { export const useBoundingRect = <T extends HTMLElement>(it: T | null) => {
const [width, setWidth] = useState<number>(0); const [rect, setRect] = useState<BoundingRect | null>(null);
useEffect(() => { useEffect(() => {
if (it !== null) { if (it !== null) {
let animationFrame: number | null = null; let animationFrame: number | null = null;
...@@ -12,7 +13,7 @@ export const useWidth = <T extends HTMLElement>(it: T | null) => { ...@@ -12,7 +13,7 @@ export const useWidth = <T extends HTMLElement>(it: T | null) => {
const observer = new ResizeObserver(([element]) => { const observer = new ResizeObserver(([element]) => {
animationFrame = window.requestAnimationFrame(() => { animationFrame = window.requestAnimationFrame(() => {
animationFrame = null; animationFrame = null;
setWidth(element.contentRect.width || 0); setRect(it.getBoundingClientRect() || null);
}); });
}) })
observer.observe(it); observer.observe(it);
...@@ -24,5 +25,5 @@ export const useWidth = <T extends HTMLElement>(it: T | null) => { ...@@ -24,5 +25,5 @@ export const useWidth = <T extends HTMLElement>(it: T | null) => {
} }
} }
}, [it]); }, [it]);
return width; return rect;
} }
import {useWidth} from "./useWidth";
export const useOffset = <T extends HTMLElement, U extends HTMLElement>(parent: T | null, child: U | null, position: number) => {
const parentWidth = useWidth(parent);
const childWidth = useWidth(child);
const offset = parentWidth * position - childWidth / 2;
return Math.max(0, Math.min(offset, parentWidth - childWidth));
}
import {useBoundingRect} from "./useBoundingRect";
export const useOffsetAbsolute = <T extends HTMLElement, U extends HTMLElement>(parent: T | null, child: U | null, position: number) => {
const parentRect = useBoundingRect(parent);
const childRect = useBoundingRect(child);
if (parentRect === null || childRect === null) {
return 0;
}
const offset = position - parentRect.left - childRect.width / 2;
const maximum = parentRect.width - childRect.width;
return Math.max(0, Math.min(offset, maximum));
}
import {useState} from "react"; import {useState} from "react";
import {useOffset} from "./useOffset"; import {useOffsetAbsolute} from "./useOffsetAbsolute";
export const useOffsetRef = <T extends HTMLElement, U extends HTMLElement>(position: number): export const useOffsetAbsoluteRef = <T extends HTMLElement, U extends HTMLElement>(position: number):
[(it: T | null) => void, (it: U | null) => void, number] => { [(it: T | null) => void, (it: U | null) => void, number] => {
const [parent, setParent] = useState<T | null>(null); const [parent, setParent] = useState<T | null>(null);
const [child, setChild] = useState<U | null>(null); const [child, setChild] = useState<U | null>(null);
return [setParent, setChild, useOffset(parent, child, position)]; return [setParent, setChild, useOffsetAbsolute(parent, child, position)];
} }
import {useBoundingRect} from "./useBoundingRect";
export const useOffsetRelative = <T extends HTMLElement, U extends HTMLElement>(parent: T | null, child: U | null, position: number) => {
const parentRect = useBoundingRect(parent);
const childRect = useBoundingRect(child);
if (parentRect === null || childRect === null) {
return 0;
}
const offset = (position * parentRect.width) - parentRect.left;
const maximum = parentRect.width - childRect.width;
return Math.max(0, Math.min(offset, maximum));
}
import {useState} from "react";
import {useOffsetAbsolute} from "./useOffsetAbsolute";
export const useOffsetRelativeRef = <T extends HTMLElement, U extends HTMLElement>(position: number):
[(it: T | null) => void, (it: U | null) => void, number] => {
const [parent, setParent] = useState<T | null>(null);
const [child, setChild] = useState<U | null>(null);
return [setParent, setChild, useOffsetAbsolute(parent, child, position)];
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment