diff --git a/src/components/@types/global.ts b/src/components/@types/global.ts index eb3ba45f..9aaee2ed 100644 --- a/src/components/@types/global.ts +++ b/src/components/@types/global.ts @@ -69,7 +69,7 @@ export type ScrollerVideoForegroundPosition = | 'center left' | 'center right'; -export type ScrollerLottieForegroundPosition = +export type LottieForegroundPosition = | 'top center' | 'top left' | 'top right' diff --git a/src/components/ScrollerLottie/Debug.svelte b/src/components/Lottie/Debug.svelte similarity index 97% rename from src/components/ScrollerLottie/Debug.svelte rename to src/components/Lottie/Debug.svelte index 80939dd2..8fc6fafc 100644 --- a/src/components/ScrollerLottie/Debug.svelte +++ b/src/components/Lottie/Debug.svelte @@ -167,8 +167,6 @@ +``` + +## With foregrounds + +The `Lottie` component can also be used with the `LottieForeground` component to display foreground elements at specific times in the animation. + +[Demo](?path=/story/components-graphics-scrollerlottie--with-foregrounds). + +```svelte + + + + + + +
+ + + +
+
+ + + +
+``` +### Using with ArchieML +With the graphics kit, you'll likely get your text and prop values from an ArchieML doc... + +```yaml +# ArchieML doc +[blocks] + type: lottie + + # Lottie file stored in `src/statics/lottie/` folder + src: lottie/LottieFile.zip + + # Array of foregrounds + [.foregrounds] + + # Foreground 1: Headline component + startFrame: 0 # When in the animation to start showing the foreground + endFrame: 50 # When to stop showing the foreground + + # Set foreground type + type: component + + # Set props to pass into `LottieForeground` + {.foregroundProps} + componentName: Headline + hed: Headline + dek: Some deck text + [.authors] + * Jane Doe + * John Smith + [] + {} + + # Foreground 2: Text only + startFrame: 50 + endFrame: 100 + + # Set foreground type + type: text + + # If the foreground type is `text`, set text prop here + {.foregroundProps} + text: Some text for the foreground + {} + +[] +``` + +... which you'll parse out of a ArchieML block object before passing to the `Lottie` component. + +```svelte + + +{#each content.blocks as block} + + {#if block.type == 'lottie'} + + {#each block.foregrounds as foreground} + {#if foreground.type == 'text'} + + {:else if foreground.type == 'component'} + {@const Component = + Components[foreground.foregroundProps.componentName]} + + + + {/if} + {/each} + + {/if} +{/each} +``` diff --git a/src/components/ScrollerLottie/ScrollerLottie.stories.svelte b/src/components/Lottie/Lottie.stories.svelte similarity index 56% rename from src/components/ScrollerLottie/ScrollerLottie.stories.svelte rename to src/components/Lottie/Lottie.stories.svelte index 454ca513..1c77fb66 100644 --- a/src/components/ScrollerLottie/ScrollerLottie.stories.svelte +++ b/src/components/Lottie/Lottie.stories.svelte @@ -1,18 +1,22 @@ - - + + + + + + - - - - - - - - - - + - - - - - +
+ + + +
+ + + +
diff --git a/src/components/ScrollerLottie/ScrollerLottie.svelte b/src/components/Lottie/Lottie.svelte similarity index 60% rename from src/components/ScrollerLottie/ScrollerLottie.svelte rename to src/components/Lottie/Lottie.svelte index 36772dcb..8950ad67 100644 --- a/src/components/ScrollerLottie/ScrollerLottie.svelte +++ b/src/components/Lottie/Lottie.svelte @@ -1,57 +1,25 @@ - +
{#if showDebugInfo && lottiePlayer} {/if} @@ -521,13 +386,15 @@ bind:this={canvas} bind:clientWidth={canvasWidth} bind:clientHeight={canvasHeight} + onmouseenter={handleMouseEnter} + onmouseleave={handleMouseLeave} >
{#if children} - {@render children?.()} + {@render children()} {/if} -
+ diff --git a/src/components/ScrollerLottie/data/defaultLottie.lottie b/src/components/Lottie/lottie/demo.lottie similarity index 100% rename from src/components/ScrollerLottie/data/defaultLottie.lottie rename to src/components/Lottie/lottie/demo.lottie diff --git a/src/components/ScrollerLottie/data/dotlottie-player.wasm b/src/components/Lottie/lottie/dotlottie-player.wasm similarity index 100% rename from src/components/ScrollerLottie/data/dotlottie-player.wasm rename to src/components/Lottie/lottie/dotlottie-player.wasm diff --git a/src/components/ScrollerLottie/data/foregroundSample.lottie b/src/components/Lottie/lottie/foregroundSample.lottie similarity index 100% rename from src/components/ScrollerLottie/data/foregroundSample.lottie rename to src/components/Lottie/lottie/foregroundSample.lottie diff --git a/src/components/ScrollerLottie/data/markerSample.lottie b/src/components/Lottie/lottie/markerSample.lottie similarity index 100% rename from src/components/ScrollerLottie/data/markerSample.lottie rename to src/components/Lottie/lottie/markerSample.lottie diff --git a/src/components/ScrollerLottie/data/themesLottie.lottie b/src/components/Lottie/lottie/themesLottie.lottie similarity index 100% rename from src/components/ScrollerLottie/data/themesLottie.lottie rename to src/components/Lottie/lottie/themesLottie.lottie diff --git a/src/components/ScrollerLottie/ts/lottieState.svelte.ts b/src/components/Lottie/ts/lottieState.svelte.ts similarity index 100% rename from src/components/ScrollerLottie/ts/lottieState.svelte.ts rename to src/components/Lottie/ts/lottieState.svelte.ts diff --git a/src/components/Lottie/ts/types.ts b/src/components/Lottie/ts/types.ts new file mode 100644 index 00000000..64f17a8e --- /dev/null +++ b/src/components/Lottie/ts/types.ts @@ -0,0 +1,44 @@ + +// Types +import type { Snippet } from 'svelte'; +import { + type Config, + type DotLottie as DotLottieType, +} from '@lottiefiles/dotlottie-web'; +import { type LottieState } from './lottieState.svelte'; + +type DotlottieProps = { + autoplay?: Config['autoplay']; + backgroundColor?: Config['backgroundColor']; + data?: Config['data']; + loop?: Config['loop']; + mode?: Config['mode']; + renderConfig?: Config['renderConfig']; + segment?: Config['segment']; + speed?: Config['speed']; + src: Config['src']; + useFrameInterpolation?: Config['useFrameInterpolation']; + marker?: Config['marker'] | undefined; + layout?: Config['layout']; + animationId?: Config['animationId']; + themeId?: Config['themeId']; + playOnHover?: boolean; + themeData?: string; + dotLottieRefCallback?: (dotLottie: DotLottieType) => void; + onLoad?: () => void; + onRender?: () => void; + onComplete?: () => void; +}; + +export type Props = DotlottieProps & { + // Additional properties can be added here if needed + lottiePlayer?: DotLottieType | undefined; + showDebugInfo?: boolean; + height?: string; + lottieState?: LottieState; + progress?: number; + tweenDuration?: number; + easing?: (t: number) => number; + /** Children render function */ + children?: Snippet; +}; diff --git a/src/components/Lottie/ts/utils.ts b/src/components/Lottie/ts/utils.ts new file mode 100644 index 00000000..e2ebe277 --- /dev/null +++ b/src/components/Lottie/ts/utils.ts @@ -0,0 +1,111 @@ +import type { DotLottie } from '@lottiefiles/dotlottie-web'; +import type { LottieState } from './lottieState.svelte'; + +function constrain(n: number, low: number, high: number) { + return Math.max(Math.min(n, high), low); +} + +export function map( + n: number, + start1: number, + stop1: number, + start2: number, + stop2: number, + withinBounds: boolean = true +) { + const newval = ((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2; + if (!withinBounds) { + return newval; + } + if (start2 < stop2) { + return constrain(newval, start2, stop2); + } else { + return constrain(newval, stop2, start2); + } +} + +/** + * Syncs the lottie player state with the component's lottie state + */ +export function syncLottieState( + lottiePlayer: DotLottie, + lottieState: LottieState +) { + lottieState.currentFrame = lottiePlayer.currentFrame; + lottieState.totalFrames = lottiePlayer.totalFrames; + lottieState.duration = lottiePlayer.duration; + lottieState.loop = lottiePlayer.loop; + lottieState.speed = lottiePlayer.speed; + lottieState.loopCount = lottiePlayer.loopCount; + lottieState.mode = lottiePlayer.mode; + lottieState.isPaused = lottiePlayer.isPaused; + lottieState.isPlaying = lottiePlayer.isPlaying; + lottieState.isStopped = lottiePlayer.isStopped; + lottieState.isLoaded = lottiePlayer.isLoaded; + lottieState.isFrozen = lottiePlayer.isFrozen; + lottieState.segment = lottiePlayer.segment ?? null; + lottieState.autoplay = lottiePlayer.autoplay ?? false; + lottieState.layout = lottiePlayer.layout ?? null; + lottieState.activeThemeId = lottiePlayer.activeThemeId ?? null; + lottieState.marker = lottiePlayer.marker ?? undefined; +} + +/** + * Gets marker info by name + */ +export function getMarkerByName(lottiePlayer: DotLottie, markerName: string) { + return lottiePlayer.markers().find((m) => m.name === markerName); +} + +/** + * Gets the start and end frames for a marker + */ +export function getMarkerRange( + lottiePlayer: DotLottie, + markerName: string +): [number, number] { + const marker = getMarkerByName(lottiePlayer, markerName); + const start = marker?.time ?? 0; + const end = start + (marker?.duration ?? 0); + return [start, end]; +} + +/** + * Calculates target frame based on progress and mode + */ +export function calculateTargetFrame( + progress: number, + mode: string, + start: number, + end: number +): number { + const adjustedProgress = + mode === 'reverse' || mode === 'reverse-bounce' ? 1 - progress : progress; + return map(adjustedProgress, 0, 1, start, end); +} + +/** + * Determines if mode is reverse + */ +export function isReverseMode(mode: string): boolean { + return mode === 'reverse' || mode === 'reverse-bounce'; +} + +/** + * Creates render config with optimized defaults + */ +export function createRenderConfig() { + return { + autoResize: true, + devicePixelRatio: + window.devicePixelRatio > 1 ? window.devicePixelRatio * 0.75 : 1, + freezeOnOffscreen: true, + }; +} + +/** + * Checks if a value is null or undefined (empty marker check) + */ +export function isNullish(value: any): boolean { + return value === null || value === undefined || value === ''; +} diff --git a/src/components/ScrollerLottie/ScrollerLottie.mdx b/src/components/ScrollerLottie/ScrollerLottie.mdx deleted file mode 100644 index 79ddb5eb..00000000 --- a/src/components/ScrollerLottie/ScrollerLottie.mdx +++ /dev/null @@ -1,363 +0,0 @@ -import { Meta } from '@storybook/blocks'; - -import * as ScrollerLottieStories from './ScrollerLottie.stories.svelte'; -import CompositionMarkerImage from './assets/marker.png?url'; - - - -# ScrollerLottie - -The `ScrollerLottie` component plays Lottie animations. It uses the [dotLottie-web](https://developers.lottiefiles.com/docs/dotlottie-player/dotlottie-web/) library to render the animations. - -## How to use .lottie files - -LottieFiles is the official platform for creating and editing Lottie animations, and exporting them in the dotLottie format for smaller file sizes. The free version of LottieFiles has limited features, so [Bodymovin](https://exchange.adobe.com/apps/cc/12557/bodymovin) remains a popular, free way to export animations as JSON files. You can use the [LottieFiles converter](https://lottiefiles.com/tools/lottie-to-dotlottie) to convert JSON files to dotLottie or optimized dotLottie formats. This component is flexible and supports both dotLottie and JSON animation files. - -> 🚧NOTE: For optimal compatibility with graphics-publisher, export your JSON files as optimized dotLottie format and rename the file extension to `*.zip`. This approach ensures full publisher support while maintaining the benefits of the dotLottie format's compression and optimization. - -## Basic demo - -To use the `ScrollerLottie` component, import it and provide the animation source. The height defaults to `100lvh`, but you can adjust this to any valid CSS height value such as `1200px` or `200lvh` with the `height` prop. - -The .lottie or .json file should be placed at the same level as the component file. If using it inside `App.svelte`, create a `data` folder and place all the animation files inside. Make sure to append **?url** to the import statement when importing an animation file, as shown in the example below. This ensures that the file is treated as a URL. - -> 💡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. - -> 💡TIP: Use showDebugInfo prop to display additional information about the component state. - -[Demo](?path=/story/components-graphics-scrollerlottie--basic) - -With the graphics kit, you'll likely get your text and prop values from an ArchieML doc... - -```yaml -# ArchieML doc -[blocks] - type: lottie - src: LottieFile.zip - :end -[] -``` - -... which you'll parse out of a ArchieML block object before passing to the `ScrollerLottie` component. - -```svelte - - -{#each content.blocks as block} - - {#if block.type == 'lottie'} - - {/if} -{/each} -``` - -## Playing a marker - -It is possible to play a specific portion of the animation using markers. Markers can be set in [AfterEffects](https://helpx.adobe.com/in/after-effects/using/layer-markers-composition-markers.html) to define separate portions of the animation. A specific marker can be played by using the `marker` prop. - -The list of available markers can be found in the debug info when `showDebugInfo` prop is enabled. - -> 💡NOTE: The **Comment** section of the Composition Marker dialog should only contain the name of your marker. - -Composition Marker Dialog - -[Demo](?path=/story/components-graphics-scrollerlottie--marker) - -With the graphics kit, you'll likely get your text and prop values from an ArchieML doc... - -```yaml -# ArchieML doc -[blocks] - type: lottie - src: LottieFile.zip - marker: myMarker - :end -[] -``` - -... which you'll parse out of a ArchieML block object before passing to the `ScrollerLottie` component. - -```svelte - - -{#each content.blocks as block} - - {#if block.type == 'lottie'} - - {/if} -{/each} -``` - -## Playing a segment - -Just like markers, it is also possible to play a specific segment of the animation using the `segment` prop. The `segment` prop expects an array of two numbers representing the start and end frames of the segment. - -[Demo](?path=/story/components-graphics-scrollerlottie--segment) - -With the graphics kit, you'll likely get your text and prop values from an ArchieML doc... - -```yaml -# ArchieML doc -[blocks] - type: lottie - src: LottieFile.zip - [.segment] - start: 0 - end: 20 - [] - :end -[] -``` - -... which you'll parse out of a ArchieML block object before passing to the `ScrollerLottie` component. - -```svelte - - -{#each content.blocks as block} - - {#if block.type == 'lottie'} - - {/if} -{/each} -``` - -## Switching themes - -[Lottie Creator](https://lottiefiles.com/theming) allows you to define multiple color themes for your animation. You can switch between these themes using the `theme` prop. - -Available themes can be found in the debug info when `showDebugInfo` prop is enabled. - -With the graphics kit, you'll likely get your text and prop values from an ArchieML doc... - -```yaml -# ArchieML doc -[blocks] - type: lottie - src: LottieFile.zip - theme: myTheme - :end -[] -``` - -... which you'll parse out of a ArchieML block object before passing to the `ScrollerLottie` component. - -```svelte - - -{#each content.blocks as block} - - {#if block.type == 'lottie'} - - {/if} -{/each} -``` - -It is also possible to switch themes dynamically based on the `progress` prop by binding a variable to it. - -[Demo](?path=/story/components-graphics-scrollerlottie--themes) - -```svelte - - - -``` - -## With ScrollerBase - -The `ScrollerLottie` component can be used in conjunction with the `ScrollerBase` component to create a more complex scrolling experience. The `ScrollerBase` component provides a scrollable container that can hold the `ScrollerLottie` component as a background. - -```svelte - - - - {#snippet backgroundSnippet()} - -
- -
- {/snippet} - {#snippet foregroundSnippet()} - -
-

Step 1

-
-
-

Step 2

-
-
-

Step 3

-
- {/snippet} -
- - -``` - -## With foregrounds - -The `ScrollerLottie` component can also be used to display captions or even components, such as `Headline` or ai2svelte files, as foregrounds at specific times in the animation. To do so, add ScrollerLottieForeground components as children of the ScrollerLottie component. - -[Demo](?path=/story/components-graphics-scrollerlottie--with-foregrounds) - -With the graphics kit, you'll likely get your text and prop values from an ArchieML doc... - -```yaml -# ArchieML doc -[blocks] - type: lottie - src: LottieFile.zip - - # Array of foregrounds - [.foregrounds] - startFrame: 0 # When in the animation to start showing the foreground - endFrame: 50 # When to stop showing the foreground - type: text - {.foregroundProps} - text: Some text for the foreground - {} - - startFrame: 50 # When in the animation to start showing the foreground - endFrame: 100 # When to stop showing the foreground - type: component - {.foregroundProps} - componentType: Headline - hed: Some headline text - dek: Some deck text - [.authors] - * Jane Doe - * John Smith - [] - {} - [] - :end - -[] -``` - -... which you'll parse out of a ArchieML block object before passing to the `ScrollerLottie` component. - -```svelte - - -{#each content.blocks as block} - - {#if block.type == 'lottie'} - - {#each block.foregrounds as foreground} - {#if foreground.type == 'text'} - - {:else if foreground.type == 'component'} - {@const Component = - Components[foreground.foregroundProps.componentType]} - - - - {/if} - {/each} - - {/if} -{/each} -``` diff --git a/src/components/ScrollerLottie/assets/marker.png b/src/components/ScrollerLottie/assets/marker.png deleted file mode 100644 index 9b1f2ee2..00000000 Binary files a/src/components/ScrollerLottie/assets/marker.png and /dev/null differ diff --git a/src/components/ScrollerLottie/demo/withScrollerBase.svelte b/src/components/ScrollerLottie/demo/withScrollerBase.svelte deleted file mode 100644 index 91002a8f..00000000 --- a/src/components/ScrollerLottie/demo/withScrollerBase.svelte +++ /dev/null @@ -1,69 +0,0 @@ - - - - {#snippet backgroundSnippet()} - -
- -
- {/snippet} - {#snippet foregroundSnippet()} - -

Step 1

-

Step 2

-

Step 3

-

Step 4

-

Step 5

- {/snippet} -
- - diff --git a/src/components/ScrollerLottie/ts/utils.ts b/src/components/ScrollerLottie/ts/utils.ts deleted file mode 100644 index 803d201d..00000000 --- a/src/components/ScrollerLottie/ts/utils.ts +++ /dev/null @@ -1,22 +0,0 @@ -function constrain(n: number, low: number, high: number) { - return Math.max(Math.min(n, high), low); -} - -export function map( - n: number, - start1: number, - stop1: number, - start2: number, - stop2: number, - withinBounds: boolean = true -) { - const newval = ((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2; - if (!withinBounds) { - return newval; - } - if (start2 < stop2) { - return constrain(newval, start2, stop2); - } else { - return constrain(newval, stop2, start2); - } -} diff --git a/src/components/ScrollerVideo/Debug.svelte b/src/components/ScrollerVideo/Debug.svelte index 547b3f04..b922f22a 100644 --- a/src/components/ScrollerVideo/Debug.svelte +++ b/src/components/ScrollerVideo/Debug.svelte @@ -120,8 +120,6 @@