210 lines
7.6 KiB
Svelte
210 lines
7.6 KiB
Svelte
<script lang="ts">
|
|
import { onDestroy } from 'svelte';
|
|
import { flattenObject } from './js/utils.js';
|
|
import ScrollyVideo from './js/ScrollyVideo.js';
|
|
import type { Snippet } from 'svelte';
|
|
import { setContext } from 'svelte';
|
|
|
|
/**
|
|
* 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;
|
|
/** ID of the scrolly container */
|
|
id?: string;
|
|
/** Bindable instance of ScrollyVideo */
|
|
scrollyVideo?: ScrollyVideo;
|
|
/** Video source URL */
|
|
src?: string;
|
|
/** Bindable percentage value to control video playback. **Ranges from 0 to 1** */
|
|
videoPercentage?: number;
|
|
/** Sets the maximum playbackRate for this video */
|
|
transitionSpeed?: number;
|
|
/** When to stop the video animation, in seconds */
|
|
frameThreshold?: number;
|
|
/** How the video should be resized to fit its container */
|
|
objectFit?: string;
|
|
/** Whether the video should have position: sticky */
|
|
sticky?: boolean;
|
|
/** Whether the video should take up the entire viewport */
|
|
full?: boolean;
|
|
/** Whether this object should automatically respond to scroll. Set this to **false** while manually controlling `videoPercentage` prop. */
|
|
trackScroll?: boolean;
|
|
/** Whether it ignores human scroll while it runs setVideoPercentage with enabled trackScroll */
|
|
lockScroll?: boolean;
|
|
/** Whether the library should use the webcodecs method. For more info, visit https://scrollyvideo.js.org/ */
|
|
useWebCodecs?: boolean;
|
|
/** The callback when it's ready to scroll */
|
|
onReady?: () => void;
|
|
/** The callback for video percentage change */
|
|
onChange?: () => void;
|
|
/** Whether to log debug information. Internal library logs. */
|
|
debug?: boolean;
|
|
/** Shows debug information on page */
|
|
showDebugInfo?: boolean;
|
|
/** Height of the video container. Set it to 100svh when using inside `ScrollerBase` */
|
|
height?: string;
|
|
/** Whether the video should autoplay */
|
|
autoplay?: boolean;
|
|
/** Variable to control component rendering on embed page */
|
|
embedded?: boolean;
|
|
/** Source for the embedded video. If not provided, defaults to `src` */
|
|
embeddedSrc?: string;
|
|
/** Children render function */
|
|
children?: Snippet;
|
|
}
|
|
|
|
/**
|
|
* Main logic for ScrollyVideo Svelte component.
|
|
* Handles instantiation, prop changes, and cleanup.
|
|
*/
|
|
let {
|
|
scrollyVideo = $bindable(),
|
|
videoPercentage,
|
|
onReady = $bindable(() => {}),
|
|
onChange = $bindable(() => {}),
|
|
height = '200svh',
|
|
showDebugInfo = false,
|
|
class: cls = '',
|
|
id = '',
|
|
embedded = false,
|
|
embeddedSrc = '',
|
|
children,
|
|
...restProps
|
|
}: Props = $props();
|
|
|
|
// variable to hold the DOM element
|
|
/**
|
|
* Reference to the scrolly video container DOM element.
|
|
* @type {HTMLDivElement | undefined}
|
|
*/
|
|
let scrollyVideoContainer = $state<HTMLDivElement | undefined>(undefined);
|
|
|
|
// Store the props so we know when things change
|
|
let lastPropsString = '';
|
|
|
|
$effect(() => {
|
|
if (scrollyVideoContainer) {
|
|
if (JSON.stringify(restProps) !== lastPropsString) {
|
|
// if scrollyvideo already exists and any parameter is updated, destroy and recreate.
|
|
if (scrollyVideo && scrollyVideo.destroy) scrollyVideo.destroy();
|
|
scrollyVideo = new ScrollyVideo({
|
|
scrollyVideoContainer,
|
|
onReady,
|
|
onChange,
|
|
...restProps,
|
|
});
|
|
|
|
// pass on component state to child components
|
|
// this controls fade in and out of foregrounds
|
|
setContext('scrollyVideoState', scrollyVideo.componentState);
|
|
|
|
// Save the new props
|
|
lastPropsString = JSON.stringify(restProps);
|
|
}
|
|
|
|
// If we need to update the target time percent
|
|
if (
|
|
scrollyVideo &&
|
|
typeof videoPercentage === 'number' &&
|
|
videoPercentage >= 0 &&
|
|
videoPercentage <= 1
|
|
) {
|
|
scrollyVideo.setVideoPercentage(videoPercentage);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 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.
|
|
* @type {string}
|
|
*/
|
|
let heightChange = $derived.by(() => {
|
|
if (scrollyVideo) {
|
|
return `calc(${height} * ${1 - scrollyVideo?.componentState.autoplayProgress})`;
|
|
} else {
|
|
return height;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
{#if embedded && (embeddedSrc || restProps.src)}
|
|
<div class="scrolly-video-container" style="width: 100%;">
|
|
<video
|
|
class="scrolly-video-embedded"
|
|
src={embeddedSrc || restProps.src}
|
|
autoplay
|
|
loop
|
|
muted
|
|
playsinline
|
|
style="width: 100%;"
|
|
></video>
|
|
</div>
|
|
{:else}
|
|
<div
|
|
{id}
|
|
class="scrolly-video-container {cls}"
|
|
style="height: {heightChange}; width: 100%; min-height: 100svh;"
|
|
>
|
|
<div bind:this={scrollyVideoContainer} data-scrolly-container>
|
|
{#if showDebugInfo && scrollyVideo}
|
|
<p class="debug-info text-xxs font-sans">
|
|
{@html JSON.stringify(flattenObject(scrollyVideo.componentState))
|
|
.replace(/[{}"]/g, '')
|
|
.split(',')
|
|
.join('<br>')}
|
|
</p>
|
|
|
|
<!-- renders foregrounds -->
|
|
{#if children}
|
|
{@render children()}
|
|
{/if}
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
|
|
<style lang="scss">
|
|
.debug-info {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
color: white;
|
|
background-color: rgba(0, 0, 0, 0.8);
|
|
z-index: 3;
|
|
padding: 8px;
|
|
margin: 0;
|
|
min-width: 25vmin;
|
|
}
|
|
</style>
|