186 lines
4.5 KiB
Svelte
186 lines
4.5 KiB
Svelte
<script lang="ts">
|
||
import ScrollerBase from '../ScrollerBase/ScrollerBase.svelte';
|
||
import Squash from './Squash.svelte';
|
||
import GraphicBlock from '../GraphicBlock/GraphicBlock.svelte';
|
||
import Block from '../Block/Block.svelte';
|
||
|
||
import type {
|
||
ContainerWidth,
|
||
ForegroundPosition,
|
||
} from '../@types/global';
|
||
|
||
type Graphic = {
|
||
src: string;
|
||
srcset: string;
|
||
alt: string;
|
||
title?: string;
|
||
description?: string;
|
||
notes?: string;
|
||
width?: ContainerWidth;
|
||
};
|
||
|
||
interface Props {
|
||
graphics: Graphic[];
|
||
/** Duration of the squash animation in milliseconds. */
|
||
animationDuration?: number;
|
||
/** When to swap the image during the animation (0–1). E.g. 0.8 = swap at 80% through. */
|
||
animationPeak?: number;
|
||
/** Width of the background */
|
||
backgroundWidth?: ContainerWidth;
|
||
/** Position of the foreground */
|
||
foregroundPosition?: ForegroundPosition;
|
||
/** Threshold prop passed to svelte-scroller */
|
||
threshold?: number;
|
||
/** Top prop passed to svelte-scroller */
|
||
top?: number;
|
||
/** Bottom prop passed to svelte-scroller */
|
||
bottom?: number;
|
||
/** Parallax prop passed to svelte-scroller */
|
||
parallax?: boolean;
|
||
/** ID of the scroller container */
|
||
id?: string;
|
||
/** Set a class to target with SCSS */
|
||
class?: string;
|
||
/** The currently active section */
|
||
index?: number;
|
||
/** How far the section has scrolled past the threshold */
|
||
offset?: number;
|
||
/** How far the foreground has travelled */
|
||
progress?: number;
|
||
}
|
||
|
||
let {
|
||
graphics,
|
||
animationDuration = 350,
|
||
animationPeak = 0.8,
|
||
backgroundWidth = 'fluid',
|
||
foregroundPosition = 'middle',
|
||
threshold = 0.5,
|
||
top = 0,
|
||
bottom = 1,
|
||
parallax = false,
|
||
id = '',
|
||
class: cls = '',
|
||
index = $bindable(0),
|
||
offset = $bindable(0),
|
||
progress = $bindable(0),
|
||
}: Props = $props();
|
||
</script>
|
||
|
||
<Block width="fluid" class="scroller-container {cls}" {id}>
|
||
<ScrollerBase
|
||
bind:index
|
||
bind:offset
|
||
bind:progress
|
||
{threshold}
|
||
{top}
|
||
{bottom}
|
||
{parallax}
|
||
query="div.step-foreground-container"
|
||
>
|
||
{#snippet backgroundSnippet()}
|
||
<div
|
||
class="background min-h-screen relative p-0 flex justify-center"
|
||
class:right={foregroundPosition === 'left opposite'}
|
||
class:left={foregroundPosition === 'right opposite'}
|
||
aria-hidden="true"
|
||
>
|
||
<div class="scroller-graphic-well w-full">
|
||
<Block
|
||
width={backgroundWidth}
|
||
class="background-container my-0 min-h-screen flex justify-center items-center relative"
|
||
>
|
||
<Squash
|
||
{graphics}
|
||
{index}
|
||
{offset}
|
||
{animationDuration}
|
||
{animationPeak}
|
||
{backgroundWidth}
|
||
/>
|
||
</Block>
|
||
</div>
|
||
</div>
|
||
{/snippet}
|
||
|
||
{#snippet foregroundSnippet()}
|
||
<div class="foreground {foregroundPosition} w-full">
|
||
{#each graphics as graphic}
|
||
<div class="step-foreground-container">
|
||
<GraphicBlock
|
||
title={graphic.title}
|
||
description={graphic.description}
|
||
notes={graphic.notes}
|
||
>
|
||
<!-- GraphicBlock requires the children snippet to be defined, but currently, our image is in the background snippet. -->
|
||
{''}
|
||
</GraphicBlock>
|
||
</div>
|
||
{/each}
|
||
</div>
|
||
{/snippet}
|
||
</ScrollerBase>
|
||
</Block>
|
||
|
||
<style lang="scss">
|
||
div.background {
|
||
&.left {
|
||
width: 50%;
|
||
float: left;
|
||
@media (max-width: 1200px) {
|
||
justify-content: center;
|
||
width: 100%;
|
||
float: initial;
|
||
}
|
||
}
|
||
&.right {
|
||
width: 50%;
|
||
float: right;
|
||
@media (max-width: 1200px) {
|
||
justify-content: center;
|
||
width: 100%;
|
||
float: initial;
|
||
}
|
||
}
|
||
|
||
div.scroller-graphic-well {
|
||
padding: 0 15px;
|
||
}
|
||
}
|
||
|
||
div.foreground {
|
||
&.right {
|
||
width: 50%;
|
||
float: right;
|
||
@media (max-width: 1200px) {
|
||
width: 100%;
|
||
float: initial;
|
||
}
|
||
}
|
||
|
||
&.left {
|
||
width: 50%;
|
||
float: left;
|
||
@media (max-width: 1200px) {
|
||
width: 100%;
|
||
float: initial;
|
||
}
|
||
}
|
||
}
|
||
|
||
.step-foreground-container {
|
||
height: 100vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
padding: 1em;
|
||
margin: 0 auto;
|
||
max-width: var(--normal-column-width, 660px);
|
||
position: relative;
|
||
pointer-events: none;
|
||
|
||
> :global(*) {
|
||
pointer-events: auto;
|
||
}
|
||
}
|
||
</style>
|