Merge pull request #342 from reuters-graphics/mf-prop-fixes
Documentation, demo, props cleanup
This commit is contained in:
commit
0f39319ae1
5 changed files with 56 additions and 81 deletions
|
|
@ -10,9 +10,9 @@ The `ScrollerVideo` component creates interactive video experiences that respond
|
|||
|
||||
## Basic demo
|
||||
|
||||
To use the `ScrollerVideo` component, import it and provide the video source. Set the `height` prop to any valid CSS height value such as `1200px`, `150vh`, or `500lvh` to set the scroll height.
|
||||
To use the `ScrollerVideo` component, import it and provide the video source. The scroll height defaults to `200lvh`, but you can adjust this to any valid CSS height value such as `1200px` or `200lvh` with the `height` prop.
|
||||
|
||||
> It is advisable to use 'lvh' or 'svh' units instead of 'vh' unit for the height, as these units are more reliable across different devices.
|
||||
> 💡TIP: Use `lvh` or `svh` units instead of `vh` unit for the height, as [these units](https://www.w3.org/TR/css-values-4/#large-viewport-size) are more reliable on mobile or other devices where elements such as the address bar toggle between being shown and hidden.
|
||||
|
||||
[Demo](?path=/story/components-graphics-scrollervideo--demo)
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ To use the `ScrollerVideo` component, import it and provide the video source. Se
|
|||
import { ScrollerVideo } from '@reuters-graphics/graphics-components';
|
||||
</script>
|
||||
|
||||
<!-- Optionally set `height` to adjust scroll height -->
|
||||
<ScrollerVideo src="my-video.mp4" height="500lvh" />
|
||||
```
|
||||
|
||||
|
|
@ -69,7 +70,7 @@ To show different videos based on the screen width, use the `ScrollerVideo` comp
|
|||
|
||||
## Embeds
|
||||
|
||||
Setting `embedded` will autoplay the entire `ScrollerVideo` component like a video.
|
||||
Setting `embedded` to `true` will turn `ScrollerVideo` into an embeddable version, where the video autoplays when the user scrolls upon it. Optionally, you can control the embed video behaviour by passing `embeddedProps` to control the autoplay `delay`, `threshold` for triggering autoplay, and the `duration` of the video.
|
||||
|
||||
> 💡**TIP:** Another way to recreate the ScrollerVideo experience for embeds is to record the desktop screen with [Scroll Capture](https://chromewebstore.google.com/detail/scroll-capture/egmhoeaacclmanaimofoooiamhpkimkk?hl=en) while scrolling through the video and use that video instead as an HTML video component.
|
||||
|
||||
|
|
@ -78,19 +79,17 @@ Setting `embedded` will autoplay the entire `ScrollerVideo` component like a vid
|
|||
```svelte
|
||||
<script lang="ts">
|
||||
import { ScrollerVideo } from '@reuters-graphics/graphics-components';
|
||||
|
||||
let embedded = $state(true); // Set to true to enable embedded mode
|
||||
</script>
|
||||
|
||||
<ScrollerVideo
|
||||
src={Goldengate}
|
||||
height="200lvh"
|
||||
trackScroll={true}
|
||||
showDebugInfo
|
||||
src='my-video.mp4'
|
||||
{embedded}
|
||||
embeddedProps={{
|
||||
delay: 200,
|
||||
threshold: 0.5, // threshold for triggering the autoplay
|
||||
height: '80lvh', // height of the scroll container
|
||||
duration: 5000, // time duration from start to end
|
||||
delay: 200, // Optional: Delay before autoplay starts. Defaults to 200ms.
|
||||
threshold: 0.5, // Optional: Threshold for triggering the autoplay. Defaults to 0.5.
|
||||
duration: 5000, // Optional: Defaults to the duration of the video.
|
||||
}}
|
||||
/>
|
||||
```
|
||||
|
|
|
|||
|
|
@ -137,17 +137,7 @@
|
|||
};
|
||||
|
||||
const args = {
|
||||
trackScroll: true,
|
||||
height: '500lvh',
|
||||
showDebugInfo: true,
|
||||
autoplay: false,
|
||||
full: true,
|
||||
sticky: true,
|
||||
objectFit: 'cover',
|
||||
transitionSpeed: 8,
|
||||
frameThreshold: 0.1,
|
||||
useWebCodecs: true,
|
||||
lockScroll: true,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
@ -168,21 +158,11 @@
|
|||
</Story>
|
||||
|
||||
<Story name="Embed version" exportName="Embed">
|
||||
<!-- <ScrollerVideo
|
||||
embedded={true}
|
||||
src={videoSrc.Goldengate}
|
||||
embeddedProps={{ autoplay: true }}
|
||||
/> -->
|
||||
<Embedded />
|
||||
</Story>
|
||||
|
||||
<Story name="Autoplay" {args}>
|
||||
<ScrollerVideo
|
||||
{...args}
|
||||
src={videoSrc.Goldengate}
|
||||
useWebCodecs={false}
|
||||
autoplay={true}
|
||||
></ScrollerVideo>
|
||||
<Story name="Autoplay">
|
||||
<ScrollerVideo {...args} src={videoSrc.Goldengate} autoplay={true} />
|
||||
</Story>
|
||||
|
||||
<Story
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import ScrollerVideo from './ts/ScrollerVideo.js';
|
||||
import ScrollerVideo from './ts/ScrollerVideo';
|
||||
import Debug from './Debug.svelte';
|
||||
import type { Snippet } from 'svelte';
|
||||
import { setContext } from 'svelte';
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
/** Bindable instance of ScrollerVideo */
|
||||
scrollerVideo?: ScrollerVideo;
|
||||
/** Video source URL */
|
||||
src?: string;
|
||||
src: string;
|
||||
/** Bindable percentage value to control video playback. **Ranges from 0 to 1** */
|
||||
videoPercentage?: number;
|
||||
/** Sets the maximum playbackRate for this video */
|
||||
|
|
@ -52,8 +52,6 @@
|
|||
embeddedProps?: {
|
||||
/** When to start the playback in terms of the component's position */
|
||||
threshold?: number;
|
||||
/** Height of embedded component */
|
||||
height?: string;
|
||||
/** Duration of ScrollerVideo experience as a video */
|
||||
duration?: number;
|
||||
/** Delay before the playback */
|
||||
|
|
@ -66,7 +64,6 @@
|
|||
/** Default properties for embedded videos */
|
||||
const defaultEmbedProps = {
|
||||
threshold: 0.5,
|
||||
height: '80lvh',
|
||||
delay: 200,
|
||||
};
|
||||
|
||||
|
|
@ -75,14 +72,15 @@
|
|||
* Handles instantiation, prop changes, and cleanup.
|
||||
*/
|
||||
let {
|
||||
class: cls = '',
|
||||
id = '',
|
||||
src,
|
||||
scrollerVideo = $bindable(),
|
||||
videoPercentage,
|
||||
onReady = $bindable(() => {}),
|
||||
onChange = $bindable(() => {}),
|
||||
height = '200lvh',
|
||||
showDebugInfo = false,
|
||||
class: cls = '',
|
||||
id = '',
|
||||
embedded = false,
|
||||
embeddedProps,
|
||||
children,
|
||||
|
|
@ -136,6 +134,7 @@
|
|||
if (scrollerVideo && scrollerVideo.destroy) scrollerVideo.destroy();
|
||||
|
||||
scrollerVideo = new ScrollerVideo({
|
||||
src,
|
||||
scrollerVideoContainer,
|
||||
onReady,
|
||||
onChange,
|
||||
|
|
@ -246,7 +245,6 @@
|
|||
{#if embedded}
|
||||
<div
|
||||
class="embedded-scroller-video-container"
|
||||
style="height: {allEmbedProps.height};"
|
||||
bind:this={embeddedContainer}
|
||||
bind:clientHeight={embeddedContainerHeight}
|
||||
onscroll={() => {
|
||||
|
|
@ -260,18 +258,8 @@
|
|||
}
|
||||
}}
|
||||
>
|
||||
<!-- style needs to be >= 100lvh to allow child element to scroll -->
|
||||
<!-- 200lvh provides smoother scrolling experience -->
|
||||
<div
|
||||
{id}
|
||||
class="scroller-video-container embedded {cls}"
|
||||
style="height: 200lvh;"
|
||||
>
|
||||
<div
|
||||
bind:this={scrollerVideoContainer}
|
||||
data-scroller-container
|
||||
style="max-height: {allEmbedProps.height};"
|
||||
>
|
||||
<div {id} class="scroller-video-container embedded {cls}">
|
||||
<div bind:this={scrollerVideoContainer} data-scroller-container>
|
||||
{@render supportingElements()}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -292,6 +280,11 @@
|
|||
.scroller-video-container {
|
||||
width: 100%;
|
||||
|
||||
// Needs to be >= 100lvh to allow child element to scroll
|
||||
// 200lvh provides smoother scrolling experience -->
|
||||
&.embedded {
|
||||
height: 200lvh;
|
||||
}
|
||||
&:not(.embedded) {
|
||||
min-height: 100lvh;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import ScrollerVideoForeground from '../ScrollerVideoForeground.svelte';
|
||||
import Goldengate from '../videos/goldengate.mp4';
|
||||
import BodyText from '../../BodyText/BodyText.svelte';
|
||||
import Block from '../../Block/Block.svelte';
|
||||
|
||||
import type { ContainerWidth } from '../../@types/global';
|
||||
|
||||
|
|
@ -46,22 +47,24 @@
|
|||
const dummyText =
|
||||
'Reprehenderit hamburger pork bresaola, dolore chuck sirloin landjaeger ham hock tempor meatball alcatra nostrud pork belly. Culpa pork belly doner ea jowl, elit deserunt leberkas cow shoulder ham hock dolore.';
|
||||
const scrollerVideoBlock = content.blocks[0];
|
||||
|
||||
let embedded = $state(true);
|
||||
</script>
|
||||
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
|
||||
<ScrollerVideo
|
||||
src={Goldengate}
|
||||
height="200lvh"
|
||||
trackScroll={true}
|
||||
showDebugInfo
|
||||
embedded={true}
|
||||
class="embedded-demo"
|
||||
showDebugInfo={true}
|
||||
{embedded}
|
||||
embeddedProps={{
|
||||
threshold: 0.5,
|
||||
height: '100lvh',
|
||||
duration: 12000,
|
||||
delay: 200,
|
||||
}}
|
||||
|
|
@ -78,3 +81,19 @@
|
|||
{/each}
|
||||
</ScrollerVideo>
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
<BodyText text={dummyText} />
|
||||
|
||||
<style lang="scss">
|
||||
:global {
|
||||
.embedded-demo .foreground-text {
|
||||
h4,
|
||||
p {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { debounce, isScrollPositionAtTarget, map, constrain } from './utils';
|
|||
import { createComponentState, type ScrollerVideoState } from './state.svelte';
|
||||
|
||||
interface ScrollerVideoArgs {
|
||||
src?: string;
|
||||
src: string;
|
||||
scrollerVideoContainer: HTMLElement | string;
|
||||
objectFit?: string;
|
||||
sticky?: boolean;
|
||||
|
|
@ -192,7 +192,7 @@ class ScrollerVideo {
|
|||
* @param {ScrollerVideoArgs} args - The arguments for initialization.
|
||||
*/
|
||||
constructor({
|
||||
src = 'https://scrollyvideo.js.org/goldengate.mp4',
|
||||
src,
|
||||
scrollerVideoContainer,
|
||||
objectFit = 'cover',
|
||||
sticky = true,
|
||||
|
|
@ -202,8 +202,8 @@ class ScrollerVideo {
|
|||
transitionSpeed = 8,
|
||||
frameThreshold = 0.1,
|
||||
useWebCodecs = true,
|
||||
onReady = () => {},
|
||||
onChange = (_percentage?: number) => {},
|
||||
onReady = () => { },
|
||||
onChange = (_percentage?: number) => { },
|
||||
debug = false,
|
||||
autoplay = false,
|
||||
}: ScrollerVideoArgs) {
|
||||
|
|
@ -240,24 +240,8 @@ class ScrollerVideo {
|
|||
this.totalTime = 0; // The total time of the video, used for calculating percentage
|
||||
this.transitioningRaf = null;
|
||||
this.componentState = createComponentState();
|
||||
|
||||
this.componentState.willAutoPlay = autoplay;
|
||||
|
||||
// Make sure that we have a DOM
|
||||
if (typeof document !== 'object') {
|
||||
console.error('ScrollerVideo must be initiated in a DOM context');
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the basic arguments are set for scrollervideo
|
||||
if (!scrollerVideoContainer) {
|
||||
console.error('scrollerVideoContainer must be a valid DOM object');
|
||||
return;
|
||||
}
|
||||
if (!src) {
|
||||
console.error('Must provide valid video src to ScrollerVideo');
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the container. If the container is a string we get the element
|
||||
|
||||
|
|
@ -741,7 +725,7 @@ class ScrollerVideo {
|
|||
const hasPassedThreshold =
|
||||
isForwardTransition ?
|
||||
this.currentTime >= this.targetTime
|
||||
: this.currentTime <= this.targetTime;
|
||||
: this.currentTime <= this.targetTime;
|
||||
|
||||
if (this.componentState.isAutoPlaying) {
|
||||
this.componentState.autoplayProgress = parseFloat(
|
||||
|
|
@ -780,7 +764,7 @@ class ScrollerVideo {
|
|||
isForwardTransition ?
|
||||
startCurrentTime +
|
||||
easedProgress * Math.abs(distance) * transitionSpeed
|
||||
: startCurrentTime -
|
||||
: startCurrentTime -
|
||||
easedProgress * Math.abs(distance) * transitionSpeed;
|
||||
|
||||
if (this.canvas) {
|
||||
|
|
@ -869,7 +853,7 @@ class ScrollerVideo {
|
|||
const targetDuration =
|
||||
this.frames?.length && this.frameRate ?
|
||||
this.frames.length / this.frameRate
|
||||
: this.video?.duration || 0;
|
||||
: this.video?.duration || 0;
|
||||
// The time we want to transition to
|
||||
this.targetTime = Math.max(Math.min(percentage, 1), 0) * targetDuration;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue