edited advance used demo docs

This commit is contained in:
MinamiFunakoshiTR 2025-07-23 13:01:13 -04:00
parent 0658de887e
commit 7d69c76120
Failed to extract signature
5 changed files with 99 additions and 198 deletions

View file

@ -1,5 +1,6 @@
import type { Component } from 'svelte'; import type { Component } from 'svelte';
import type { TransitionOptions } from '../../components/ScrollyVideo/ts/ScrollyVideo.ts';
import type { ScrollyVideoState } from '../../components/ScrollyVideo/ts/state.svelte.ts';
/** /**
* Used for the list of <option> tags nested in a <select> input. * Used for the list of <option> tags nested in a <select> input.
*/ */
@ -67,3 +68,50 @@ export type ScrollyVideoForegroundPosition =
| 'center center' | 'center center'
| 'center left' | 'center left'
| 'center right'; | 'center right';
// Complete ScrollyVideo instance interface
export interface ScrollyVideoInstance {
// Properties
container: HTMLElement | null;
scrollyVideoContainer: Element | string | undefined;
src: string;
transitionSpeed: number;
frameThreshold: number;
useWebCodecs: boolean;
objectFit: string;
sticky: boolean;
trackScroll: boolean;
onReady: () => void;
onChange: (percentage?: number) => void;
debug: boolean;
autoplay: boolean;
video: HTMLVideoElement | undefined;
videoPercentage: number;
isSafari: boolean;
currentTime: number;
targetTime: number;
canvas: HTMLCanvasElement | null;
context: CanvasRenderingContext2D | null;
frames: ImageBitmap[] | null;
frameRate: number;
targetScrollPosition: number | null;
currentFrame: number;
usingWebCodecs: boolean;
totalTime: number;
transitioningRaf: number | null;
componentState: ScrollyVideoState;
// Methods
updateScrollPercentage: ((jump: boolean) => void) | undefined;
resize: (() => void) | undefined;
setVideoPercentage(percentage: number, options?: TransitionOptions): void;
setCoverStyle(el: HTMLElement | HTMLCanvasElement | undefined): void;
decodeVideo(): Promise<void>;
paintCanvasFrame(frameNum: number): void;
transitionToTargetTime(options: TransitionOptions): void;
setTargetTimePercent(percentage: number, options?: TransitionOptions): void;
setScrollPercent(percentage: number): void;
destroy(): void;
autoplayScroll(): void;
updateDebugInfo(): void;
}

View file

@ -385,178 +385,77 @@ The `ScrollyVideo` component can be used inside the [ScrollerBase](?path=/story/
## Advanced usecases ## Advanced usecases
For advanced use cases such as looping a particular section of the video, or jumping to a specific time in the video, you can bind `scrollyVideo` prop and take benefits of methods such as `setVideoPercentage` or bindable methods such as `onReady` and `onChange`. This allows for fine-grained control over the video playback and interaction with the scroll position. Using the methods attached to the bindable prop `scrollyVideo` allows for advanced customisation of the scroll video behaviour. For example, you can create a looping video that plays a specific section of the video repeatedly, or jump to a specific time in the video when the user scrolls to a certain point.
This code below would make the video smoothly jump to the halfway point of the video. Setting `jump` to `true` will make the video jump to the specified percentage abruptly:
```js ```js
scrollyVideo.setVideoPercentage(0.5, { scrollyVideo.setVideoPercentage(0.5, {
jump: true, jump: false,
}); });
``` ```
This will seek the video to 50% progress at a single instant. Setting `jump` to `false` will make smooth transition to the provided progress value. > **Note**: When using these methods, set `trackScroll` to `false` and pass functions to the `onReady` prop to ensure that the video is ready before calling any methods on it.
[Demo](?path=/story/components-graphics-scrollyvideo--advanced) Here is a [demo](?path=/story/components-graphics-scrollyvideo--advanced) that uses `ScrollyVideo` with `ScrollerBase` to make the video jump to the start or the end of the video depending on what step of the scroller the user is on.
```svelte ```svelte
<script lang="ts"> <script lang="ts">
import { import {
ScrollyVideo, ScrollyVideo,
ScrollerBase, ScrollerBase,
type ScrollyVideoInstance,
} from '@reuters-graphics/graphics-components'; } from '@reuters-graphics/graphics-components';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
let progress = $state(0); let scrollyVideo: ScrollyVideoInstance | undefined = $state(undefined);
let scrollyVideo = $state(); let animationFrame = $state(0);
let now; let index = $state(0); // index for the current step in ScrollerBase
let then = 0;
let time = 0;
let currentProgress = 0; // holds progress value for dynamic looping
let loopCutoff = 0.33; // value between 0-1 to loop the video by
let totalTime = 9 * 1000; // milliseconds
let animationId; // If ScrollerBase is on index 0, jump to the start of the video.
// Otherwise, jump to 1, or 100% (the end), of the video.
// clamps n value between low and high function jumpVideo() {
function constrain(n, low, high) { if (index === 0) {
return Math.max(Math.min(n, high), low); scrollyVideo?.setVideoPercentage(0, {
} jump: false, // Eases the jump
});
// maps n value between two ranges
function map(n, start1, stop1, start2, stop2, withinBounds = true) {
const newval =
((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2;
if (!withinBounds) {
return newval;
}
if (start2 < stop2) {
return constrain(newval, start2, stop2);
} else { } else {
return constrain(newval, stop2, start2); scrollyVideo?.setVideoPercentage(1, {
} jump: false,
} });
// loops the video between 0 and loopCutoff
function renderVideo() {
if (progress < loopCutoff) {
now = Date.now();
const elapsed = now - then;
// if (elapsed > fpsInterval) {
time += elapsed;
currentProgress = map(time, 0, totalTime, 0, 1);
scrollyVideo.setVideoPercentage(currentProgress, { jump: true });
if (currentProgress > loopCutoff) {
currentProgress = 0;
time = 0;
scrollyVideo.setVideoPercentage(0, { jump: true });
}
then = now;
// }
} else {
scrollyVideo.setVideoPercentage(progress, { jump: true });
} }
animationId = requestAnimationFrame(renderVideo); animationFrame = requestAnimationFrame(jumpVideo);
} }
// initializes video autoplay // Cancel requestAnimationFrame on destroy
// when it's ready to play
function initAutoplay() {
then = Date.now();
renderVideo();
}
// cancel RAF on destroy
onDestroy(() => { onDestroy(() => {
if (animationId) { cancelAnimationFrame(animationFrame);
cancelAnimationFrame(animationId);
}
}); });
</script> </script>
<ScrollerBase bind:progress query="div.step-foreground-container" visible> <ScrollerBase bind:index query="div.step-foreground-container">
<!-- ScrollyVideo as background -->
{#snippet backgroundSnippet()} {#snippet backgroundSnippet()}
<!-- Pass `jumpVideo` to `onReady` and set `trackScroll` to `false` -->
<ScrollyVideo <ScrollyVideo
bind:scrollyVideo bind:scrollyVideo
src="my-video.mp4" src={Tennis}
height="100svh" height="100svh"
trackScroll={false} trackScroll={false}
showDebugInfo showDebugInfo
onReady={initAutoplay} onReady={jumpVideo}
/> />
<!-- Only for debugging -->
<div id="progress-bar">
<p>ScrollerBase progress: {progress.toPrecision(2)}</p>
<progress class="mb-4" value={progress}></progress>
</div>
{/snippet} {/snippet}
<!-- Simple text foregrounds -->
{#snippet foregroundSnippet()} {#snippet foregroundSnippet()}
<!-- Add custom foreground HTML or component -->
<div class="step-foreground-container"> <div class="step-foreground-container">
<h3 class="text-center">Step 1</h3> <h3 class="text-center">Index {index}</h3>
</div> </div>
<div class="step-foreground-container"> <div class="step-foreground-container">
<h3 class="text-center">Step 2</h3> <h3 class="text-center">Index {index}</h3>
</div>
<div class="step-foreground-container">
<h3 class="text-center">Step 3</h3>
</div> </div>
{/snippet} {/snippet}
</ScrollerBase> </ScrollerBase>
<style lang="scss">
@use '../../../scss/mixins' as mixins;
// svelte-scroller-background
#progress-bar {
background-color: rgba(0, 0, 0, 0.8);
position: absolute;
z-index: 4;
right: 0;
padding: 1rem;
top: 0;
progress {
height: 6px;
background-color: #ff000044; /* Background color of the entire bar */
margin: 0;
}
progress::-webkit-progress-value {
background-color: white;
border-radius: 10px;
}
progress::-webkit-progress-bar {
background-color: #444444;
border-radius: 10px;
}
p {
font-family: var(--theme-font-family-sans-serif);
color: white;
font-size: var(--theme-font-size-xs);
padding: 0;
margin: 0;
}
}
.step-foreground-container {
height: 100vh;
width: 50%;
padding: 1em;
margin: auto;
h3 {
// align center
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: white;
}
}
</style>
``` ```

View file

@ -5,16 +5,17 @@
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
// Types // Types
import type { ScrollyVideoInstance } from '../ts/ScrollyVideo'; import type { ScrollyVideoInstance } from '../../@types/global.ts';
let progress = $state(0);
let scrollyVideo: ScrollyVideoInstance | undefined = $state(undefined); let scrollyVideo: ScrollyVideoInstance | undefined = $state(undefined);
let animationFrame = $state(0); let animationFrame = $state(0);
let index = $state(0); // index for the current step in ScrollerBase let index = $state(0); // index for the current step in ScrollerBase
/**
* If ScrollerBase is on index 0, jump to the start of the video.
* Otherwise, jump to 1, or 100% (the end), of the video.
*/
function jumpVideo() { function jumpVideo() {
// If ScrollerBase is on index 0, jump to the start of the video.
// Otherwise, jump to 100% (the end) of the video.
if (index === 0) { if (index === 0) {
scrollyVideo?.setVideoPercentage(0, { scrollyVideo?.setVideoPercentage(0, {
jump: false, // Eases the jump jump: false, // Eases the jump
@ -34,14 +35,10 @@
}); });
</script> </script>
<ScrollerBase <ScrollerBase bind:index query="div.step-foreground-container">
bind:progress
bind:index
query="div.step-foreground-container"
visible
>
<!-- ScrollyVideo as background --> <!-- ScrollyVideo as background -->
{#snippet backgroundSnippet()} {#snippet backgroundSnippet()}
<!-- Pass `jumpVideo` to `onReady` and set `trackScroll` to `false` -->
<ScrollyVideo <ScrollyVideo
bind:scrollyVideo bind:scrollyVideo
src={Tennis} src={Tennis}

View file

@ -25,7 +25,7 @@ interface ScrollyVideoArgs {
resize?: () => void; resize?: () => void;
} }
interface TransitionOptions { export interface TransitionOptions {
jump: boolean; jump: boolean;
transitionSpeed?: number; transitionSpeed?: number;
easing?: ((progress: number) => number) | null; easing?: ((progress: number) => number) | null;
@ -202,8 +202,8 @@ class ScrollyVideo {
transitionSpeed = 8, transitionSpeed = 8,
frameThreshold = 0.1, frameThreshold = 0.1,
useWebCodecs = true, useWebCodecs = true,
onReady = () => { }, onReady = () => {},
onChange = (_percentage?: number) => { }, onChange = (_percentage?: number) => {},
debug = false, debug = false,
autoplay = false, autoplay = false,
}: ScrollyVideoArgs) { }: ScrollyVideoArgs) {
@ -741,7 +741,7 @@ class ScrollyVideo {
const hasPassedThreshold = const hasPassedThreshold =
isForwardTransition ? isForwardTransition ?
this.currentTime >= this.targetTime this.currentTime >= this.targetTime
: this.currentTime <= this.targetTime; : this.currentTime <= this.targetTime;
if (this.componentState.isAutoPlaying) { if (this.componentState.isAutoPlaying) {
this.componentState.autoplayProgress = parseFloat( this.componentState.autoplayProgress = parseFloat(
@ -780,7 +780,7 @@ class ScrollyVideo {
isForwardTransition ? isForwardTransition ?
startCurrentTime + startCurrentTime +
easedProgress * Math.abs(distance) * transitionSpeed easedProgress * Math.abs(distance) * transitionSpeed
: startCurrentTime - : startCurrentTime -
easedProgress * Math.abs(distance) * transitionSpeed; easedProgress * Math.abs(distance) * transitionSpeed;
if (this.canvas) { if (this.canvas) {
@ -869,7 +869,7 @@ class ScrollyVideo {
const targetDuration = const targetDuration =
this.frames?.length && this.frameRate ? this.frames?.length && this.frameRate ?
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 // The time we want to transition to
this.targetTime = Math.max(Math.min(percentage, 1), 0) * targetDuration; this.targetTime = Math.max(Math.min(percentage, 1), 0) * targetDuration;
@ -972,50 +972,3 @@ class ScrollyVideo {
} }
} }
export default ScrollyVideo; export default ScrollyVideo;
// Complete ScrollyVideo instance interface
export interface ScrollyVideoInstance {
// Properties
container: HTMLElement | null;
scrollyVideoContainer: Element | string | undefined;
src: string;
transitionSpeed: number;
frameThreshold: number;
useWebCodecs: boolean;
objectFit: string;
sticky: boolean;
trackScroll: boolean;
onReady: () => void;
onChange: (percentage?: number) => void;
debug: boolean;
autoplay: boolean;
video: HTMLVideoElement | undefined;
videoPercentage: number;
isSafari: boolean;
currentTime: number;
targetTime: number;
canvas: HTMLCanvasElement | null;
context: CanvasRenderingContext2D | null;
frames: ImageBitmap[] | null;
frameRate: number;
targetScrollPosition: number | null;
currentFrame: number;
usingWebCodecs: boolean;
totalTime: number;
transitioningRaf: number | null;
componentState: ScrollyVideoState;
// Methods
updateScrollPercentage: ((jump: boolean) => void) | undefined;
resize: (() => void) | undefined;
setVideoPercentage(percentage: number, options?: TransitionOptions): void;
setCoverStyle(el: HTMLElement | HTMLCanvasElement | undefined): void;
decodeVideo(): Promise<void>;
paintCanvasFrame(frameNum: number): void;
transitionToTargetTime(options: TransitionOptions): void;
setTargetTimePercent(percentage: number, options?: TransitionOptions): void;
setScrollPercent(percentage: number): void;
destroy(): void;
autoplayScroll(): void;
updateDebugInfo(): void;
}

View file

@ -51,4 +51,8 @@ export { default as ToolsHeader } from './components/ToolsHeader/ToolsHeader.sve
export { default as Video } from './components/Video/Video.svelte'; export { default as Video } from './components/Video/Video.svelte';
export { default as Visible } from './components/Visible/Visible.svelte'; export { default as Visible } from './components/Visible/Visible.svelte';
export type { ContainerWidth, HeadlineSize } from './components/@types/global'; export type {
ContainerWidth,
HeadlineSize,
ScrollyVideoInstance,
} from './components/@types/global';