fix TS issues

This commit is contained in:
Sudev Kiyada 2025-10-03 12:12:00 +05:30
parent fc17d3b911
commit f3375f02b1
Failed to extract signature
5 changed files with 142 additions and 123 deletions

View file

@ -9,6 +9,10 @@ import CompositionMarkerImage from './assets/marker.png?url';
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. 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.
## Basic demo ## 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. 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.
@ -27,7 +31,7 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
# ArchieML doc # ArchieML doc
[blocks] [blocks]
type: lottie type: lottie
src: LottieFile src: LottieFile.lottie
:end :end
[] []
``` ```
@ -37,13 +41,14 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
```svelte ```svelte
<script lang="ts"> <script lang="ts">
import { ScrollerLottie } from '@reuters-graphics/graphics-components'; import { ScrollerLottie } from '@reuters-graphics/graphics-components';
import { assets } from '$app/paths';
</script> </script>
{#each content.blocks as block} {#each content.blocks as block}
<!-- Inside the content.blocks for loop... --> <!-- Inside the content.blocks for loop... -->
{#if block.type == 'lottie'} {#if block.type == 'lottie'}
<ScrollerLottie <ScrollerLottie
src={`./data/${block.src}.lottie?url`} src={`${assets}/animations/${block.src}`}
autoplay autoplay
loop loop
showDebugInfo showDebugInfo
@ -70,7 +75,7 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
# ArchieML doc # ArchieML doc
[blocks] [blocks]
type: lottie type: lottie
src: LottieFile src: LottieFile.lottie
marker: myMarker marker: myMarker
:end :end
[] []
@ -81,13 +86,14 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
```svelte ```svelte
<script lang="ts"> <script lang="ts">
import { ScrollerLottie } from '@reuters-graphics/graphics-components'; import { ScrollerLottie } from '@reuters-graphics/graphics-components';
import { assets } from '$app/paths';
</script> </script>
{#each content.blocks as block} {#each content.blocks as block}
<!-- Inside the content.blocks for loop... --> <!-- Inside the content.blocks for loop... -->
{#if block.type == 'lottie'} {#if block.type == 'lottie'}
<ScrollerLottie <ScrollerLottie
src={`./data/${block.src}.lottie?url`} src={`${assets}/animations/${block.src}`}
marker={block.marker} marker={block.marker}
autoplay autoplay
loop loop
@ -109,7 +115,7 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
# ArchieML doc # ArchieML doc
[blocks] [blocks]
type: lottie type: lottie
src: LottieFile src: LottieFile.lottie
[.segment] [.segment]
start: 0 start: 0
end: 20 end: 20
@ -123,13 +129,14 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
```svelte ```svelte
<script lang="ts"> <script lang="ts">
import { ScrollerLottie } from '@reuters-graphics/graphics-components'; import { ScrollerLottie } from '@reuters-graphics/graphics-components';
import { assets } from '$app/paths';
</script> </script>
{#each content.blocks as block} {#each content.blocks as block}
<!-- Inside the content.blocks for loop... --> <!-- Inside the content.blocks for loop... -->
{#if block.type == 'lottie'} {#if block.type == 'lottie'}
<ScrollerLottie <ScrollerLottie
src={`./data/${block.src}.lottie?url`} src={`${assets}/animations/${block.src}`}
segment={[block.segment.start, block.segment.end]} segment={[block.segment.start, block.segment.end]}
autoplay autoplay
loop loop
@ -151,7 +158,7 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
# ArchieML doc # ArchieML doc
[blocks] [blocks]
type: lottie type: lottie
src: LottieFile src: LottieFile.lottie
theme: myTheme theme: myTheme
:end :end
[] []
@ -162,13 +169,14 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
```svelte ```svelte
<script lang="ts"> <script lang="ts">
import { ScrollerLottie } from '@reuters-graphics/graphics-components'; import { ScrollerLottie } from '@reuters-graphics/graphics-components';
import { assets } from '$app/paths';
</script> </script>
{#each content.blocks as block} {#each content.blocks as block}
<!-- Inside the content.blocks for loop... --> <!-- Inside the content.blocks for loop... -->
{#if block.type == 'lottie'} {#if block.type == 'lottie'}
<ScrollerLottie <ScrollerLottie
src={`./data/${block.src}.lottie?url`} src={`${assets}/animations/${block.src}`}
theme={block.theme} theme={block.theme}
autoplay autoplay
loop loop
@ -270,7 +278,7 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
# ArchieML doc # ArchieML doc
[blocks] [blocks]
type: lottie type: lottie
src: LottieFile src: LottieFile.lottie
# Array of foregrounds # Array of foregrounds
[.foregrounds] [.foregrounds]
@ -303,7 +311,11 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
```svelte ```svelte
<script lang="ts"> <script lang="ts">
import { ScrollerLottie } from '@reuters-graphics/graphics-components'; import {
ScrollerLottie,
ScrollerLottieForeground,
} from '@reuters-graphics/graphics-components';
import { assets } from '$app/paths';
const Components = $state({ const Components = $state({
Headline, Headline,
@ -315,7 +327,7 @@ With the graphics kit, you'll likely get your text and prop values from an Archi
<!-- Inside the content.blocks for loop... --> <!-- Inside the content.blocks for loop... -->
{#if block.type == 'lottie'} {#if block.type == 'lottie'}
<ScrollerLottie <ScrollerLottie
src={`./data/${block.src}.lottie?url`} src={`${assets}/animations/${block.src}`}
theme={block.theme} theme={block.theme}
autoplay autoplay
loop loop

View file

@ -27,7 +27,7 @@
speed?: Config['speed']; speed?: Config['speed'];
src?: Config['src']; src?: Config['src'];
useFrameInterpolation?: Config['useFrameInterpolation']; useFrameInterpolation?: Config['useFrameInterpolation'];
marker?: Config['marker'] | null | undefined; marker?: Config['marker'] | undefined;
layout?: Config['layout']; layout?: Config['layout'];
animationId?: Config['animationId']; animationId?: Config['animationId'];
themeId?: Config['themeId']; themeId?: Config['themeId'];
@ -56,8 +56,8 @@
let canvas: HTMLCanvasElement; let canvas: HTMLCanvasElement;
let canvasWidth: number = $state(1); let canvasWidth: number = $state(1);
let canvasHeight: number = $state(1); let canvasHeight: number = $state(1);
let prevSrc = void 0; let prevSrc: undefined | string = void 0;
let prevData = void 0; let prevData: undefined | unknown = void 0;
let progressTween = new Tween(0, { duration: 100 }); let progressTween = new Tween(0, { duration: 100 });
let start: number = $state(0); let start: number = $state(0);
let end: number = $state(0); let end: number = $state(0);
@ -77,7 +77,7 @@
themeId = '', themeId = '',
themeData = '', themeData = '',
playOnHover = false, playOnHover = false,
marker = '', marker = undefined,
layout = { fit: 'contain', align: [0.5, 0.5] }, layout = { fit: 'contain', align: [0.5, 0.5] },
animationId = '', animationId = '',
lottiePlayer = $bindable(undefined), lottiePlayer = $bindable(undefined),
@ -99,8 +99,7 @@
setContext('lottieState', lottieState); setContext('lottieState', lottieState);
function onLoadEvent() { function onLoadEvent() {
if (showDebugInfo) { if (lottiePlayer) {
// set layout
lottiePlayer.setLayout(layout); lottiePlayer.setLayout(layout);
lottieState.allMarkers = lottiePlayer.markers().map((x) => x.name); lottieState.allMarkers = lottiePlayer.markers().map((x) => x.name);
@ -116,13 +115,13 @@
start = segment ? segment[0] : 0; start = segment ? segment[0] : 0;
end = segment ? segment[1] : lottiePlayer.totalFrames - 1; end = segment ? segment[1] : lottiePlayer.totalFrames - 1;
} }
// set to frame 1 to trigger initial render
// helpful especially when themeId is set
lottiePlayer.setFrame(1);
onLoad(); // call user-defined onLoad function
} }
// set to frame 1 to trigger initial render
// helpful especially when themeId is set
lottiePlayer.setFrame(1);
onLoad(); // call user-defined onLoad function
} }
function onCompleteEvent() { function onCompleteEvent() {
@ -152,23 +151,73 @@
if (lottiePlayer && lottieState) { if (lottiePlayer && lottieState) {
keys.forEach((key) => { keys.forEach((key) => {
lottieState[key] = lottiePlayer[key]; switch (key) {
case 'currentFrame':
lottieState.currentFrame = lottiePlayer!.currentFrame;
break;
case 'totalFrames':
lottieState.totalFrames = lottiePlayer!.totalFrames;
break;
case 'duration':
lottieState.duration = lottiePlayer!.duration;
break;
case 'loop':
lottieState.loop = lottiePlayer!.loop;
break;
case 'speed':
lottieState.speed = lottiePlayer!.speed;
break;
case 'loopCount':
lottieState.loopCount = lottiePlayer!.loopCount;
break;
case 'mode':
lottieState.mode = lottiePlayer!.mode;
break;
case 'isPaused':
lottieState.isPaused = lottiePlayer!.isPaused;
break;
case 'isPlaying':
lottieState.isPlaying = lottiePlayer!.isPlaying;
break;
case 'isStopped':
lottieState.isStopped = lottiePlayer!.isStopped;
break;
case 'isLoaded':
lottieState.isLoaded = lottiePlayer!.isLoaded;
break;
case 'isFrozen':
lottieState.isFrozen = lottiePlayer!.isFrozen;
break;
case 'segment':
lottieState.segment = lottiePlayer!.segment ?? null;
break;
case 'autoplay':
lottieState.autoplay = lottiePlayer!.autoplay ?? false;
break;
case 'layout':
lottieState.layout = lottiePlayer!.layout ?? null;
break;
case 'activeThemeId':
lottieState.activeThemeId = lottiePlayer!.activeThemeId ?? null;
break;
case 'marker':
lottieState.marker = lottiePlayer!.marker ?? undefined;
break;
}
}); });
progress = (lottiePlayer.currentFrame + 1) / lottiePlayer.totalFrames;
lottieState.progress = progress;
onRender(); // call user-defined onRender function
} }
progress = (lottiePlayer.currentFrame + 1) / lottiePlayer.totalFrames;
lottieState.progress = progress;
onRender(); // call user-defined onRender function
} }
const hoverHandler = (event) => { const hoverHandler = (event: MouseEvent) => {
if (!playOnHover || !lottiePlayer.isLoaded) return; if (!playOnHover || !lottiePlayer?.isLoaded) return;
if (event.type === 'mouseenter') { if (event.type === 'mouseenter') {
lottiePlayer.play(); lottiePlayer?.play();
} else if (event.type === 'mouseleave') { } else if (event.type === 'mouseleave') {
lottiePlayer.pause(); lottiePlayer?.pause();
} }
}; };
@ -216,7 +265,7 @@
return () => { return () => {
canvas.removeEventListener('mouseenter', hoverHandler); canvas.removeEventListener('mouseenter', hoverHandler);
canvas.removeEventListener('mouseleave', hoverHandler); canvas.removeEventListener('mouseleave', hoverHandler);
lottiePlayer.destroy(); lottiePlayer?.destroy();
}; };
}); });
@ -232,12 +281,12 @@
$effect(() => { $effect(() => {
if (lottieState.isLoaded && lottieState.progress !== progress) { if (lottieState.isLoaded && lottieState.progress !== progress) {
autoplay = false; autoplay = false;
lottiePlayer.pause(); lottiePlayer?.pause();
loop = false; loop = false;
if (progress >= 0 && progress <= 1) { if (progress >= 0 && progress <= 1) {
if (lottieState.isFrozen) { if (lottieState.isFrozen) {
lottiePlayer.unfreeze(); lottiePlayer?.unfreeze();
lottieState.isFrozen = false; lottieState.isFrozen = false;
} }
const targetFrame = map( const targetFrame = map(
@ -258,7 +307,7 @@
} else { } else {
progressTween.target = progress < 0 ? start : end; progressTween.target = progress < 0 ? start : end;
} }
lottiePlayer.freeze(); lottiePlayer?.freeze();
lottieState.isFrozen = true; lottieState.isFrozen = true;
} }
} }
@ -267,7 +316,7 @@
// Tweens to progress value // Tweens to progress value
$effect(() => { $effect(() => {
if (progressTween.current >= 0) { if (progressTween.current >= 0) {
lottiePlayer.setFrame(progressTween.current); lottiePlayer?.setFrame(progressTween.current);
} }
}); });
@ -275,7 +324,7 @@
$effect(() => { $effect(() => {
if ( if (
typeof layout === 'object' && typeof layout === 'object' &&
lottiePlayer.isLoaded && lottiePlayer?.isLoaded &&
!isEqual(layout, lottiePlayer.layout) !isEqual(layout, lottiePlayer.layout)
) { ) {
lottiePlayer.setLayout(layout); lottiePlayer.setLayout(layout);
@ -284,15 +333,15 @@
// Handles marker change // Handles marker change
$effect(() => { $effect(() => {
if (lottieState.isLoaded && lottiePlayer.marker !== marker) { if (lottieState.isLoaded && lottiePlayer?.marker !== marker) {
if (typeof marker === 'string') { if (typeof marker === 'string') {
lottiePlayer.setMarker(marker); lottiePlayer?.setMarker(marker);
start = start =
lottiePlayer.markers().find((m) => m.name === marker)?.time ?? 0; lottiePlayer?.markers().find((m) => m.name === marker)?.time ?? 0;
end = end =
start + start +
(lottiePlayer.markers().find((m) => m.name === marker)?.duration ?? (lottiePlayer?.markers().find((m) => m.name === marker)?.duration ??
0); 0);
// change lottieState marker because // change lottieState marker because
@ -301,7 +350,7 @@
lottieState.marker = marker; lottieState.marker = marker;
} }
} else if (marker === null || marker === undefined) { } else if (marker === null || marker === undefined) {
lottiePlayer.setMarker(''); lottiePlayer?.setMarker('');
} else { } else {
console.warn('Invalid marker type:', marker); console.warn('Invalid marker type:', marker);
} }
@ -313,9 +362,9 @@
if ( if (
lottieState.isLoaded && lottieState.isLoaded &&
typeof speed == 'number' && typeof speed == 'number' &&
lottiePlayer.speed !== speed lottiePlayer?.speed !== speed
) { ) {
lottiePlayer.setSpeed(speed); lottiePlayer?.setSpeed(speed);
} }
}); });
@ -324,15 +373,15 @@
if ( if (
lottieState.isLoaded && lottieState.isLoaded &&
typeof useFrameInterpolation == 'boolean' && typeof useFrameInterpolation == 'boolean' &&
lottiePlayer.useFrameInterpolation !== useFrameInterpolation lottiePlayer?.useFrameInterpolation !== useFrameInterpolation
) { ) {
lottiePlayer.setUseFrameInterpolation(useFrameInterpolation); lottiePlayer?.setUseFrameInterpolation(useFrameInterpolation);
} }
}); });
// Handles segment change // Handles segment change
$effect(() => { $effect(() => {
if (lottieState.isLoaded && !isEqual(lottiePlayer.segment, segment)) { if (lottieState.isLoaded && !isEqual(lottiePlayer?.segment, segment)) {
if ( if (
Array.isArray(segment) && Array.isArray(segment) &&
segment.length === 2 && segment.length === 2 &&
@ -340,9 +389,9 @@
typeof segment[1] === 'number' typeof segment[1] === 'number'
) { ) {
let [start, end] = segment; let [start, end] = segment;
lottiePlayer.setSegment(start, end); lottiePlayer?.setSegment(start, end);
} else if (segment === null || segment === undefined) { } else if (segment === null || segment === undefined) {
lottiePlayer.setSegment(0, lottiePlayer.totalFrames); lottiePlayer?.setSegment(0, lottiePlayer?.totalFrames);
} }
} }
}); });
@ -352,9 +401,9 @@
if ( if (
lottieState.isLoaded && lottieState.isLoaded &&
typeof loop == 'boolean' && typeof loop == 'boolean' &&
lottiePlayer.loop !== loop lottiePlayer?.loop !== loop
) { ) {
lottiePlayer.setLoop(loop); lottiePlayer?.setLoop(loop);
} }
}); });
@ -373,9 +422,9 @@
$effect(() => { $effect(() => {
if ( if (
lottieState.isLoaded && lottieState.isLoaded &&
lottiePlayer.backgroundColor !== backgroundColor lottiePlayer?.backgroundColor !== backgroundColor
) { ) {
lottiePlayer.setBackgroundColor(backgroundColor || ''); lottiePlayer?.setBackgroundColor(backgroundColor || '');
} }
}); });
@ -384,16 +433,16 @@
if ( if (
lottieState.isLoaded && lottieState.isLoaded &&
typeof mode == 'string' && typeof mode == 'string' &&
lottiePlayer.mode !== mode lottiePlayer?.mode !== mode
) { ) {
lottiePlayer.setMode(mode); lottiePlayer?.setMode(mode);
} }
}); });
// Handles src change // Handles src change
$effect(() => { $effect(() => {
if (lottieState && src !== prevSrc) { if (lottieState && src !== prevSrc) {
lottiePlayer.load({ lottiePlayer?.load({
src, src,
autoplay, autoplay,
loop, loop,
@ -441,23 +490,23 @@
$effect(() => { $effect(() => {
if ( if (
lottieState.isLoaded && lottieState.isLoaded &&
lottiePlayer.activeAnimationId !== animationId lottiePlayer?.activeAnimationId !== animationId
) { ) {
lottiePlayer.loadAnimation(animationId); lottiePlayer?.loadAnimation(animationId);
} }
}); });
// Handles themeId change // Handles themeId change
$effect(() => { $effect(() => {
if (lottieState.isLoaded && lottiePlayer.activeThemeId !== themeId) { if (lottieState.isLoaded && lottiePlayer?.activeThemeId !== themeId) {
lottiePlayer.setTheme(themeId); lottiePlayer?.setTheme(themeId);
} }
}); });
// Handles themeData change // Handles themeData change
$effect(() => { $effect(() => {
if (lottieState.isLoaded && lottiePlayer.isLoaded) { if (lottieState.isLoaded && lottiePlayer?.isLoaded) {
lottiePlayer.setThemeData(themeData); lottiePlayer?.setThemeData(themeData);
} }
}); });
</script> </script>
@ -495,27 +544,4 @@
display: block; display: block;
} }
} }
.debug-info {
position: absolute;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.6);
z-index: 3;
margin: 0;
min-width: 25vmin;
.title {
width: 100%;
padding: 4px 0 0 8px;
margin: 0;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
p {
color: white;
margin: 0;
padding: 4px 8px 8px 8px;
}
}
</style> </style>

View file

@ -6,7 +6,7 @@
// Types // Types
import type { Component, Snippet } from 'svelte'; import type { Component, Snippet } from 'svelte';
import type { LottieState } from './lottieState.svelte'; import type { LottieState } from './ts/lottieState.svelte';
import type { import type {
ContainerWidth, ContainerWidth,
ScrollerLottieForegroundPosition, ScrollerLottieForegroundPosition,

View file

@ -1,4 +1,16 @@
import type { Layout } from '@lottiefiles/dotlottie-web';
export interface LottieState { export interface LottieState {
[key: string]:
| number
| boolean
| string
| null
| Array<string>
| Array<number>
| [number, number]
| Layout
| undefined;
progress: number; progress: number;
currentFrame: number; currentFrame: number;
totalFrames: number; totalFrames: number;
@ -14,9 +26,9 @@ export interface LottieState {
isFrozen: boolean; isFrozen: boolean;
segment: null | [number, number]; segment: null | [number, number];
autoplay: boolean; autoplay: boolean;
layout: null | string; layout: null | Layout;
allMarkers: Array<string>; allMarkers: Array<string>;
marker: null | string; marker: undefined | string;
allThemes: Array<string>; allThemes: Array<string>;
activeThemeId: null | string; activeThemeId: null | string;
} }
@ -40,7 +52,7 @@ export function createLottieState(): LottieState {
autoplay: false, autoplay: false,
layout: null, layout: null,
allMarkers: [], allMarkers: [],
marker: null, marker: undefined,
allThemes: [], allThemes: [],
activeThemeId: null, activeThemeId: null,
}); });

View file

@ -34,37 +34,6 @@
<style lang="scss"> <style lang="scss">
@use '../../../scss/mixins' as mixins; @use '../../../scss/mixins' as mixins;
#progress-bar {
background-color: rgba(0, 0, 0, 0.8);
position: absolute;
z-index: 2;
right: 0;
padding: 1rem;
progress {
height: 6px;
background-color: #ff000044; /* Background color of the entire bar */
}
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 { .step-foreground-container {
height: 100lvh; height: 100lvh;
width: 50%; width: 50%;