fixes types

This commit is contained in:
Sudev Kiyada 2025-06-02 23:40:09 +05:30
parent b01b8f8a80
commit f8421b675e
Failed to extract signature
10 changed files with 131 additions and 54 deletions

View file

@ -27,6 +27,8 @@ The `ScrollyVideo` component is built on top of the [ScrollyVideo.js](https://sc
To use the `ScrollyVideo` component, simply import it and provide the video source. Set height prop to any valid CSS height value, such as `1200px`, `50vh`, or `500svh` to set the scrolling height.
[Demo](?path=/story/components-graphics-scrollyvideo--basic)
```svelte
<script lang="ts">
import { ScrollyVideo } from '@reuters-graphics/graphics-components';
@ -44,11 +46,17 @@ To use the `ScrollyVideo` component, simply import it and provide the video sour
It can also be used to display multiple different videos based on the viewport width. This is useful for responsive designs where your subject in the video is not in the center. After Effects can be usd to maintain a crop with a focus on the subject, and the `ScrollyVideo` component can be used to display the appropriate video based on the viewport width.
[Demo](?path=/story/components-graphics-scrollyvideo--multiple-videos)
```svelte
<script lang="ts">
import { ScrollyVideo } from '@reuters-graphics/graphics-components';
let width = $state(0);
</script>
<svelte:window bind:innerWidth={width} />
{#if width < 600}
<!-- Aspect ratio 9:16 -->
<ScrollyVideo
@ -80,6 +88,8 @@ It can also be used to display multiple different videos based on the viewport w
Autoplay can be enabled by setting the `autoplay` prop to `true`. This will start the video playback automatically when the component is mounted. Upon interruption by manual scroll, the video will resume playback on scroll. The height is altered to remaining portion of the video.
[Demo](?path=/story/components-graphics-scrollyvideo--autoplay)
```svelte
<script lang="ts">
import { ScrollyVideo } from '@reuters-graphics/graphics-components';
@ -98,6 +108,8 @@ Autoplay can be enabled by setting the `autoplay` prop to `true`. This will star
The `ScrollyVideo` component can also be used inside the `ScrollerBase` component to create a scroller-based layout. This allows for more complex interactions and layouts, such as combining ai2svelte components with scrolly video content.
[Demo](?path=/story/components-graphics-scrollyvideo--inside-scroller-base)
```svelte
<script lang="ts">
import {
@ -268,3 +280,17 @@ The `ScrollyVideo` component can also be used inside the `ScrollerBase` componen
}
</style>
```
## 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.
```js
scrollyVideo.setVideoPercentage(0.5, {
jump: false,
transitionSpeed: 12,
easing: d3.easeLinear,
});
```
This will set the video to 50% progress, with a smooth transition at a playback rate of 12, using a linear easing function.

View file

@ -6,6 +6,9 @@
const { Story } = defineMeta({
title: 'Components/Graphics/ScrollyVideo',
component: ScrollyVideo,
parameters: {
docsTools: { remount: true },
},
argTypes: {
autoplay: {
control: 'boolean',
@ -137,6 +140,13 @@
height: '500svh',
showDebugInfo: true,
autoplay: false,
full: true,
sticky: true,
objectFit: 'cover',
transitionSpeed: 8,
frameThreshold: 0.1,
useWebCodecs: true,
lockScroll: true,
});
</script>
@ -144,28 +154,37 @@
<Story name="Basic" {args}></Story>
<Story
name="Multiple Videos"
args={{
trackScroll: true,
height: '500svh',
showDebugInfo: true,
autoplay: false,
}}
>
{#if width < 600}
<ScrollyVideo {...args} src={videoSrc.V_9_16} />
{:else if width < 1200}
<ScrollyVideo {...args} src={videoSrc.V_1_1} />
{:else}
<ScrollyVideo {...args} src={videoSrc.V_16_9} />
{/if}
<Story name="Multiple Videos" {args}>
{#snippet children(args)}
{#key args}
{#if width < 600}
<ScrollyVideo {...args} src={videoSrc.V_9_16} />
{:else if width < 1200}
<ScrollyVideo {...args} src={videoSrc.V_1_1} />
{:else}
<ScrollyVideo {...args} src={videoSrc.V_16_9} />
{/if}
{/key}
{/snippet}
</Story>
<Story name="Autoplay" {args}>
<ScrollyVideo {...args} src={videoSrc.Goldengate} autoplay={true} />
{#snippet children(args)}
{#key args}
<ScrollyVideo
{...args}
src={videoSrc.Goldengate}
useWebCodecs={true}
autoplay={true}
></ScrollyVideo>
{/key}
{/snippet}
</Story>
<Story name="inside ScrollerBase" {args}>
<WithScrollerBase />
{#snippet children(args)}
{#key args}
<WithScrollerBase />
{/key}
{/snippet}
</Story>

View file

@ -65,7 +65,7 @@
}: Props = $props();
// variable to hold the DOM element
let scrollyVideoContainer = $state();
let scrollyVideoContainer = $state<HTMLDivElement | undefined>(undefined);
// Store the props so we know when things change
let lastPropsString = '';
@ -77,6 +77,8 @@
if (scrollyVideo && scrollyVideo.destroy) scrollyVideo.destroy();
scrollyVideo = new ScrollyVideo({
scrollyVideoContainer,
onReady,
onChange,
...restProps,
});
@ -102,9 +104,7 @@
});
let heightChange = $derived.by(() => {
return scrollyVideoState.isAutoPlaying ?
`calc(${height} * ${1 - scrollyVideoState.framesData.currentFrame / scrollyVideoState.framesData.totalFrames})`
: `calc(${height} * ${1 - scrollyVideoState.autoplayProgress})`;
return `calc(${height} * ${1 - scrollyVideoState.autoplayProgress})`;
});
</script>
@ -129,7 +129,7 @@
<div bind:this={scrollyVideoContainer} data-scrolly-container>
{#if showDebugInfo}
<p class="debug-info text-xxs font-sans">
{@html JSON.stringify(flattenObject([scrollyVideoState]))
{@html JSON.stringify(flattenObject(scrollyVideoState))
.replace(/[{}"]/g, '')
.split(',')
.join('<br>')}

View file

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/no-empty-object-type */
/* eslint-disable @typescript-eslint/no-explicit-any */
export default ScrollyVideo;
/**
* ____ _ _ __ ___ _
@ -29,8 +29,8 @@ declare class ScrollyVideo {
debug,
autoplay,
}: {
src?: any;
scrollyVideoContainer: any;
src?: string;
scrollyVideoContainer: string | HTMLDivElement | undefined;
objectFit?: string;
sticky?: boolean;
full?: boolean;
@ -45,7 +45,7 @@ declare class ScrollyVideo {
autoplay?: boolean;
});
container: Element;
src: any;
src: string;
transitionSpeed: number;
frameThreshold: number;
useWebCodecs: boolean;
@ -63,11 +63,11 @@ declare class ScrollyVideo {
targetTime: number;
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
frames: any[];
frames: ImageBitmap[] | null;
frameRate: number;
currentFrameNumber: number;
updateScrollPercentage: (jump: any) => void;
targetScrollPosition: any;
updateScrollPercentage: (jump: boolean) => void;
targetScrollPosition: number | null;
resize: () => void;
/**
* Sets the currentTime of the video as a specified percentage of its total duration.
@ -78,13 +78,13 @@ declare class ScrollyVideo {
* - transitionSpeed: number - Defines the speed of the transition when `jump` is false. Represents the duration of the transition in milliseconds. Default is 8.
* - easing: (progress: number) => number - A function that defines the easing curve for the transition. It takes the progress ratio (a number between 0 and 1) as an argument and returns the eased value, affecting the playback speed during the transition.
*/
setVideoPercentage(percentage: any, options?: {}): void;
setVideoPercentage(percentage: number, options?: {}): void;
/**
* Sets the style of the video or canvas to "cover" it's container
*
* @param el
*/
setCoverStyle(el: any): void;
setCoverStyle(el: string): void;
/**
* Uses webCodecs to decode the video into frames
*/
@ -94,7 +94,7 @@ declare class ScrollyVideo {
*
* @param frameNum
*/
paintCanvasFrame(frameNum: any): void;
paintCanvasFrame(frameNum: number): void;
/**
* Transitions the video or the canvas to the proper frame.
*
@ -108,9 +108,9 @@ declare class ScrollyVideo {
transitionSpeed,
easing,
}: {
jump: any;
jump: boolean;
transitionSpeed?: number;
easing?: any;
easing?: (progress: number) => number;
}): void;
transitioningRaf: number;
/**
@ -122,13 +122,13 @@ declare class ScrollyVideo {
* - transitionSpeed: number - Defines the speed of the transition when `jump` is false. Represents the duration of the transition in milliseconds. Default is 8.
* - easing: (progress: number) => number - A function that defines the easing curve for the transition. It takes the progress ratio (a number between 0 and 1) as an argument and returns the eased value, affecting the playback speed during the transition.
*/
setTargetTimePercent(percentage: any, options?: {}): void;
setTargetTimePercent(percentage: number, options?: {}): void;
/**
* Simulate trackScroll programmatically (scrolls on page by percentage of video)
*
* @param percentage
*/
setScrollPercent(percentage: any): void;
setScrollPercent(percentage: number): void;
/**
* Call to destroy this ScrollyVideo object
*/

View file

@ -75,6 +75,17 @@ class ScrollyVideo {
this.video.pause();
this.video.load();
this.video.addEventListener(
'canplaythrough',
() => {
this.onReady();
if (this.autoplay && !this.useWebCodecs) {
this.autoplayScroll();
}
},
{ once: true }
);
// Start the video percentage at 0
this.videoPercentage = 0;
@ -190,7 +201,6 @@ class ScrollyVideo {
this.updateScrollPercentage(true);
this.totalTime = this.video.duration;
this.setCoverStyle(this.canvas || this.video);
if (this.autoplay) this.autoplayScroll();
},
{ once: true }
);
@ -201,7 +211,6 @@ class ScrollyVideo {
this.setTargetTimePercent(0, { jump: true });
this.totalTime = this.video.duration;
this.setCoverStyle(this.canvas || this.video);
if (this.autoplay) this.autoplayScroll();
},
{ once: true }
);
@ -372,6 +381,8 @@ class ScrollyVideo {
// Paint our first frame
this.paintCanvasFrame(Math.floor(this.currentTime * this.frameRate));
this.onReady();
if (this.autoplay) this.autoplayScroll();
}
/**
@ -456,6 +467,10 @@ class ScrollyVideo {
this.currentTime >= this.targetTime
: this.currentTime <= this.targetTime;
if (scrollyVideoState.isAutoPlaying) {
scrollyVideoState.autoplayProgress = this.currentTime / this.totalTime;
}
// If we are already close enough to our target, pause the video and return.
// This is the base case of the recursive function
if (

View file

@ -12,7 +12,7 @@ type FramesData = {
totalFrames: number;
};
type ScrollyVideoState = {
export type ScrollyVideoState = {
generalData: GeneralData;
usingWebCodecs: boolean;
framesData: FramesData;

View file

@ -1,6 +1,25 @@
export function debounce(func: any, delay?: number): (...args: any[]) => void;
import type { ScrollyVideoState } from './types';
type FlattenedScrollyVideoState = {
src: string;
videoPercentage: number;
frameRate: number;
currentTime: number;
totalTime: number;
usingWebCodecs: boolean;
codec: string;
currentFrame: number;
totalFrames: number;
isAutoPlaying: boolean;
autoplayProgress: number;
};
export function debounce<T extends (...args: unknown[]) => unknown>(
func: T,
delay?: number
): (...args: Parameters<T>) => void;
export function isScrollPositionAtTarget(
targetScrollPosition: any,
targetScrollPosition: number | null,
threshold?: number
): boolean;
function constrain(n: number, low: number, high: number): number;
@ -12,4 +31,6 @@ export function map(
stop2: number,
withinBounds?: boolean
): number;
export function flattenObject(obj: any): any;
export function flattenObject(
obj: ScrollyVideoState
): FlattenedScrollyVideoState;

View file

@ -1,15 +1,13 @@
export function debounce(func, delay = 0) {
let timeoutId;
return function (...args) {
const context = this;
return (...args) => {
// Clear the previous timeout if it exists
clearTimeout(timeoutId);
// Set a new timeout to call the function later
timeoutId = setTimeout(() => {
func.apply(context, args);
func.apply(this, args);
}, delay);
};
}
@ -21,8 +19,6 @@ export const isScrollPositionAtTarget = (
const currentScrollPosition = window.pageYOffset;
const difference = Math.abs(currentScrollPosition - targetScrollPosition);
console.log(targetScrollPosition, currentScrollPosition, difference);
return difference < threshold;
};

View file

@ -1,6 +1,6 @@
declare function _default(
src: any,
emitFrame: any,
debug: any
): Promise<never> | Promise<void> | any;
src: string,
emitFrame: (frame: ImageBitmap) => void,
debug: boolean
): Promise<never> | Promise<void>;
export default _default;

View file

@ -152,7 +152,7 @@ const decodeVideo = (
} else reject(new Error('URL provided is not a valid mp4 video file.'));
};
mp4boxfile.onSamples = (track_id, ref, samples) => {
mp4boxfile.onSamples = (_track_id, _ref, samples) => {
for (let i = 0; i < samples.length; i += 1) {
const sample = samples[i];
const type = sample.is_sync ? 'key' : 'delta';