adds Illustrator screenshot for reference

This commit is contained in:
Sudev Kiyada 2026-01-09 09:44:07 +05:30
parent 1ee82f4f5a
commit b354ce267b
Failed to extract signature
7 changed files with 219 additions and 73 deletions

View file

@ -2,6 +2,8 @@ import { Meta } from '@storybook/blocks';
import * as HorizontalScrollerStories from './HorizontalScroller.stories.svelte';
import IllustratorScreenshot from './assets/illustrator.png';
<Meta of={HorizontalScrollerStories} />
# HorizontalScroller
@ -161,7 +163,10 @@ respect_height: true
allow_overflow: true
```
This can be useful to even transition tagged content inside the ai2svelte graphic as part of the horizontal scrolling experience. For example, caption boxes exported as `htext` tagged layers can be animated to fade in/out or move in/out of view based on the scroll progress. Or one could even use tagged `png` layers to create parallax effects.
<img
src={IllustratorScreenshot}
alt="Screenshot showing Illustrator document with artboard panel"
/>
[Demo](?path=/story/components-graphics-horizontalscroller--scrollable-ai-2-svelte)
@ -170,9 +175,85 @@ This can be useful to even transition tagged content inside the ai2svelte graphi
import { HorizontalScroller } from '@reuters-graphics/graphics-components';
import AiGraphic from './ai2svelte/ai-graphic.svelte';
import { sineInOut } from 'svelte/easing';
// If using with the graphics kit
import { assets } from '$app/paths';
</script>
<HorizontalScroller
width="fluid"
height="800lvh"
direction="right"
easing={sineInOut}
showDebugInfo
>
<AiGraphic assetsPath={assets} />
</HorizontalScroller>
```
## With ai2svelte components (advanced)
Binding the scrollProgress can be useful to even transition tagged content inside the ai2svelte graphic as part of the horizontal scrolling experience. For example, caption boxes exported as `htext` tagged layers can be animated to fade in/out or move in/out of view based on the scroll progress. Or one could even use tagged `png` layers to create parallax effects.
[Demo](?path=/story/components-graphics-horizontalscroller--scrollable-ai-2-svelte-advanced)
```svelte
<script lang="ts">
import { HorizontalScroller } from '@reuters-graphics/graphics-components';
import AiGraphic from './ai2svelte/ai-graphic.svelte';
import { sineInOut } from 'svelte/easing';
// If using with the graphics kit
import { assets } from '$app/paths';
// bind scrollProgress for advanced interactivity
let scrollProgress: number = $state(0);
let pngLayer: HTMLElement | null;
let captions: HTMLElement[] | null;
let threshold = 0.8;
let screenWidth: number = $state(0);
function handleScroll() {
// to create the parallax movement
if (pngLayer) {
pngLayer.style.transform = `translateX(${map(scrollProgress, 0, 1, -400, 400)}px)`;
}
// for each caption, checks if position of the caption < 80vw
// if it is, show it
// if not, hide it
if (captions?.length) {
captions.forEach((caption) => {
let captionWidth = caption.getBoundingClientRect().width;
let captionMidpoint =
caption.getBoundingClientRect().left + captionWidth / 2;
if (
captionMidpoint < screenWidth * threshold &&
caption.style.opacity !== '1'
) {
caption.style.opacity = '1';
} else if (
captionMidpoint > screenWidth * threshold &&
caption.style.opacity !== '0'
) {
caption.style.opacity = '0';
}
});
}
}
// refetch new captions and png image
// every time the artboard changes
function onArtboardChange(artboard: HTMLElement) {
pngLayer = artboard.querySelector('.g-png-layer-overlay');
captions = Array.from(artboard.querySelectorAll('.g-captions'));
if (pngLayer) {
window.removeEventListener('scroll', handleScroll);
window.addEventListener('scroll', handleScroll, {
passive: true,
});
}
}
</script>
<HorizontalScroller
@ -183,17 +264,16 @@ This can be useful to even transition tagged content inside the ai2svelte graphi
easing={sineInOut}
showDebugInfo
>
<AiGraphic />
<AiGraphic assetsPath={assets} />
</HorizontalScroller>
<style lang="scss">
#horizontal-stack {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-around;
gap: 10vw;
height: 100%;
:global(.scroller-caption) {
padding: 1rem;
margin: 0;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 8px;
filter: drop-shadow(0px 2px 16px rgba(0, 0, 0, 0.2));
}
</style>
```

View file

@ -6,6 +6,7 @@
import DemoSnippetBlock from './demo/DemoSnippet.svelte';
import CustomChildrenBlock from './demo/CustomChildrenSnippet.svelte';
import ScrollableGraphic from './demo/ScrollableGraphic.svelte';
import AdvancedScrollableGraphic from './demo/AdvancedScrollableGraphic.svelte';
import WithScrollerBaseComponent from './demo/withScrollerBase.svelte';
import Block from '../Block/Block.svelte';
@ -43,6 +44,22 @@
{/snippet}
</Story>
<Story
name="Extended demo"
args={{
children: DemoSnippet,
height: '200lvh',
clampedProgress: true,
clampStart: -1,
clampEnd: 2,
showDebugInfo: true,
}}
>
{#snippet children(args)}
<DemoComponent {...args}></DemoComponent>
{/snippet}
</Story>
<Story
name="With stops"
args={{
@ -85,6 +102,10 @@
<ScrollableGraphic />
</Story>
<Story name="Scrollable ai2svelte (advanced)">
<AdvancedScrollableGraphic />
</Story>
<Story name="With ScrollerBase">
<WithScrollerBaseComponent />
</Story>

View file

@ -99,15 +99,22 @@
let translateX: number = $derived.by(() => {
let processedProgress = progressTween.current;
let normalisedProgress = processedProgress;
if (clampedProgress) {
processedProgress = Math.min(
Math.max(progressTween.current, clampStart),
clampEnd
);
}
const normalisedProgress =
direction === 'right' ? processedProgress : 1 - processedProgress;
processedProgress = map(processedProgress, 0, 1, clampStart, clampEnd);
normalisedProgress =
direction === 'right' ? processedProgress : (
clampEnd - processedProgress
);
} else {
normalisedProgress =
direction === 'right' ? processedProgress : 1 - processedProgress;
}
const translate = -(contentWidth - containerWidth) * normalisedProgress;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -0,0 +1,97 @@
<script lang="ts">
import Demo from './graphic/ai2svelte/demo.svelte';
import BodyText from '../../BodyText/BodyText.svelte';
import HorizontalScroller from '../HorizontalScroller.svelte';
import { map } from '../utils/index';
import { sineInOut } from 'svelte/easing';
const foobarText: string =
'In the mystical land of Foobaristan, the legendary hero Foo set out on an epic quest to find his missing semicolon, only to discover that Bar had accidentally used it as a bookmark inside a JSON file. Naturally, the entire kingdom crashed immediately. As the villagers panicked, Foo and Bar tried to fix the situation by turning everything off and on again, but all that did was anger the ancient deity known as “The Build System,” which now demanded three sacrifices: a clean cache, a fresh node_modules folder, and someones weekend. And thus began the saga nobody asked for, yet every developer somehow relates to.';
let scrollProgress: number = $state(0);
let pngLayer: HTMLElement | null;
let captions: HTMLElement[] | null;
let threshold = 0.8;
let screenWidth: number = $state(0);
function handleScroll() {
if (pngLayer) {
pngLayer.style.transform = `translateX(${map(scrollProgress, 0, 1, -400, 400)}px)`;
}
if (captions?.length) {
captions.forEach((caption) => {
let captionWidth = caption.getBoundingClientRect().width;
let captionMidpoint =
caption.getBoundingClientRect().left + captionWidth / 2;
if (
captionMidpoint < screenWidth * threshold &&
caption.style.opacity !== '1'
) {
caption.style.opacity = '1';
} else if (
captionMidpoint > screenWidth * threshold &&
caption.style.opacity !== '0'
) {
caption.style.opacity = '0';
}
});
}
}
function onArtboardChange(artboard: HTMLElement) {
pngLayer = artboard.querySelector('.g-png-layer-overlay');
captions = Array.from(artboard.querySelectorAll('.g-captions'));
if (pngLayer) {
window.removeEventListener('scroll', handleScroll);
window.addEventListener('scroll', handleScroll, {
passive: true,
});
}
}
</script>
<svelte:window bind:innerWidth={screenWidth} />
<BodyText text={foobarText} />
<HorizontalScroller
height="800lvh"
direction="right"
bind:scrollProgress
easing={sineInOut}
showDebugInfo
>
<Demo
{onArtboardChange}
debugTaggedText
taggedText={{
htext: {
captions: {
caption1:
'<div class="scroller-caption"><strong>Destruction!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
caption2:
'<div class="scroller-caption"><strong>Destruction!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
caption3:
'<div class="scroller-caption"><strong>Destruction!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
caption4:
'<div class="scroller-caption"><strong>Destruction!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
},
},
}}
/>
</HorizontalScroller>
<BodyText text={foobarText} />
<style lang="scss">
:global(.scroller-caption) {
padding: 1rem;
margin: 0;
background-color: rgba(255, 255, 255, 0.8);
border-radius: 8px;
filter: drop-shadow(0px 2px 16px rgba(0, 0, 0, 0.2));
}
</style>

View file

@ -1,4 +1,4 @@
<div style="width: 400vw; height: 100lvh;">
<div style="width: 400vw; height: 100lvh; border: 2px solid red;">
<img
src="https://picsum.photos/1200/640?t=1"
alt="Sample"

View file

@ -2,80 +2,21 @@
import Demo from './graphic/ai2svelte/demo.svelte';
import BodyText from '../../BodyText/BodyText.svelte';
import HorizontalScroller from '../HorizontalScroller.svelte';
import { map } from '../utils/index';
import { sineInOut } from 'svelte/easing';
const foobarText: string =
'In the mystical land of Foobaristan, the legendary hero Foo set out on an epic quest to find his missing semicolon, only to discover that Bar had accidentally used it as a bookmark inside a JSON file. Naturally, the entire kingdom crashed immediately. As the villagers panicked, Foo and Bar tried to fix the situation by turning everything off and on again, but all that did was anger the ancient deity known as “The Build System,” which now demanded three sacrifices: a clean cache, a fresh node_modules folder, and someones weekend. And thus began the saga nobody asked for, yet every developer somehow relates to.';
let scrollProgress: number = $state(0);
let pngLayer: HTMLElement | null;
let captions: HTMLElement[] | null;
let threshold = 0.8;
let screenWidth: number = $state(0);
function handleScroll() {
if (pngLayer) {
pngLayer.style.transform = `translateX(${map(scrollProgress, 0, 1, -400, 400)}px)`;
}
if (captions?.length) {
captions.forEach((caption) => {
let captionWidth = caption.getBoundingClientRect().width;
let captionMidpoint =
caption.getBoundingClientRect().left + captionWidth / 2;
if (captionMidpoint < screenWidth * threshold) {
caption.style.opacity = '1';
} else {
caption.style.opacity = '0';
}
});
}
}
function onArtboardChange(artboard: HTMLElement) {
pngLayer = artboard.querySelector('.g-png-layer-overlay');
captions = Array.from(artboard.querySelectorAll('.g-captions'));
if (pngLayer) {
window.removeEventListener('scroll', handleScroll);
window.addEventListener('scroll', handleScroll, {
passive: true,
});
}
}
</script>
<svelte:window bind:innerWidth={screenWidth} />
<BodyText text={foobarText} />
<HorizontalScroller
height="800lvh"
direction="right"
bind:scrollProgress
easing={sineInOut}
showDebugInfo
>
<Demo
{onArtboardChange}
debugTaggedText
taggedText={{
htext: {
captions: {
caption1:
'<div class="scroller-caption"><strong>Destruction!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
caption2:
'<div class="scroller-caption"><strong>Destruction!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
caption3:
'<div class="scroller-caption"><strong>Destruction!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
caption4:
'<div class="scroller-caption"><strong>Destruction!</strong><br/>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</div>',
},
},
}}
/>
<Demo />
</HorizontalScroller>
<BodyText text={foobarText} />