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 {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";
interface Props {
previewTrack: HTMLTrackElement | null,
duration: number,
position: number,
position: MousePosition | null,
hidden: boolean,
}
export function PreviewBar({previewTrack, duration, position, hidden}: Props) {
const classes = useStyles();
const [previewBarRef, previewHeadRef, offset] = useOffsetRef(position);
const [previewBarRef, previewHeadRef, offset] = useOffsetAbsoluteRef(position?.absolute || 0);
return useMemo(() => (
<div
......@@ -30,7 +31,7 @@ export function PreviewBar({previewTrack, duration, position, hidden}: Props) {
>
<PreviewViewer
previewTrack={previewTrack}
position={position * duration}
position={(position?.relative || 0) * duration}
/>
</div>
</div>
......@@ -40,11 +41,12 @@ export function PreviewBar({previewTrack, duration, position, hidden}: Props) {
const useStyles = createUseStyles({
previewBar: {
position: "relative",
width: "40rem",
width: "45rem",
display: "flex",
flexDirection: "row",
alignContent: "stretch",
background: "#7c7",
marginLeft: 60,
},
previewHead: {
maxWidth: "136rem",
......
import React, {MouseEvent, useCallback, useMemo, useState} from "react";
import {createUseStyles} from "react-jss";
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";
interface Props {
......@@ -14,61 +16,74 @@ interface Props {
export function SeekBar({video, previewTrack, duration, position}: Props) {
const classes = useStyles();
const [seekPosition, setSeekPosition] = useState<number | null>(null);
const isVisible = seekPosition !== null;
const [mousePosition, setMousePosition] = useState<MousePosition | null>(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(() => {
setSeekPosition(null)
}, [setSeekPosition]);
setMousePosition(null)
}, []);
const onClick = useCallback((event: MouseEvent<HTMLDivElement>) => {
const position = getMousePosition(event);
if (position === null) {
return;
}
if (video) {
setMousePosition(position);
if (video && position) {
video.currentTime = position.relative * duration;
}
}, [duration, video]);
const onMouseMove = useCallback((event: MouseEvent<HTMLDivElement>) => {
const position = getMousePosition(event);
if (position === null) {
return;
}
window.requestAnimationFrame(() => {
setSeekPosition(position.relative)
})
setMousePosition(position);
}, []);
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(() => (
<div
ref={seekBarRef}
ref={setSeekBarRef}
className={classes.seekBar}
onMouseLeave={onMouseLeave}
onClick={onClick}
onMouseMove={onMouseMove}
>
<div
ref={seekHeadRef}
className={classes.seekHead}
style={{
opacity: isVisible ? 1 : 0,
transform: `translate3d(${offset}px, 0, 0)`
}}
/>
{seekHead}
{playHead}
</div>
), [classes, isVisible, offset, onClick, onMouseLeave, onMouseMove, seekBarRef, seekHeadRef]);
), [classes.seekBar, onClick, onMouseLeave, onMouseMove, playHead, seekHead]);
return (
<React.Fragment>
<PreviewBar
previewTrack={previewTrack}
duration={duration}
position={seekPosition || 0}
hidden={seekPosition === null}
position={mousePosition}
hidden={mousePosition === null}
/>
{seekBar}
</React.Fragment>
......@@ -81,12 +96,22 @@ const useStyles = createUseStyles({
width: "40rem",
display: "flex",
flexDirection: "row",
alignContent: "stretch",
alignContent: "center",
alignItems: "center",
height: "5rem",
background: "#77c",
},
seekHead: {
width: "0.1rem",
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";
export function getMousePosition(event: MouseEvent<HTMLDivElement>): MousePosition | null {
const position = event.clientX - event.currentTarget.offsetLeft;
const width = event.currentTarget.offsetWidth;
if (position < 0) return null;
if (position > width) return null;
return {
absolute: position,
absolute: event.clientX,
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 ResizeObserver from 'resize-observer-polyfill';
import {BoundingRect} from "./boundingRect";
export const useWidth = <T extends HTMLElement>(it: T | null) => {
const [width, setWidth] = useState<number>(0);
export const useBoundingRect = <T extends HTMLElement>(it: T | null) => {
const [rect, setRect] = useState<BoundingRect | null>(null);
useEffect(() => {
if (it !== null) {
let animationFrame: number | null = null;
......@@ -12,7 +13,7 @@ export const useWidth = <T extends HTMLElement>(it: T | null) => {
const observer = new ResizeObserver(([element]) => {
animationFrame = window.requestAnimationFrame(() => {
animationFrame = null;
setWidth(element.contentRect.width || 0);
setRect(it.getBoundingClientRect() || null);
});
})
observer.observe(it);
......@@ -24,5 +25,5 @@ export const useWidth = <T extends HTMLElement>(it: T | null) => {
}
}
}, [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 {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] => {
const [parent, setParent] = useState<T | 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