A scrollyteller component for Svelte. Svelte port of the ABC News React Scrollyteller.
npm install @abcnews/svelte-scrollyteller
Add aunty config to package.json:
"aunty": {
"type": "svelte",
"build": {
"entry": [
"index"
],
"includedDependencies": [
"@abcnews/svelte-scrollyteller"
]
}
}
The scrollyteller takes an array of panels of content nodes and turns them into a series of elements which scroll over the <Scrollyteller> component's children.
The panels prop is in the format of:
[
{
data: {
info: 'Any arbitrary data, returned to you when this marker is active'
},
align: 'left', // optional: align the panels to the left or right
panelClass: 'my-custom-target', // optional: in case you want to style it manually
nodes: [<DOM elements for this panel>]
},
{
data: {
thing: 'Config when this second marker is hit'
},
nodes: [<DOM elements for this panel>]
}
]
When a new box comes into view the onMarker callback will fire with the data of the incoming panel.
<script lang="ts">
import Scrollyteller from '@abcnews/svelte-scrollyteller';
import MyGraphic from 'MyGraphic.svelte';
let {panels} = $props();
let marker = $state(0);
let progress = $state();
</script>
<Scrollyteller
{panels}
onMarker={({ detail }) => {
marker = detail;
}}
onProgress={({ detail }) => {
progress = detail;
}}
layout={{
align: 'left',
// resizeInteractive: true
// transparentFloat: true
}}
>
<MyGraphic marker="{marker}" />
</Scrollyteller>
<style lang="scss">
// Optionally create a ratio box for your graphic. It will self-centre itself
// into the appropriate space when resizeInteractive=true
.myGraphic{
aspect-ratio: 16/9;
height: 100%;
width: unset;
@container (max-aspect-ratio:16/9) {
width: 100%;
height: auto;
}
}
</style>
For a more complete example using Typescript see the examples.
| Property | Type | Description | Default |
|---|---|---|---|
| panels | Refer to Usage | required Array of nodes and data which dictate the markers | |
| onMarker | callback function | required Called when a marker intersects and returns that markers data |
|
| onProgress | callback function | Fires on scroll and returns the scrollyteller progress | |
| customPanel | Svelte Component | Component to replace the default panel component | Panel.svelte |
| observerOptions | IntersectionObserverInit | Options for the intersection observer. Refer to the docs | {threshold: 0.5} |
The layout={} prop controls how the scrollyteller is laid out, and has the following options:
| Property | Type | Description |
|---|---|---|
| align | string | Alignment for blocks. One of left/right/centre/none. "none" applies no breakpoint styling so you can do your own custom styles. |
| resizeInteractive | boolean | Defaults to true if not set. This handles the scrollyteller graphic position according to the current breakpoint. |
| transparentFloat | boolean | Defaults to true if align is left or right. Removes the block background for left/right aligned pieces, for a better reading experience. |
| mobileVariant | string | Toggle mobile betwen:
|
The resizeInteractive prop lets you opt into predefined graphic sizes and placements. When enabled, the graphic will appear toward the top on mobile, and in the centre when left/right aligned. On mobile this allows the most space for blocks to scroll without hitting the graphic, and looks aesthetically pleasing on desktop/larger portrait tablets like the iPad Pro.
The graphic slot has position:relative set, uses flexbox to put its contents into the correct spot, and allows you to use container queries to size your interactive. This works best when your interactive fits itself to its container and reserves only the space it needs. See the examples above for some example code using aspect-ratio to fit an interactive to the graphic slot, although you could use any number of methods.
You can opt out of the resizeInteractive behaviour if you want to use your own styles, at which point you can take over the entire screen.
The scrollyteller inherits the light/dark colour scheme from Odyssey.
The Svelte Scrollyteller also uses the following CSS variables that you can set anywhere in the DOM above the scrollyteller:
| Attribute | Variable to use | Fallback value |
|---|---|---|
background-color |
--color-panel-background |
dark/light mode variant |
Text colour |
--color-panel-text |
dark/light mode variant |
Background opacity |
--color-panel-opacity |
1 |
Background CSS filter |
--color-panel-filter |
blur(2.5px) |
Background border |
--color-panel-border |
none |
You can also specify a panelClass class and style the panels manually (see Usage above).
When developing ABC News stories with Odyssey you can use the loadScrollyteller function to gather panels within a CoreMedia article.
See a more complete usage example with Odyssey.
CoreMedia text:
#scrollytellerVARIABLEvalue
This is the opening paragraph panel
#markVARIABLEvalue
This is a second panel
#markVARval
This is another paragraph
#endscrollyteller
JS Code:
import { loadScrollyteller } from "@abcnews/svelte-scrollyteller";
import App from "App.svelte";
/** Optionally, specify generics to type your markers */
type MyPanelData = {
electorate: string;
viz: "map" | "hex" | "chart";
};
const scrollyData = loadScrollyteller<MyPanelData>(
"", // If set to eg. "one" use #scrollytellerNAMEone in CoreMedia
"u-full", // Class to apply to mount point u-full makes it full width in Odyssey
"mark", // Name of marker in CoreMedia eg. for "point" use #point default: #mark
);
new App({
target: scrollyData.mountNode,
props: { panels: scrollyData.panels },
});
The scrollyteller is available as a Web Component for use in other frameworks. See README.WebComponent.md for more info.
The Svelte components are packaged using SvelteKit svelte-package.
The Web Component is built separately using Vite with the vite-webcomponents.config.js config.
Add #debug=true to your story to enable debug mode and clearly outline each section, block, breakpoint, and observer trigger point.
git clone [email protected]:abcnews/svelte-scrollyteller.git
cd svelte-scrollyteller
npm i
npm run dev
This will get a SvelteKit app to use for development and testing up and running.
npm run release
This will: