@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.
- package/dist/components/@types/global.d.ts +45 -0
- package/dist/components/Scroller/Scroller.mdx +2 -2
- package/dist/components/ScrollerBase/ScrollerBase.mdx +0 -5
- package/dist/components/ScrollerVideo/Debug.svelte +207 -0
- package/dist/components/ScrollerVideo/Debug.svelte.d.ts +5 -0
- package/dist/components/ScrollerVideo/ScrollerVideo.mdx +462 -0
- package/dist/components/ScrollerVideo/ScrollerVideo.stories.svelte +190 -0
- package/dist/components/ScrollerVideo/ScrollerVideo.stories.svelte.d.ts +19 -0
- package/dist/components/ScrollerVideo/ScrollerVideo.svelte +292 -0
- package/dist/components/ScrollerVideo/ScrollerVideo.svelte.d.ts +58 -0
- package/dist/components/ScrollerVideo/ScrollerVideoForeground.svelte +164 -0
- package/dist/components/ScrollerVideo/ScrollerVideoForeground.svelte.d.ts +17 -0
- package/dist/components/ScrollerVideo/demo/AdvancedUsecases.svelte +114 -0
- package/dist/components/ScrollerVideo/demo/AdvancedUsecases.svelte.d.ts +3 -0
- package/dist/components/ScrollerVideo/demo/Embedded.svelte +94 -0
- package/dist/components/ScrollerVideo/demo/Embedded.svelte.d.ts +3 -0
- package/dist/components/ScrollerVideo/demo/WithAi2svelteForegrounds.svelte +117 -0
- package/dist/components/ScrollerVideo/demo/WithAi2svelteForegrounds.svelte.d.ts +3 -0
- package/dist/components/ScrollerVideo/demo/WithScrollerBase.svelte +80 -0
- package/dist/components/ScrollerVideo/demo/WithScrollerBase.svelte.d.ts +3 -0
- package/dist/components/ScrollerVideo/demo/WithTextForegrounds.svelte +72 -0
- package/dist/components/ScrollerVideo/demo/WithTextForegrounds.svelte.d.ts +18 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/ai-chart.svelte +631 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/ai-chart.svelte.d.ts +3 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation1.svelte +428 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation1.svelte.d.ts +26 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation2.svelte +402 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation2.svelte.d.ts +26 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation3.svelte +398 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation3.svelte.d.ts +26 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation4.svelte +360 -0
- package/dist/components/ScrollerVideo/demo/graphic/ai2svelte/annotation4.svelte.d.ts +26 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/ai-chart-md.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/ai-chart-sm.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/ai-chart-xs.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-lg.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-md.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-sm.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-xl.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation1-xs.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-lg.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-md.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-sm.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-xl.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation2-xs.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-lg.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-md.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-sm.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-xl.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation3-xs.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-lg.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-md.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-sm.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-xl.png +0 -0
- package/dist/components/ScrollerVideo/demo/graphic/imgs/annotation4-xs.png +0 -0
- package/dist/components/ScrollerVideo/ts/ScrollerVideo.d.ts +248 -0
- package/dist/components/ScrollerVideo/ts/ScrollerVideo.js +762 -0
- package/dist/components/ScrollerVideo/ts/mp4box.d.ts +137 -0
- package/dist/components/ScrollerVideo/ts/state.svelte.d.ts +51 -0
- package/dist/components/ScrollerVideo/ts/state.svelte.js +25 -0
- package/dist/components/ScrollerVideo/ts/utils.d.ts +70 -0
- package/dist/components/ScrollerVideo/ts/utils.js +92 -0
- package/dist/components/ScrollerVideo/ts/videoDecoder.d.ts +11 -0
- package/dist/components/ScrollerVideo/ts/videoDecoder.js +193 -0
- package/dist/components/ScrollerVideo/videos/HPO.mp4 +0 -0
- package/dist/components/ScrollerVideo/videos/drone.mp4 +0 -0
- package/dist/components/ScrollerVideo/videos/goldengate.mp4 +0 -0
- package/dist/components/ScrollerVideo/videos/tennis.mp4 +0 -0
- package/dist/components/ScrollerVideo/videos/waves_lg.mp4 +0 -0
- package/dist/components/ScrollerVideo/videos/waves_md.mp4 +0 -0
- package/dist/components/ScrollerVideo/videos/waves_sm.mp4 +0 -0
- package/dist/components/SiteHeadline/SiteHeadline.mdx +4 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -0
- 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>
|