UNPKG

react-video-scroll-ssr

Version:

A React component to seek or control the video frame rate on scroll.

166 lines (144 loc) 4.94 kB
// @flow import * as React from 'react' import PropTypes from 'prop-types' type Props = { // Controlling the playback rate when seeking the video on scroll playbackRate: number, // Should seek the video on horizontal scrolling ? horizontalScroll: boolean, // onScroll is invoked when the page is scroll along x or y axis onScroll: ({ // 'div' wrapper element for video element wrapperEl: HTMLElement, // video element videoEl: HTMLElement, // Current frame / current time of video currentFrame: number, // Video duration duration: number, playbackRate: number }) => void, // onLoad is invoked when the video is finished loading. // Use this to update the height of wrapper or video element or applying some other styles. onLoad: ({ wrapperEl: HTMLElement, videoEl: HTMLElement, playbackRate: number }) => void, // Function to set the current frame / current time and seek the video on scroll setCurrentFrame: ({ duration: number, playbackRate: number }) => number, children: React.Node } const rAF = typeof window !== 'undefined' ? window.requestAnimationFrame : () => {} export class VideoScroll extends React.Component<Props, void> { // TODO: https://github.com/facebook/flow/pull/5920 // $FlowFixMe videoRef: { current: HTMLVideoElement | null } = React.createRef() // TODO: https://github.com/facebook/flow/pull/5920 // $FlowFixMe divWrapperRef: { current: HTMLElement | null } = React.createRef() static propTypes = { playbackRate: PropTypes.number, horizontalScroll: PropTypes.bool, onScroll: PropTypes.func, onLoad: PropTypes.func, setCurrentFrame: PropTypes.func } static defaultProps = { playbackRate: 10, horizontalScroll: false } componentDidMount() { this.videoRef.current.addEventListener('loadedmetadata', this.seek) } componentWillUnmount() { this.videoRef.current.removeEventListener('loadedmetadata', this.seek) } seek = () => { // Start the video from beginning let currentFrame = 0 if (typeof this.props.onLoad === 'function') { // Invoke the callback and apply any necessary styles or adjustments this.props.onLoad({ wrapperEl: this.divWrapperRef.current, videoEl: this.videoRef.current, duration: this.videoRef.current.duration, playbackRate: this.props.playbackRate }) } const startOnScroll = () => { // User defined function to set the current frame and seek the video on scroll if ( this.props.setCurrentFrame && typeof this.props.setCurrentFrame === 'function' ) { currentFrame = this.props.setCurrentFrame({ playbackRate: this.props.playbackRate, duration: this.videoRef.current.duration }) } else { const offset = this.props.horizontalScroll ? window.pageXOffset : window.pageYOffset currentFrame = offset / this.props.playbackRate } // Set the current frame when scroll along x or y axis this.videoRef.current.currentTime = currentFrame // Do some extra stuff here if (typeof this.props.onScroll === 'function') { this.props.onScroll({ wrapperEl: this.divWrapperRef.current, videoEl: this.videoRef.current, currentFrame: currentFrame, duration: this.videoRef.current.duration, playbackRate: this.props.playbackRate }) } rAF(startOnScroll) } rAF(startOnScroll) } attachRefToVideoEl = () => { // For tracking the number of <video> elements which are children of <VideoScroll> component let count = 0 return React.Children.map( this.props.children, (child: React.Element<any>, i: number): React.Node => { if (child.type === 'video') { count += 1 // To seek more than one video, wrap the other video element inside an another VideoScroll component if (count > 1) { const PROD_ERR = '<VideoScroll> component expected only one <video> element as its children' throw new Error( process.env.NODE_ENV !== 'production' ? `${PROD_ERR} but received more than one <video> element. To seek both the videos, wrap the other video element inside another <VideoScroll> component.` : `${PROD_ERR}.` ) } else if (count === 1) { return React.cloneElement(child, { key: i, ref: this.videoRef }) } } return React.cloneElement(child, { key: i }) } ) } render(): React.Node { const { children, playbackRate, setCurrentFrame, horizontalScroll, onScroll, onLoad, ...rest } = this.props return ( <div ref={this.divWrapperRef} {...rest}> {this.attachRefToVideoEl()} </div> ) } }