updates scrollable graphic example

This commit is contained in:
Sudev Kiyada 2026-01-13 12:12:09 +05:30
parent 74d6de2ff7
commit 0674ec24e6
Failed to extract signature
9 changed files with 158 additions and 91 deletions

View file

@ -2,7 +2,7 @@ import { Meta } from '@storybook/blocks';
import * as HorizontalScrollerStories from './HorizontalScroller.stories.svelte';
import IllustratorScreenshot from './assets/illustrator.jpg';
import IllustratorScreenshot from './assets/illustrator.png';
<Meta of={HorizontalScrollerStories} />
@ -22,7 +22,10 @@ See the full list of available props under the `Controls` tab in the [demo](?pat
```svelte
<script lang="ts">
import { HorizontalScroller, Block } from '@reuters-graphics/graphics-components';
import {
HorizontalScroller,
Block,
} from '@reuters-graphics/graphics-components';
</script>
<!-- Wrap `HorizontalScroller` in a fluid container for a full bleed experience -->
@ -69,7 +72,10 @@ By default, `duration` is set to `400` milliseconds.
```svelte
<script lang="ts">
import { HorizontalScroller, Block } from '@reuters-graphics/graphics-components';
import {
HorizontalScroller,
Block,
} from '@reuters-graphics/graphics-components';
import { quartInOut } from 'svelte/easing';
</script>
@ -106,9 +112,13 @@ If using custom `mappedStart` and `mappedEnd` values, you must also set `stops`
```svelte
<script lang="ts">
import { HorizontalScroller, Block } from '@reuters-graphics/graphics-components';
import {
HorizontalScroller,
Block,
} from '@reuters-graphics/graphics-components';
import { quartInOut } from 'svelte/easing';
</script>
<!-- Wrap `HorizontalScroller` in a fluid container for a full bleed experience -->
<Block width="fluid">
<HorizontalScroller
@ -154,7 +164,10 @@ allow_overflow: true
```svelte
<script lang="ts">
import { HorizontalScroller, Block } from '@reuters-graphics/graphics-components';
import {
HorizontalScroller,
Block,
} from '@reuters-graphics/graphics-components';
import AiGraphic from './ai2svelte/ai-graphic.svelte';
// If using with the graphics kit
@ -166,11 +179,7 @@ allow_overflow: true
<!-- Wrap `HorizontalScroller` in a fluid container for a full bleed experience -->
<Block width="fluid">
<HorizontalScroller
height="800lvh"
easing={sineInOut}
showDebugInfo
>
<HorizontalScroller height="800lvh" easing={sineInOut} showDebugInfo>
<AiGraphic assetsPath={assets} />
</HorizontalScroller>
</Block>
@ -184,7 +193,7 @@ The demo below has 2 advanced interactions: fade in/out of caption boxes based o
### Captions fading in/out
Caption boxes are exported as `htext` [tagged layers](https://reuters-graphics.github.io/ai2svelte/users/tagged-layers/) in ai2svelte. In this example, we use the `handleScroll()` function to check the position of each caption box relative to the viewport width and set its opacity to `1` (visible) or `0` (hidden) based on whether the caption box is within the `threshold` of the viewport.
Caption boxes are exported as `htext` [tagged layers](https://reuters-graphics.github.io/ai2svelte/users/tagged-layers/) in ai2svelte. In this example, we use the `handleScroll()` function to check the position of each caption box relative to the viewport width and set its opacity to `1` (visible) or `0` (hidden) based on whether the caption box is within the `threshold` of the viewport. Set `override_text: true` in the ai2svelte export settings to allow custom HTML content in tagged text layers.
### Parallax effect with png layer
@ -194,7 +203,10 @@ This demo has a tagged `png` [layer](https://reuters-graphics.github.io/ai2svelt
```svelte
<script lang="ts">
import { HorizontalScroller, Block } from '@reuters-graphics/graphics-components';
import {
HorizontalScroller,
Block,
} from '@reuters-graphics/graphics-components';
import AiGraphic from './ai2svelte/ai-graphic.svelte';
import { sineInOut } from 'svelte/easing';
@ -211,7 +223,7 @@ This demo has a tagged `png` [layer](https://reuters-graphics.github.io/ai2svelt
function handleScroll() {
// Create a parallax movement for the foreground png layer
if (pngLayer) {
pngLayer.style.transform = `translateX(${map(progress, 0, 1, -400, 400)}px)`;
pngLayer.style.transform = `scale(1.5) translateX(${map(progress, 0, 1, -15, 85)}%)`;
}
// For each caption, checks if position of the caption is below the threshold.
@ -247,6 +259,9 @@ This demo has a tagged `png` [layer](https://reuters-graphics.github.io/ai2svelt
window.addEventListener('scroll', handleScroll, {
passive: true,
});
// to translate overlay layer on initial load
handleScroll();
}
}
</script>
@ -259,7 +274,9 @@ This demo has a tagged `png` [layer](https://reuters-graphics.github.io/ai2svelt
easing={sineInOut}
showDebugInfo={true}
>
<AiGraphic assetsPath={assets} {onArtboardChange}
<AiGraphic
assetsPath={assets}
{onArtboardChange}
taggedText={{
htext: {
captions: {
@ -273,7 +290,8 @@ This demo has a tagged `png` [layer](https://reuters-graphics.github.io/ai2svelt
'<div class="scroller-caption"><strong>Caption 4!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
},
},
}} />
}}
/>
</HorizontalScroller>
</Block>
@ -281,14 +299,14 @@ This demo has a tagged `png` [layer](https://reuters-graphics.github.io/ai2svelt
:global(.scroller-caption) {
padding: 1rem;
margin: 0;
background-color: rgba(255, 255, 255, 0.8);
background-color: rgba(0, 0, 0, 0.8);
border-radius: 8px;
color: white;
filter: drop-shadow(0px 2px 16px rgba(0, 0, 0, 0.2));
}
</style>
```
## With custom child components
You can create a custom horizontal layout with any component and pass it as a child to the `HorizontalScroller`. Here's an example with `DatawrapperChart`, `Headline` and ai2svelte components laid out in a horizontal scroll.
@ -355,6 +373,7 @@ You can create a custom horizontal layout with any component and pass it as a ch
You can also integrate HorizontalScroller with `ScrollerBase` for a horizontal scroll with vertical captions.
When using `HorizontalScroller` with `ScrollerBase` or other scrollers, you must:
- Create a `progress` state variable and bind it to both `ScrollerBase` and `HorizontalScroller`
- Set `HorizontalScroller`'s `height` to `100lvh`
- Set `handleScroll` to `false`
@ -366,7 +385,7 @@ When using `HorizontalScroller` with `ScrollerBase` or other scrollers, you must
import {
HorizontalScroller,
ScrollerBase,
Block
Block,
} from '@reuters-graphics/graphics-components';
import AiGraphic from './ai2svelte/ai-graphic.svelte';
import { circInOut } from 'svelte/easing';

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

View file

@ -18,7 +18,7 @@
function handleScroll() {
if (pngLayer) {
pngLayer.style.transform = `translateX(${map(progress, 0, 1, -400, 400)}px)`;
pngLayer.style.transform = `scale(1.5) translateX(${map(progress, 0, 1, -15, 85)}%)`;
}
if (captions?.length) {
@ -51,6 +51,9 @@
window.addEventListener('scroll', handleScroll, {
passive: true,
});
// to translate overlay layer on initial load
handleScroll();
}
}
</script>
@ -68,7 +71,6 @@
>
<Demo
{onArtboardChange}
debugTaggedText
taggedText={{
htext: {
captions: {
@ -93,8 +95,9 @@
:global(.scroller-caption) {
padding: 1rem;
margin: 0;
background-color: rgba(255, 255, 255, 0.8);
background-color: rgba(0, 0, 0, 0.8);
border-radius: 8px;
color: white;
filter: drop-shadow(0px 2px 16px rgba(0, 0, 0, 0.2));
}
</style>

View file

@ -1,4 +1,4 @@
<script lang="ts">
<script>
// For demo purposes only, hard-wiring img paths from Vite
// @ts-ignore img
import imageXl from '../imgs/demo-xl.jpg';
@ -6,8 +6,11 @@
import imageLg from '../imgs/demo-lg.jpg';
// @ts-ignore img
import imagePngOverlayXl from '../imgs/layer-overlay-xl.png';
// @ts-ignore img
import imagePngOverlayLg from '../imgs/layer-overlay-lg.png';
let {
assetsPath = '/',
onAiMounted = () => {},
onArtboardChange = () => {},
taggedText = { text: {}, htext: {} },
@ -15,15 +18,15 @@
artboardWidth = $bindable(undefined),
} = $props();
import { onMount, untrack } from 'svelte';
let aiBox: HTMLDivElement | undefined = $state(undefined);
let aiBox;
let screenWidth = $state(0);
let aiBoxWidth = $derived(artboardWidth ?? screenWidth);
let activeArtboard: Element | undefined = $state(undefined);
let activeArtboard = $state(undefined);
onMount(() => {
onAiMounted();
});
$effect(() => {
if (aiBoxWidth && aiBox) {
if (aiBoxWidth) {
const currentArtboard = aiBox.querySelectorAll('.g-artboard')[0];
if (currentArtboard?.id !== activeArtboard?.id) {
activeArtboard = untrack(() => currentArtboard);
@ -40,47 +43,61 @@
class="ai2svelte"
bind:this={aiBox}
style:--debug-tagged-text={debugTaggedText ? 'visible' : 'hidden'}
style:--debug-stroke={debugTaggedText ? '2px' : '0px'}
>
<!-- Artboard: lg -->
{#if aiBoxWidth && aiBoxWidth >= 0 && aiBoxWidth < 800}
{#if aiBoxWidth && aiBoxWidth >= 0 && aiBoxWidth < 1200}
<div
id="g-demo-lg"
class="g-artboard"
style="max-width: 799px;aspect-ratio: 2.75483870967742;"
style="max-width: 1199px;aspect-ratio: 2.75483870967742;"
data-aspect-ratio="2.755"
data-min-width="0"
data-max-width="799"
data-max-width="1199"
>
<div
id="g-demo-lg-img"
class="g-demo-lg-img g-aiImg"
alt=""
style="background-image: url({imageLg});"
loading="lazy"
></div>
<div
id="g-png-layer-overlay-lg"
class="g-png-layer-overlay g-aiImg"
alt=""
style="opacity:1;;background-image: url({imagePngOverlayLg});"
loading="lazy"
></div>
</div>
{/if}
<!-- Artboard: xl -->
{#if aiBoxWidth && aiBoxWidth >= 800}
{#if aiBoxWidth && aiBoxWidth >= 1200}
<div
id="g-demo-xl"
class="g-artboard"
style="min-width: 800px;aspect-ratio: 6.6758064516129;"
data-aspect-ratio="6.676"
data-min-width="800"
style="min-width: 1200px;aspect-ratio: 4.80806451612903;"
data-aspect-ratio="4.808"
data-min-width="1200"
>
<div
id="g-demo-xl-img"
class="g-demo-xl-img g-aiImg"
alt=""
style="background-image: url({imageXl});"
loading="lazy"
></div>
<div
id="g-png-layer-overlay-xl"
class="g-png-layer-overlay g-aiImg"
alt=""
style="opacity:1;;background-image: url({imagePngOverlayXl});"
loading="lazy"
></div>
<div
id="g-caption2"
class="g-captions g-aiAbs"
style="top:38.5484%;left:23.6715%;width:5.2428%;"
style="top:14.3548%;left:34.8126%;width:7.2794%;"
>
<p
class="g-pstyle0 g-taggedText g-htext"
@ -93,7 +110,7 @@
<div
id="g-caption3"
class="g-captions g-aiAbs"
style="top:38.5484%;left:49.4749%;width:5.2428%;"
style="top:14.3548%;left:60.6764%;width:7.2794%;"
>
<p
class="g-pstyle0 g-taggedText g-htext"
@ -106,7 +123,7 @@
<div
id="g-caption4"
class="g-captions g-aiAbs"
style="top:38.5484%;left:83.976%;width:5.2428%;"
style="top:14.3548%;left:84.0914%;width:7.2794%;"
>
<p
class="g-pstyle0 g-taggedText g-htext"
@ -119,7 +136,7 @@
<div
id="g-caption1"
class="g-captions g-aiAbs"
style="top:38.5484%;left:2.966%;width:3.4791%;"
style="top:14.3548%;left:4.1182%;width:4.8306%;"
>
<p
class="g-pstyle0 g-taggedText g-htext"
@ -137,7 +154,7 @@
TAGGED TEXT PROPS
taggedText={{
taggedText={
{
"text": {
@ -151,12 +168,12 @@ taggedText={{
}
}
}
}}
}
-->
<!-- End ai2svelte - 2026-01-07 11:23 -->
<!-- End ai2svelte - 2026-01-13 11:12 -->
<!-- Generated by ai2svelte v1.0.3 - 2026-01-07 11:23 -->
<!-- Generated by ai2svelte v1.0.3 - 2026-01-13 11:12 -->
<!-- ai file: demo.ai -->
<style lang="scss">
#g-demo-box,
@ -187,6 +204,13 @@ taggedText={{
background-size: contain;
background-repeat: no-repeat;
}
#g-demo-box .g-aiSymbol {
position: absolute;
box-sizing: border-box;
}
#g-demo-box .g-aiPointText p {
white-space: nowrap;
}
#g-demo-box {
height: 100%;
}
@ -225,8 +249,9 @@ taggedText={{
content: attr(data-tagged-type);
padding: 0px 4px;
font-size: 0.6rem;
font-style: normal;
color: #fff;
background-color: black;
background-color: #ee0000;
display: block;
font-weight: 800;
visibility: var(--debug-tagged-text, hidden);
@ -236,14 +261,34 @@ taggedText={{
content: attr(data-tagged-prop);
padding: 0px 4px;
font-size: 0.8rem;
font-style: normal;
color: #fff;
font-weight: 500;
visibility: var(--debug-tagged-text, hidden);
}
.g-taggedText:empty {
background-color: #00000088;
outline: 2px solid black;
background-color: #440000;
outline: 2px solid #ee0000;
visibility: var(--debug-tagged-text, hidden);
}
.g-taggedText:not(:empty)::before {
content: attr(data-tagged-type);
position: absolute;
width: calc(100% + 4px);
transform: translateY(-100%) translateX(-2px);
padding: 0px 4px;
font-size: 0.6rem;
font-style: normal;
color: #fff;
background-color: black;
display: block;
font-weight: 800;
visibility: var(--debug-tagged-text, hidden);
}
.g-taggedText {
outline: var(--debug-stroke, 0px) solid black;
}
</style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB