From 73285cd91b0069f5a92bce0d67554157ec9472a6 Mon Sep 17 00:00:00 2001 From: Sudev Kiyada Date: Tue, 3 Jun 2025 19:45:39 +0530 Subject: [PATCH] adds jsdoc style comments --- .../ScrollyVideo/ScrollyVideo.svelte | 44 ++++- .../ScrollyVideo/js/ScrollyVideo.ts | 162 ++++++++++++++++-- .../ScrollyVideo/js/state.svelte.ts | 29 ++++ src/components/ScrollyVideo/js/utils.ts | 50 ++++++ 4 files changed, 266 insertions(+), 19 deletions(-) diff --git a/src/components/ScrollyVideo/ScrollyVideo.svelte b/src/components/ScrollyVideo/ScrollyVideo.svelte index cf180383..f1deac66 100644 --- a/src/components/ScrollyVideo/ScrollyVideo.svelte +++ b/src/components/ScrollyVideo/ScrollyVideo.svelte @@ -5,7 +5,32 @@ import type { Snippet } from 'svelte'; import { setContext } from 'svelte'; - // Define the props interface + /** + * Props for the ScrollyVideo Svelte component. + * @typedef {Object} Props + * @property {string} [class] - CSS class for scrolly container. + * @property {string} [id] - ID of the scrolly container. + * @property {ScrollyVideo} [scrollyVideo] - Bindable instance of ScrollyVideo. + * @property {string} [src] - Video source URL. + * @property {number} [videoPercentage] - Bindable percentage value to control video playback. Ranges from 0 to 1. + * @property {number} [transitionSpeed] - Sets the maximum playbackRate for this video. + * @property {number} [frameThreshold] - When to stop the video animation, in seconds. + * @property {string} [objectFit] - How the video should be resized to fit its container. + * @property {boolean} [sticky] - Whether the video should have position: sticky. + * @property {boolean} [full] - Whether the video should take up the entire viewport. + * @property {boolean} [trackScroll] - Whether this object should automatically respond to scroll. Set this to false while manually controlling `videoPercentage` prop. + * @property {boolean} [lockScroll] - Whether it ignores human scroll while it runs setVideoPercentage with enabled trackScroll. + * @property {boolean} [useWebCodecs] - Whether the library should use the webcodecs method. For more info, visit https://scrollyvideo.js.org/ + * @property {() => void} [onReady] - The callback when it's ready to scroll. + * @property {() => void} [onChange] - The callback for video percentage change. + * @property {boolean} [debug] - Whether to log debug information. Internal library logs. + * @property {boolean} [showDebugInfo] - Shows debug information on page. + * @property {string} [height] - Height of the video container. Set it to 100svh when using inside `ScrollerBase`. + * @property {boolean} [autoplay] - Whether the video should autoplay. + * @property {boolean} [embedded] - Variable to control component rendering on embed page. + * @property {string} [embeddedSrc] - Source for the embedded video. If not provided, defaults to `src`. + * @property {Snippet} [children] - Children render function. + */ interface Props { /** CSS class for scrolly container */ class?: string; @@ -53,6 +78,10 @@ children?: Snippet; } + /** + * Main logic for ScrollyVideo Svelte component. + * Handles instantiation, prop changes, and cleanup. + */ let { scrollyVideo = $bindable(), videoPercentage, @@ -69,6 +98,10 @@ }: Props = $props(); // variable to hold the DOM element + /** + * Reference to the scrolly video container DOM element. + * @type {HTMLDivElement | undefined} + */ let scrollyVideoContainer = $state(undefined); // Store the props so we know when things change @@ -106,12 +139,17 @@ } }); - // Cleanup the component on destroy + /** + * Cleanup the component on destroy. + */ onDestroy(() => { if (scrollyVideo && scrollyVideo.destroy) scrollyVideo.destroy(); }); - // heightChange drives the height of the component when autoplay is set to true + /** + * heightChange drives the height of the component when autoplay is set to true. + * @type {string} + */ let heightChange = $derived.by(() => { if (scrollyVideo) { return `calc(${height} * ${1 - scrollyVideo?.componentState.autoplayProgress})`; diff --git a/src/components/ScrollyVideo/js/ScrollyVideo.ts b/src/components/ScrollyVideo/js/ScrollyVideo.ts index 07e6e333..ac5f2d57 100644 --- a/src/components/ScrollyVideo/js/ScrollyVideo.ts +++ b/src/components/ScrollyVideo/js/ScrollyVideo.ts @@ -27,39 +27,165 @@ interface TransitionOptions { autoplay?: boolean; } +/** + * ScrollyVideo class for scroll-driven or programmatic video playback with Svelte integration. + */ class ScrollyVideo { + /** + * The container element for the video or canvas. + * @type {HTMLElement | null} + */ container: HTMLElement | null; + /** + * The original container argument (element or string ID). + * @type {Element | string | undefined} + */ scrollyVideoContainer: Element | string | undefined; + /** + * Video source URL. + * @type {string} + */ src: string; + /** + * Speed of transitions. + * @type {number} + */ transitionSpeed: number; + /** + * Threshold for frame transitions. + * @type {number} + */ frameThreshold: number; + /** + * Whether to use WebCodecs for decoding. + * @type {boolean} + */ useWebCodecs: boolean; + /** + * CSS object-fit property for video/canvas. + * @type {string} + */ objectFit: string; + /** + * Whether to use sticky positioning. + * @type {boolean} + */ sticky: boolean; + /** + * Whether to track scroll position. + * @type {boolean} + */ trackScroll: boolean; + /** + * Callback when ready. + * @type {() => void} + */ onReady: () => void; + /** + * Callback on scroll percentage change. + * @type {(percentage?: number) => void} + */ onChange: (percentage?: number) => void; + /** + * Enable debug logging. + * @type {boolean} + */ debug: boolean; + /** + * Enable autoplay. + * @type {boolean} + */ autoplay: boolean; + /** + * The HTML video element. + * @type {HTMLVideoElement | undefined} + */ video: HTMLVideoElement | undefined; + /** + * Current scroll/video percentage (0-1). + * @type {number} + */ videoPercentage: number; + /** + * True if browser is Safari. + * @type {boolean} + */ isSafari: boolean; + /** + * Current video time in seconds. + * @type {number} + */ currentTime: number; + /** + * Target video time in seconds. + * @type {number} + */ targetTime: number; + /** + * Canvas for rendering frames (if using WebCodecs). + * @type {HTMLCanvasElement | null} + */ canvas: HTMLCanvasElement | null; + /** + * 2D context for the canvas. + * @type {CanvasRenderingContext2D | null} + */ context: CanvasRenderingContext2D | null; + /** + * Decoded video frames (if using WebCodecs). + * @type {ImageBitmap[] | null} + */ frames: ImageBitmap[] | null; + /** + * Video frame rate. + * @type {number} + */ frameRate: number; + /** + * Target scroll position in pixels, if set. + * @type {number | null} + */ targetScrollPosition: number | null = null; + /** + * Current frame index (if using WebCodecs). + * @type {number} + */ currentFrame: number; - usingWebCodecs: boolean; // Whether we are using webCodecs - totalTime: number; // The total time of the video, used for calculating percentage + /** + * True if using WebCodecs for decoding. + * @type {boolean} + */ + usingWebCodecs: boolean; + /** + * Total video duration in seconds. + * @type {number} + */ + totalTime: number; + /** + * RequestAnimationFrame ID for transitions. + * @type {number | null} + */ transitioningRaf: number | null; - componentState: ScrollyVideoState; // Placeholder for component state, if needed - + /** + * State object for component-level state. + * @type {ScrollyVideoState} + */ + componentState: ScrollyVideoState; + /** + * Function to update scroll percentage (set in constructor). + * @type {((jump: boolean) => void) | undefined} + */ updateScrollPercentage: ((jump: boolean) => void) | undefined; + /** + * Function to handle resize events (set in constructor). + * @type {(() => void) | undefined} + */ resize: (() => void) | undefined; + /** + * Creates a new ScrollyVideo instance. + * @param {ScrollyVideoArgs} args - The arguments for initialization. + */ constructor({ src = 'https://scrollyvideo.js.org/goldengate.mp4', scrollyVideoContainer, @@ -356,9 +482,8 @@ class ScrollyVideo { } /** - * Sets the style of the video or canvas to "cover" it's container - * - * @param el + * Sets the style of the video or canvas to "cover" its container. + * @param {HTMLElement | HTMLCanvasElement | undefined} el - The element to style. */ setCoverStyle(el: HTMLElement | HTMLCanvasElement | undefined): void { if (!el) { @@ -418,9 +543,10 @@ class ScrollyVideo { } /** - * Uses webCodecs to decode the video into frames + * Uses webCodecs to decode the video into frames. + * @returns {Promise} Resolves when decoding is complete. */ - async decodeVideo() { + async decodeVideo(): Promise { if (!this.useWebCodecs) { if (this.debug) console.warn('Cannot perform video decode: `useWebCodes` disabled'); @@ -496,9 +622,8 @@ class ScrollyVideo { } /** - * Paints the frame of to the canvas - * - * @param frameNum + * Paints the frame to the canvas. + * @param {number} frameNum - The frame index to paint. */ paintCanvasFrame(frameNum: number): void { if (!this.frames) { @@ -751,9 +876,8 @@ class ScrollyVideo { } /** - * Simulate trackScroll programmatically (scrolls on page by percentage of video) - * - * @param percentage + * Simulate trackScroll programmatically (scrolls on page by percentage of video). + * @param {number} percentage - The percentage of the video to scroll to. */ setScrollPercent(percentage: number) { if (!this.trackScroll) { @@ -785,7 +909,7 @@ class ScrollyVideo { } /** - * Call to destroy this ScrollyVideo object + * Call to destroy this ScrollyVideo object. */ destroy() { if (this.debug) console.info('Destroying ScrollyVideo'); @@ -801,6 +925,9 @@ class ScrollyVideo { if (this.container) this.container.innerHTML = ''; } + /** + * Autoplay the video by scrolling to the end. + */ autoplayScroll() { this.setVideoPercentage(1, { jump: false, @@ -811,6 +938,9 @@ class ScrollyVideo { this.componentState.isAutoPlaying = true; } + /** + * Updates debug information in the component state. + */ updateDebugInfo() { this.componentState.generalData.src = this.src; this.componentState.generalData.videoPercentage = parseFloat( diff --git a/src/components/ScrollyVideo/js/state.svelte.ts b/src/components/ScrollyVideo/js/state.svelte.ts index b3950bd4..f01e03b2 100644 --- a/src/components/ScrollyVideo/js/state.svelte.ts +++ b/src/components/ScrollyVideo/js/state.svelte.ts @@ -1,3 +1,12 @@ +/** + * General video data for ScrollyVideo state. + * @typedef {Object} GeneralData + * @property {string} src - Video source URL. + * @property {number} videoPercentage - Current video percentage (0-1). + * @property {number} frameRate - Video frame rate. + * @property {number} currentTime - Current video time in seconds. + * @property {number} totalTime - Total video duration in seconds. + */ type GeneralData = { src: string; videoPercentage: number; @@ -6,12 +15,28 @@ type GeneralData = { totalTime: number; }; +/** + * Frame-level data for ScrollyVideo state. + * @typedef {Object} FramesData + * @property {string} codec - Video codec string. + * @property {number} currentFrame - Current frame index. + * @property {number} totalFrames - Total number of frames. + */ type FramesData = { codec: string; currentFrame: number; totalFrames: number; }; +/** + * State object for ScrollyVideo component. + * @typedef {Object} ScrollyVideoState + * @property {GeneralData} generalData - General video data. + * @property {boolean} usingWebCodecs - Whether WebCodecs is used. + * @property {FramesData} framesData - Frame-level data. + * @property {boolean} isAutoPlaying - Whether video is autoplaying. + * @property {number} autoplayProgress - Progress of autoplay (0-1). + */ export type ScrollyVideoState = { generalData: GeneralData; usingWebCodecs: boolean; @@ -20,6 +45,10 @@ export type ScrollyVideoState = { autoplayProgress: number; }; +/** + * Creates a new ScrollyVideoState object with default values. + * @returns {ScrollyVideoState} The initialized state object. + */ export function createComponentState(): ScrollyVideoState { const scrollyVideoState = $state({ generalData: { diff --git a/src/components/ScrollyVideo/js/utils.ts b/src/components/ScrollyVideo/js/utils.ts index 520e8ed4..fc955e45 100644 --- a/src/components/ScrollyVideo/js/utils.ts +++ b/src/components/ScrollyVideo/js/utils.ts @@ -1,5 +1,20 @@ import type { ScrollyVideoState } from './state.svelte'; +/** + * Flattened version of ScrollyVideoState for easier access to all properties. + * @typedef {Object} FlattenedScrollyVideoState + * @property {string} src - Video source URL. + * @property {number} videoPercentage - Current video percentage (0-1). + * @property {number} frameRate - Video frame rate. + * @property {number} currentTime - Current video time in seconds. + * @property {number} totalTime - Total video duration in seconds. + * @property {boolean} usingWebCodecs - Whether WebCodecs is used. + * @property {string} codec - Video codec string. + * @property {number} currentFrame - Current frame index. + * @property {number} totalFrames - Total number of frames. + * @property {boolean} isAutoPlaying - Whether video is autoplaying. + * @property {number} autoplayProgress - Progress of autoplay (0-1). + */ type FlattenedScrollyVideoState = { src: string; videoPercentage: number; @@ -14,6 +29,13 @@ type FlattenedScrollyVideoState = { autoplayProgress: number; }; +/** + * Returns a debounced version of the given function. + * @template T + * @param {T} func - The function to debounce. + * @param {number} [delay=0] - The debounce delay in milliseconds. + * @returns {(...args: Parameters) => void} The debounced function. + */ export function debounce void>( func: T, delay = 0 @@ -28,6 +50,12 @@ export function debounce void>( }; } +/** + * Checks if the current scroll position is at the target position within a threshold. + * @param {number} targetScrollPosition - The target scroll position in pixels. + * @param {number} [threshold=1] - The allowed threshold in pixels. + * @returns {boolean} True if the current scroll position is within the threshold of the target. + */ export const isScrollPositionAtTarget = ( targetScrollPosition: number, threshold: number = 1 @@ -38,10 +66,27 @@ export const isScrollPositionAtTarget = ( return difference < threshold; }; +/** + * Constrains a number between a lower and upper bound. + * @param {number} n - The number to constrain. + * @param {number} low - The lower bound. + * @param {number} high - The upper bound. + * @returns {number} The constrained value. + */ function constrain(n: number, low: number, high: number): number { return Math.max(Math.min(n, high), low); } +/** + * Maps a number from one range to another. + * @param {number} n - The number to map. + * @param {number} start1 - Lower bound of the value's current range. + * @param {number} stop1 - Upper bound of the value's current range. + * @param {number} start2 - Lower bound of the value's target range. + * @param {number} stop2 - Upper bound of the value's target range. + * @param {boolean} [withinBounds=true] - Whether to constrain the result within the target range. + * @returns {number} The mapped value. + */ export function map( n: number, start1: number, @@ -61,6 +106,11 @@ export function map( } } +/** + * Flattens a ScrollyVideoState object into a single-level object for easier access. + * @param {ScrollyVideoState} obj - The state object to flatten. + * @returns {FlattenedScrollyVideoState} The flattened state object. + */ export function flattenObject( obj: ScrollyVideoState ): FlattenedScrollyVideoState {