@reuters-graphics/graphics-components 3.0.12 → 3.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/components/@types/global.d.ts +45 -0
  2. package/dist/components/Scroller/Scroller.mdx +2 -2
  3. package/dist/components/ScrollerBase/ScrollerBase.mdx +0 -5
  4. package/dist/components/ScrollerVideo/Debug.svelte +207 -0
  5. package/dist/components/ScrollerVideo/Debug.svelte.d.ts +5 -0
  6. package/dist/components/ScrollerVideo/ScrollerVideo.mdx +462 -0
  7. package/dist/components/ScrollerVideo/ScrollerVideo.stories.svelte +190 -0
  8. package/dist/components/ScrollerVideo/ScrollerVideo.stories.svelte.d.ts +19 -0
  9. package/dist/components/ScrollerVideo/ScrollerVideo.svelte +292 -0
  10. package/dist/components/ScrollerVideo/ScrollerVideo.svelte.d.ts +58 -0
  11. package/dist/components/ScrollerVideo/ScrollerVideoForeground.svelte +164 -0
  12. package/dist/components/ScrollerVideo/ScrollerVideoForeground.svelte.d.ts +17 -0
  13. package/dist/components/ScrollerVideo/demo/AdvancedUsecases.svelte +114 -0
  14. package/dist/components/ScrollerVideo/demo/AdvancedUsecases.svelte.d.ts +3 -0
  15. package/dist/components/ScrollerVideo/demo/Embedded.svelte +94 -0
  16. package/dist/components/ScrollerVideo/demo/Embedded.svelte.d.ts +3 -0
  17. package/dist/components/ScrollerVideo/demo/WithAi2svelteForegrounds.svelte +117 -0
  18. package/dist/components/ScrollerVideo/demo/WithAi2svelteForegrounds.svelte.d.ts +3 -0
  19. package/dist/components/ScrollerVideo/demo/WithScrollerBase.svelte +80 -0
  20. package/dist/components/ScrollerVideo/demo/WithScrollerBase.svelte.d.ts +3 -0
  21. package/dist/components/ScrollerVideo/demo/WithTextForegrounds.svelte +72 -0
  22. package/dist/components/ScrollerVideo/demo/WithTextForegrounds.svelte.d.ts +18 -0
  23. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/ai-chart.svelte +631 -0
  24. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/ai-chart.svelte.d.ts +3 -0
  25. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation1.svelte +428 -0
  26. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation1.svelte.d.ts +26 -0
  27. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation2.svelte +402 -0
  28. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation2.svelte.d.ts +26 -0
  29. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation3.svelte +398 -0
  30. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation3.svelte.d.ts +26 -0
  31. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation4.svelte +360 -0
  32. package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation4.svelte.d.ts +26 -0
  33. package/dist/components/ScrollerVideo/demo/graphic/imgs/ai-chart-md.png +0 -0
  34. package/dist/components/ScrollerVideo/demo/graphic/imgs/ai-chart-sm.png +0 -0
  35. package/dist/components/ScrollerVideo/demo/graphic/imgs/ai-chart-xs.png +0 -0
  36. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-lg.png +0 -0
  37. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-md.png +0 -0
  38. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-sm.png +0 -0
  39. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-xl.png +0 -0
  40. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-xs.png +0 -0
  41. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-lg.png +0 -0
  42. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-md.png +0 -0
  43. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-sm.png +0 -0
  44. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-xl.png +0 -0
  45. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-xs.png +0 -0
  46. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-lg.png +0 -0
  47. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-md.png +0 -0
  48. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-sm.png +0 -0
  49. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-xl.png +0 -0
  50. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-xs.png +0 -0
  51. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-lg.png +0 -0
  52. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-md.png +0 -0
  53. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-sm.png +0 -0
  54. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-xl.png +0 -0
  55. package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-xs.png +0 -0
  56. package/dist/components/ScrollerVideo/ts/ScrollerVideo.d.ts +248 -0
  57. package/dist/components/ScrollerVideo/ts/ScrollerVideo.js +762 -0
  58. package/dist/components/ScrollerVideo/ts/mp4box.d.ts +137 -0
  59. package/dist/components/ScrollerVideo/ts/state.svelte.d.ts +51 -0
  60. package/dist/components/ScrollerVideo/ts/state.svelte.js +25 -0
  61. package/dist/components/ScrollerVideo/ts/utils.d.ts +70 -0
  62. package/dist/components/ScrollerVideo/ts/utils.js +92 -0
  63. package/dist/components/ScrollerVideo/ts/videoDecoder.d.ts +11 -0
  64. package/dist/components/ScrollerVideo/ts/videoDecoder.js +193 -0
  65. package/dist/components/ScrollerVideo/videos/HPO.mp4 +0 -0
  66. package/dist/components/ScrollerVideo/videos/drone.mp4 +0 -0
  67. package/dist/components/ScrollerVideo/videos/goldengate.mp4 +0 -0
  68. package/dist/components/ScrollerVideo/videos/tennis.mp4 +0 -0
  69. package/dist/components/ScrollerVideo/videos/waves_lg.mp4 +0 -0
  70. package/dist/components/ScrollerVideo/videos/waves_md.mp4 +0 -0
  71. package/dist/components/ScrollerVideo/videos/waves_sm.mp4 +0 -0
  72. package/dist/components/SiteHeadline/SiteHeadline.mdx +4 -1
  73. package/dist/index.d.ts +3 -1
  74. package/dist/index.js +2 -0
  75. package/package.json +3 -1
@@ -0,0 +1,292 @@
1
+ <script lang="ts">
2
+ import { onDestroy } from 'svelte';
3
+ import ScrollerVideo from './ts/ScrollerVideo';
4
+ import Debug from './Debug.svelte';
5
+ import type { Snippet } from 'svelte';
6
+ import { setContext } from 'svelte';
7
+ import { dev } from '$app/environment';
8
+ import { Tween } from 'svelte/motion';
9
+
10
+ interface Props {
11
+ /** CSS class for scroller container */
12
+ class?: string;
13
+ /** ID of the scroller container */
14
+ id?: string;
15
+ /** Bindable instance of ScrollerVideo */
16
+ scrollerVideo?: ScrollerVideo;
17
+ /** Video source URL */
18
+ src: string;
19
+ /** Bindable percentage value to control video playback. **Ranges from 0 to 1** */
20
+ videoPercentage?: number;
21
+ /** Sets the maximum playbackRate for this video */
22
+ transitionSpeed?: number;
23
+ /** When to stop the video animation, in seconds */
24
+ frameThreshold?: number;
25
+ /** How the video should be resized to fit its container */
26
+ objectFit?: string;
27
+ /** Whether the video should have position: sticky */
28
+ sticky?: boolean;
29
+ /** Whether the video should take up the entire viewport */
30
+ full?: boolean;
31
+ /** Whether this object should automatically respond to scroll. Set this to **false** while manually controlling `videoPercentage` prop. */
32
+ trackScroll?: boolean;
33
+ /** Whether it ignores human scroll while it runs setVideoPercentage with enabled trackScroll */
34
+ lockScroll?: boolean;
35
+ /** Whether the library should use the webcodecs method. For more info, visit https://scrollyvideo.js.org/ */
36
+ useWebCodecs?: boolean;
37
+ /** The callback when it's ready to scroll */
38
+ onReady?: () => void;
39
+ /** The callback for video percentage change */
40
+ onChange?: () => void;
41
+ /** Whether to log debug information. Internal library logs. */
42
+ debug?: boolean;
43
+ /** Shows debug information on page */
44
+ showDebugInfo?: boolean;
45
+ /** Height of the video container. Set it to 100lvh when using inside `ScrollerBase` */
46
+ height?: string;
47
+ /** Whether the video should autoplay */
48
+ autoplay?: boolean;
49
+ /** Variable to control component rendering on embed page */
50
+ embedded?: boolean;
51
+ /** Additional properties for embedded videos */
52
+ embeddedProps?: {
53
+ /** When to start the playback in terms of the component's position */
54
+ threshold?: number;
55
+ /** Duration of ScrollerVideo experience as a video */
56
+ duration?: number;
57
+ /** Delay before the playback */
58
+ delay?: number;
59
+ };
60
+ /** Children render function */
61
+ children?: Snippet;
62
+ }
63
+
64
+ /** Default properties for embedded videos */
65
+ const defaultEmbedProps = {
66
+ threshold: 0.5,
67
+ delay: 200,
68
+ };
69
+
70
+ /**
71
+ * Main logic for ScrollerVideo Svelte component.
72
+ * Handles instantiation, prop changes, and cleanup.
73
+ */
74
+ let {
75
+ class: cls = '',
76
+ id = '',
77
+ src,
78
+ scrollerVideo = $bindable(),
79
+ videoPercentage,
80
+ onReady = $bindable(() => {}),
81
+ onChange = $bindable(() => {}),
82
+ height = '200lvh',
83
+ showDebugInfo = false,
84
+ embedded = false,
85
+ embeddedProps,
86
+ children,
87
+ ...restProps
88
+ }: Props = $props();
89
+
90
+ // variable to hold the DOM element
91
+ /**
92
+ * Reference to the scroller video container DOM element.
93
+ * @type {HTMLDivElement | undefined}
94
+ */
95
+ let scrollerVideoContainer = $state<HTMLDivElement | undefined>(undefined);
96
+
97
+ // Store the props so we know when things change
98
+ let lastPropsString = '';
99
+
100
+ // Concatenate default and passed embedded props
101
+ let allEmbedProps = {
102
+ ...defaultEmbedProps,
103
+ ...embeddedProps,
104
+ };
105
+
106
+ // Holds regular scroller video component
107
+ // and scrolls automatically for embedded version
108
+ let embeddedContainer = $state<HTMLDivElement | undefined>(undefined);
109
+ let embeddedContainerHeight = $state<number | undefined>(undefined);
110
+ let embeddedContainerScrollHeight: number = $derived.by(() => {
111
+ let scrollHeight = 1;
112
+ if (embeddedContainer && embeddedContainerHeight) {
113
+ scrollHeight = embeddedContainer.scrollHeight - embeddedContainerHeight;
114
+ }
115
+ return scrollHeight;
116
+ });
117
+
118
+ const embeddedContainerScrollY = new Tween(0, {
119
+ duration: 1000,
120
+ delay: allEmbedProps.delay,
121
+ easing: (t) => +t,
122
+ });
123
+
124
+ $effect(() => {
125
+ if (embeddedContainer) {
126
+ embeddedContainer.scrollTop = embeddedContainerScrollY.current;
127
+ }
128
+ });
129
+
130
+ $effect(() => {
131
+ if (scrollerVideoContainer) {
132
+ if (JSON.stringify(restProps) !== lastPropsString) {
133
+ // if scrollervideo already exists and any parameter is updated, destroy and recreate.
134
+ if (scrollerVideo && scrollerVideo.destroy) scrollerVideo.destroy();
135
+
136
+ scrollerVideo = new ScrollerVideo({
137
+ src,
138
+ scrollerVideoContainer,
139
+ onReady,
140
+ onChange,
141
+ ...restProps,
142
+ trackScroll: embedded ? false : restProps.trackScroll, // trackScroll disabled for embedded version
143
+ autoplay: embedded ? false : restProps.autoplay, // autoplay disabled for embedded version
144
+ });
145
+
146
+ // if embedded prop is set,
147
+ // play the video when it crosses the threshold
148
+ // and reset it to zero when it crosses the threshold in opposite direction
149
+ if (embedded) {
150
+ const updatedOnReady = () => {
151
+ // add user defined onReady
152
+ onReady();
153
+
154
+ window?.addEventListener('scroll', () => {
155
+ if (
156
+ embeddedContainer &&
157
+ embeddedContainer.getBoundingClientRect().top <
158
+ window.innerHeight * allEmbedProps.threshold
159
+ ) {
160
+ if (
161
+ embeddedContainerScrollY.current == 0 &&
162
+ embeddedContainerHeight &&
163
+ scrollerVideo?.componentState
164
+ ) {
165
+ const scrollDuration =
166
+ allEmbedProps.duration ||
167
+ scrollerVideo.componentState.generalData.totalTime * 1000;
168
+ embeddedContainerScrollY.set(embeddedContainerScrollHeight, {
169
+ duration: scrollDuration,
170
+ delay: allEmbedProps.delay,
171
+ });
172
+ }
173
+ } else if (
174
+ embeddedContainer &&
175
+ embeddedContainer.getBoundingClientRect().top >
176
+ window.innerHeight * allEmbedProps.threshold
177
+ ) {
178
+ if (embeddedContainerScrollY.current > 0) {
179
+ embeddedContainerScrollY.set(0, { duration: 0 });
180
+ }
181
+ }
182
+ });
183
+ };
184
+
185
+ scrollerVideo.onReady = updatedOnReady;
186
+ }
187
+
188
+ // pass on component state to child components
189
+ // this controls fade in and out of foregrounds
190
+ setContext('scrollerVideoState', scrollerVideo.componentState);
191
+
192
+ // Save the new props
193
+ lastPropsString = JSON.stringify(restProps);
194
+ }
195
+
196
+ // If we need to update the target time percent
197
+ if (
198
+ scrollerVideo &&
199
+ videoPercentage &&
200
+ videoPercentage >= 0 &&
201
+ videoPercentage <= 1
202
+ ) {
203
+ scrollerVideo.setVideoPercentage(videoPercentage);
204
+ }
205
+ }
206
+ });
207
+
208
+ /**
209
+ * Cleanup the component on destroy.
210
+ */
211
+ onDestroy(() => {
212
+ if (scrollerVideo && scrollerVideo.destroy) scrollerVideo.destroy();
213
+ });
214
+
215
+ /**
216
+ * heightChange drives the height of the component when autoplay is set to true.
217
+ * @type {string}
218
+ */
219
+ let heightChange = $derived.by(() => {
220
+ if (scrollerVideo) {
221
+ return `calc(${height} * ${1 - scrollerVideo?.componentState.autoplayProgress})`;
222
+ } else {
223
+ return height;
224
+ }
225
+ });
226
+ </script>
227
+
228
+ <!-- snippet to avoid redundancy between regular and embedded versions -->
229
+ <!-- renders Debug component and children foregrounds -->
230
+ {#snippet supportingElements()}
231
+ {#if scrollerVideo}
232
+ {#if showDebugInfo && dev}
233
+ <div class="debug-info">
234
+ <Debug componentState={scrollerVideo.componentState} />
235
+ </div>
236
+ {/if}
237
+
238
+ <!-- renders foregrounds -->
239
+ {#if children}
240
+ {@render children()}
241
+ {/if}
242
+ {/if}
243
+ {/snippet}
244
+
245
+ {#if embedded}
246
+ <div
247
+ class="embedded-scroller-video-container"
248
+ bind:this={embeddedContainer}
249
+ bind:clientHeight={embeddedContainerHeight}
250
+ onscroll={() => {
251
+ if (scrollerVideo && embeddedContainer) {
252
+ let scrollProgress =
253
+ embeddedContainer.scrollTop / embeddedContainerScrollHeight;
254
+ scrollerVideo.setVideoPercentage(scrollProgress, {
255
+ jump: scrollProgress == 0,
256
+ easing: (t) => t,
257
+ });
258
+ }
259
+ }}
260
+ >
261
+ <div {id} class="scroller-video-container embedded {cls}">
262
+ <div bind:this={scrollerVideoContainer} data-scroller-container>
263
+ {@render supportingElements()}
264
+ </div>
265
+ </div>
266
+ </div>
267
+ {:else}
268
+ <div
269
+ {id}
270
+ class="scroller-video-container {cls}"
271
+ style="height: {heightChange}"
272
+ >
273
+ <div bind:this={scrollerVideoContainer} data-scroller-container>
274
+ {@render supportingElements()}
275
+ </div>
276
+ </div>
277
+ {/if}
278
+
279
+ <style>.scroller-video-container {
280
+ width: 100%;
281
+ }
282
+ .scroller-video-container.embedded {
283
+ height: 200lvh;
284
+ }
285
+ .scroller-video-container:not(.embedded) {
286
+ min-height: 100lvh;
287
+ }
288
+
289
+ .embedded-scroller-video-container {
290
+ max-height: 100lvh;
291
+ overflow: hidden;
292
+ }</style>
@@ -0,0 +1,58 @@
1
+ import ScrollerVideo from './ts/ScrollerVideo';
2
+ import type { Snippet } from 'svelte';
3
+ interface Props {
4
+ /** CSS class for scroller container */
5
+ class?: string;
6
+ /** ID of the scroller container */
7
+ id?: string;
8
+ /** Bindable instance of ScrollerVideo */
9
+ scrollerVideo?: ScrollerVideo;
10
+ /** Video source URL */
11
+ src: string;
12
+ /** Bindable percentage value to control video playback. **Ranges from 0 to 1** */
13
+ videoPercentage?: number;
14
+ /** Sets the maximum playbackRate for this video */
15
+ transitionSpeed?: number;
16
+ /** When to stop the video animation, in seconds */
17
+ frameThreshold?: number;
18
+ /** How the video should be resized to fit its container */
19
+ objectFit?: string;
20
+ /** Whether the video should have position: sticky */
21
+ sticky?: boolean;
22
+ /** Whether the video should take up the entire viewport */
23
+ full?: boolean;
24
+ /** Whether this object should automatically respond to scroll. Set this to **false** while manually controlling `videoPercentage` prop. */
25
+ trackScroll?: boolean;
26
+ /** Whether it ignores human scroll while it runs setVideoPercentage with enabled trackScroll */
27
+ lockScroll?: boolean;
28
+ /** Whether the library should use the webcodecs method. For more info, visit https://scrollyvideo.js.org/ */
29
+ useWebCodecs?: boolean;
30
+ /** The callback when it's ready to scroll */
31
+ onReady?: () => void;
32
+ /** The callback for video percentage change */
33
+ onChange?: () => void;
34
+ /** Whether to log debug information. Internal library logs. */
35
+ debug?: boolean;
36
+ /** Shows debug information on page */
37
+ showDebugInfo?: boolean;
38
+ /** Height of the video container. Set it to 100lvh when using inside `ScrollerBase` */
39
+ height?: string;
40
+ /** Whether the video should autoplay */
41
+ autoplay?: boolean;
42
+ /** Variable to control component rendering on embed page */
43
+ embedded?: boolean;
44
+ /** Additional properties for embedded videos */
45
+ embeddedProps?: {
46
+ /** When to start the playback in terms of the component's position */
47
+ threshold?: number;
48
+ /** Duration of ScrollerVideo experience as a video */
49
+ duration?: number;
50
+ /** Delay before the playback */
51
+ delay?: number;
52
+ };
53
+ /** Children render function */
54
+ children?: Snippet;
55
+ }
56
+ declare const ScrollerVideo: import("svelte").Component<Props, {}, "onReady" | "onChange" | "scrollerVideo">;
57
+ type ScrollerVideo = ReturnType<typeof ScrollerVideo>;
58
+ export default ScrollerVideo;
@@ -0,0 +1,164 @@
1
+ <script lang="ts">
2
+ import Block from '../Block/Block.svelte';
3
+ import { fade } from 'svelte/transition';
4
+ import { getContext } from 'svelte';
5
+ import { Markdown } from '@reuters-graphics/svelte-markdown';
6
+
7
+ // Types
8
+ import type { Component, Snippet } from 'svelte';
9
+ import type { ScrollerVideoState } from './ts/state.svelte';
10
+ import type {
11
+ ContainerWidth,
12
+ ScrollerVideoForegroundPosition,
13
+ } from '../@types/global';
14
+
15
+ interface ForegroundProps {
16
+ id?: string;
17
+ class?: string;
18
+ startTime?: number;
19
+ endTime?: number;
20
+ children?: Snippet;
21
+ backgroundColour?: string;
22
+ width?: ContainerWidth;
23
+ position?: ScrollerVideoForegroundPosition | string;
24
+ text?: string;
25
+ Foreground?: Component;
26
+ }
27
+
28
+ let {
29
+ id = '',
30
+ class: cls = '',
31
+ startTime = 0,
32
+ endTime = 1,
33
+ children,
34
+ backgroundColour = '#000',
35
+ width = 'normal',
36
+ position = 'center center',
37
+ text,
38
+ Foreground,
39
+ }: ForegroundProps = $props();
40
+
41
+ let componentState: ScrollerVideoState = getContext('scrollerVideoState');
42
+ </script>
43
+
44
+ <Block class={`scroller-video-foreground ${cls}`} {id}>
45
+ {#if componentState.generalData.currentTime >= startTime && componentState.generalData.currentTime <= endTime}
46
+ <div
47
+ class="scroller-foreground"
48
+ in:fade={{ delay: 100, duration: 200 }}
49
+ out:fade={{ delay: 0, duration: 100 }}
50
+ >
51
+ <!-- Text blurb foreground -->
52
+ {#if text}
53
+ <Block
54
+ class="scroller-video-foreground-text {position.split(' ')[1]}"
55
+ {width}
56
+ >
57
+ <div
58
+ style="background-color: {backgroundColour};"
59
+ class="foreground-text {position.split(' ')[0]}"
60
+ >
61
+ <Markdown source={text} />
62
+ </div>
63
+ </Block>
64
+ <!-- Render children snippet -->
65
+ {:else if children}
66
+ <div class="scroller-video-foreground-item">
67
+ {@render children()}
68
+ </div>
69
+ <!-- Render Foreground component -->
70
+ {:else if Foreground}
71
+ <div class="scroller-video-foreground-item">
72
+ <Block width="fluid">
73
+ <Foreground />
74
+ </Block>
75
+ </div>
76
+ {/if}
77
+ </div>
78
+ {/if}
79
+ </Block>
80
+
81
+ <style>/* Generated from
82
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
83
+ */
84
+ /* Generated from
85
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
86
+ */
87
+ /* Scales by 1.125 */
88
+ .scroller-foreground {
89
+ width: 100%;
90
+ height: 100%;
91
+ }
92
+
93
+ :global(.scroller-video-foreground) {
94
+ position: absolute;
95
+ top: 50%;
96
+ left: 50%;
97
+ transform: translate(-50%, -50%);
98
+ width: 100%;
99
+ height: 100%;
100
+ display: flex;
101
+ flex-direction: column;
102
+ align-items: center;
103
+ justify-content: center;
104
+ z-index: 2;
105
+ }
106
+
107
+ .scroller-video-foreground-item {
108
+ width: 100%;
109
+ height: 100%;
110
+ position: absolute;
111
+ display: flex;
112
+ justify-content: center;
113
+ align-items: center;
114
+ }
115
+
116
+ :global .scroller-video-foreground-text {
117
+ position: absolute;
118
+ width: 100%;
119
+ max-width: 594px;
120
+ height: 100%;
121
+ }
122
+ @media (max-width: 1200px) {
123
+ :global .scroller-video-foreground-text {
124
+ left: 50%;
125
+ transform: translateX(-50%);
126
+ }
127
+ }
128
+ :global .scroller-video-foreground-text.left {
129
+ left: 0;
130
+ }
131
+ :global .scroller-video-foreground-text.right {
132
+ right: 0;
133
+ }
134
+ :global .scroller-video-foreground-text.center {
135
+ left: 50%;
136
+ transform: translateX(-50%);
137
+ }
138
+ :global .foreground-text {
139
+ position: absolute;
140
+ top: 50%;
141
+ left: 50%;
142
+ transform: translate(-50%, -50%);
143
+ border-radius: 0.25rem;
144
+ background-color: white;
145
+ width: 100%;
146
+ padding-block: clamp(1.69rem, 1.58rem + 0.52vw, 2rem);
147
+ padding-inline: clamp(1.13rem, 1.06rem + 0.31vw, 1.31rem);
148
+ margin: 0;
149
+ }
150
+ :global .foreground-text :global(*) {
151
+ margin: 0;
152
+ padding: 0;
153
+ }
154
+ :global .foreground-text.center {
155
+ top: 50%;
156
+ }
157
+ :global .foreground-text.top {
158
+ top: 0;
159
+ transform: translate(-50%, 50%);
160
+ }
161
+ :global .foreground-text.bottom {
162
+ top: 100%;
163
+ transform: translate(-50%, -150%);
164
+ }</style>
@@ -0,0 +1,17 @@
1
+ import type { Component, Snippet } from 'svelte';
2
+ import type { ContainerWidth, ScrollerVideoForegroundPosition } from '../@types/global';
3
+ interface ForegroundProps {
4
+ id?: string;
5
+ class?: string;
6
+ startTime?: number;
7
+ endTime?: number;
8
+ children?: Snippet;
9
+ backgroundColour?: string;
10
+ width?: ContainerWidth;
11
+ position?: ScrollerVideoForegroundPosition | string;
12
+ text?: string;
13
+ Foreground?: Component;
14
+ }
15
+ declare const ScrollerVideoForeground: Component<ForegroundProps, {}, "">;
16
+ type ScrollerVideoForeground = ReturnType<typeof ScrollerVideoForeground>;
17
+ export default ScrollerVideoForeground;
@@ -0,0 +1,114 @@
1
+ <script lang="ts">
2
+ import ScrollerVideo from '../ScrollerVideo.svelte';
3
+ import ScrollerBase from '../../ScrollerBase/ScrollerBase.svelte';
4
+ import Tennis from '../videos/tennis.mp4';
5
+ import { onDestroy } from 'svelte';
6
+
7
+ // Types
8
+ import type { ScrollerVideoInstance } from '../../@types/global.ts';
9
+
10
+ let scrollerVideo: ScrollerVideoInstance | undefined = $state(undefined);
11
+ let animationFrame = $state(0);
12
+ let index = $state(0); // index for the current step in ScrollerBase
13
+
14
+ /**
15
+ * If ScrollerBase is on index 0, jump to the start of the video.
16
+ * Otherwise, jump to 1, or 100% (the end), of the video.
17
+ */
18
+ function jumpVideo() {
19
+ if (index === 0) {
20
+ scrollerVideo?.setVideoPercentage(0, {
21
+ jump: false, // Eases the jump
22
+ });
23
+ } else {
24
+ scrollerVideo?.setVideoPercentage(1, {
25
+ jump: false,
26
+ });
27
+ }
28
+
29
+ animationFrame = requestAnimationFrame(jumpVideo);
30
+ }
31
+
32
+ // cancel requestAnimationFrame on destroy
33
+ onDestroy(() => {
34
+ cancelAnimationFrame(animationFrame);
35
+ });
36
+ </script>
37
+
38
+ <ScrollerBase bind:index query="div.step-foreground-container">
39
+ <!-- ScrollerVideo as background -->
40
+ {#snippet backgroundSnippet()}
41
+ <!-- Pass `jumpVideo` to `onReady` and set `trackScroll` to `false` -->
42
+ <ScrollerVideo
43
+ bind:scrollerVideo
44
+ src={Tennis}
45
+ height="100lvh"
46
+ trackScroll={false}
47
+ showDebugInfo
48
+ onReady={jumpVideo}
49
+ />
50
+ {/snippet}
51
+
52
+ <!-- Simple text foregrounds -->
53
+ {#snippet foregroundSnippet()}
54
+ <div class="step-foreground-container">
55
+ <h3 class="text-center">Index {index}</h3>
56
+ </div>
57
+ <div class="step-foreground-container">
58
+ <h3 class="text-center">Index {index}</h3>
59
+ </div>
60
+ {/snippet}
61
+ </ScrollerBase>
62
+
63
+ <style>/* Generated from
64
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
65
+ */
66
+ /* Generated from
67
+ https://utopia.fyi/space/calculator/?c=320,18,1.125,1280,21,1.25,7,3,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l&g=s,l,xl,12
68
+ */
69
+ /* Scales by 1.125 */
70
+ #progress-bar {
71
+ background-color: rgba(0, 0, 0, 0.8);
72
+ position: absolute;
73
+ z-index: 4;
74
+ right: 0;
75
+ padding: 1rem;
76
+ top: 0;
77
+ }
78
+ #progress-bar progress {
79
+ height: 6px;
80
+ background-color: rgba(255, 0, 0, 0.2666666667); /* Background color of the entire bar */
81
+ margin: 0;
82
+ }
83
+ #progress-bar progress::-webkit-progress-value {
84
+ background-color: white;
85
+ border-radius: 10px;
86
+ }
87
+ #progress-bar progress::-webkit-progress-bar {
88
+ background-color: #444444;
89
+ border-radius: 10px;
90
+ }
91
+ #progress-bar p {
92
+ font-family: var(--theme-font-family-sans-serif);
93
+ color: white;
94
+ font-size: var(--theme-font-size-xs);
95
+ padding: 0;
96
+ margin: 0;
97
+ }
98
+
99
+ .step-foreground-container {
100
+ height: 100lvh;
101
+ width: 50%;
102
+ padding: 1em;
103
+ margin: auto;
104
+ }
105
+ .step-foreground-container h3 {
106
+ display: flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ margin: 70% auto 0 auto;
110
+ height: 60px;
111
+ max-width: 400px;
112
+ color: white;
113
+ background: steelblue;
114
+ }</style>
@@ -0,0 +1,3 @@
1
+ declare const AdvancedUsecases: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type AdvancedUsecases = ReturnType<typeof AdvancedUsecases>;
3
+ export default AdvancedUsecases;