# Scroller

A scrollytelling component that drives background graphics from an array of steps with foreground text; built on ScrollerBase.

**Category:** Components/Graphics/Scroller

**Import:** `import { Scroller } from '@reuters-graphics/graphics-components'`

## Props

| Prop | Type | Default | Required | Description |
|------|------|---------|:--------:|-------------|
| `steps` | `ScrollerStep[]` | — | ✓ | An array of step objects that define the steps in your scroller.  Each step object in the array can have:  - `background` A background component. **REQUIRED** - `backgroundProps` Optional props for background component. - `foreground` A component or markdown-formatted string. **REQUIRED** - `foregroundProps` Optional props for foreground component. - `altText` Optional alt text for the background, read aloud after the foreground text. You can add it to each step or just to the first step to describe the entire scroller graphic. **RECOMMENDED** |
| `backgroundWidth` | `ContainerWidth` | `'fluid'` |  | Width of the background Options: `normal`, `wide`, `wider`, `widest`, `fluid` |
| `foregroundPosition` | `ForegroundPosition` | `'middle'` |  | Position of the foreground Options: `middle`, `left`, `right`, `left opposite`, `right opposite` |
| `stackBackground` | `boolean` | `true` |  | Whether previous background steps should stack below the current one.  - `true` _default_ Background graphics from previous steps will remain visible below the active one, allowing you to stack graphics with transparent backgrounds. - `false` Only the background graphic from the current step will show and backgrounds from previous steps are hidden. |
| `preload` | `number` | `1` |  | How many background steps to load before and after the currently active one, effectively lazy-loading them.  Setting to `0` disables lazy-loading and loads all backgrounds at once. |
| `embedded` | `boolean` | `false` |  | Setting to `true` will unroll the scroll experience into a flat layout |
| `embeddedLayout` | `'fb' \| 'bf'` | `'fb'` |  | Layout order when `embedded` is `true`.  - `fb` _default_ Foreground then background - `bf` Background then foreground Options: `fb`, `bf` |
| `threshold` | `number` | `0.5` |  | Threshold prop passed to [svelte-scroller](https://github.com/sveltejs/svelte-scroller#parameters) |
| `top` | `number` | `0` |  | Top prop passed to [svelte-scroller](https://github.com/sveltejs/svelte-scroller#parameters) |
| `bottom` | `number` | `1` |  | Bottom prop passed to [svelte-scroller](https://github.com/sveltejs/svelte-scroller#parameters) |
| `parallax` | `boolean` | `false` |  | Parallax prop passed to [svelte-scroller](https://github.com/sveltejs/svelte-scroller#parameters) |
| `id` | `string` | `''` |  | ID of the scroller container |
| `class` | `string` | `''` |  | Set a class to target with SCSS |
| `index` | `number` | `$bindable(0)` |  | The currently active section |
| `offset` | `number` | `$bindable(0)` |  | How far the section has scrolled past the threshold, as a value between 0 and 1 |
| `progress` | `number` | `$bindable(0)` |  | How far the foreground has travelled, where 0 is the top of the foreground crossing top, and 1 is the bottom crossing bottom |

## Examples

### Demo

```svelte
<Scroller
  steps={/* array — see Props/Types for full type */}
  foregroundPosition="middle"
  backgroundWidth="fluid"
/>
```

### ArchieML and ai2svelte

```svelte
<Scroller
  id={docBlock.id}
  foregroundPosition={docBlock.foregroundPosition}
  stackBackground={docBlock.stackBackground === 'true'}
  steps={docBlock.steps.map((step) => ({
    background: step.background,
    foreground: step.foreground,
    altText: step.altText,
  }))}
/>
```

### Custom foreground

```svelte
<Scroller steps={/* array — see Props/Types for full type */} />
```

### Custom foreground with ArchiemL

```svelte
<Scroller
  id={docBlockCustomForeground.id}
  foregroundPosition={docBlockCustomForeground.foregroundPosition}
  stackBackground={docBlockCustomForeground.stackBackground === 'true'}
  steps={docBlockCustomForeground.steps.map((step) => ({
    background: step.background,
    foreground: step.foreground,
    altText: step.altText,
  }))}
/>
```

## Documentation

# Scroller

The `Scroller` component creates a basic scrollytelling graphic with layout options.

This component is designed to handle most common layouts for scrollytelling. To make something more complex, customise [ScrollerBase](?path=/story/components-graphics-scrollerbase--docs), which is a Svelte 5 version of the [svelte-scroller](https://github.com/sveltejs/svelte-scroller).

[Demo](?path=/story/components-graphics-scroller--demo)

```svelte
<script>
  import { Scroller } from '@reuters-graphics/graphics-components';

  import MyBackground from './MyBackground.svelte'; // Your own background component

  // Array of step objects that define the steps in your scroller.
  const steps = [
    {
      background: MyBackground,
      backgroundProps: { colour: 'red' }, // Optional props for your background component
      foreground: '#### Step 1\n\nLorem ipsum red',
      altText: 'Red background',
    },
    {
      background: MyBackground,
      backgroundProps: { colour: 'blue' },
      foreground: '#### Step 2\n\nLorem ipsum blue',
      altText: 'Blue background',
    },
    {
      background: MyBackground,
      backgroundProps: { colour: 'green' },
      foreground: '#### Step 3\n\nLorem ipsum green',
      altText: 'Green background',
    },
  ];
</script>

<Scroller {steps} foregroundPosition="middle" backgroundWidth="fluid" />
```

## Using with ArchieML and ai2svelte

[Demo](?path=/story/components-graphics-scroller--archie-ml)

In your graphics kit project, import your ai2svelte graphics in `App.svelte` and add them to the `aiCharts` object:

```svelte
<!-- App.svelte -->
<script>
  import AiMap1 from './ai2svelte/my-map-1.svelte';
  import AiMap2 from './ai2svelte/my-map-2.svelte';
  import AiMap3 from './ai2svelte/my-map-3.svelte';

  import content from '$locales/en/content.json';

  // Graphics kit only
  import { asset } from '$app/paths'; // 👈 If using in the graphics kit...
  import { truthy } from '$utils/propValidators'; // 👈 If using in the graphics kit...

  const aiCharts = {
    AiMap1,
    AiMap2,
    AiMap3,
    // Other charts...
  };
</script>
```

Then add the following structure to your ArchieML doc, making sure that the names of your charts in the `aiCharts` object match the names of each step's `background` in the ArchieML doc:

```yaml
# ArchieML doc
[blocks]
type: ai-scroller
id: my-map-scroller
width: fluid
foregroundPosition: right
stackBackground: true

# Array of step objects
  [.steps]
    background: AiMap1
    foreground: #### Step 1

    Here's where something happend.
    :end
    altText: A map showing the Upper West side in New York City.

    Can add paragraphs of alt text if you want to break up sentences.
    :end

    background: AiMap2
    foreground: #### Step 2

    Something happened on some street...
    :end
    altText: The same map now highlights 98th Street.
    :end

    background: AiMap3
    foreground: #### Step 3

    ... and now there are multiple protests.
    :end
    altText: The same map now highlights three locations near 98th Street where something particulary important happened.
    :end
  []
[]
```

Then parse the relevant ArchieML block object before passing to the `Scroller` component.

```svelte
<!-- App.svelte -->
{#each content.blocks as block}
  {#if block.type === 'ai-scroller'}
    <Scroller
      id={block.id}
      backgroundWidth={block.backgroundWidth}
      foregroundPosition={block.foregroundPosition}
      stackBackground={truthy(block.stackBackground)}
      steps={block.steps.map((step) => ({
        background: aiCharts[step.background],
        backgroundProps: { assetsPath: asset('/').replace(/\/$/, '') },
        foreground: step.foreground,
        altText: step.altText,
      }))}
    />
  {/if}
{/each}
```

> **Note:** Some props, like `stackBackground`, expect boolean values. If you're using the graphics kit, use the `truthy()` util function to convert a string value to a boolean.

> **Note:** In the graphics kit, the image source paths in ai2svelte components have to be fixed by passing the resolved `asset('/')` root to each step object's `assetsPath`, like in the example above.

## Custom foreground

[Demo](?path=/story/components-graphics-scroller--custom-foreground)

Instead of just text, you can use components as foregrounds, and optionally pass props to it.

If you're customising your own foreground component, remember to add alt text that describes the background graphic.

```svelte
<script>
  import MyBackground from './MyBackground.svelte'; // Your own background component
  import MyInteractiveForeground from './MyInteractiveForeground.svelte'; // Your custom foreground component

  const steps = [
    {
      background: MyBackground,
      backgroundProps: { colour: 'red' }, // Props for your background component, if needed
      foreground: MyInteractiveForeground, // Custom foreground component
    },
    {
      background: MyBackground,
      backgroundProps: { colour: 'blue' },
      foreground: '#### Step 2\n\nLorem ipsum blue', // You can still add a markdown string as foreground; you can mix and match
    },
    {
      background: MyBackground,
      backgroundProps: { colour: 'green' },
      foreground: MyInteractiveForeground,
      foregroundProps: { count: 100 }, // Props for your custom foreground component, if needed
    },
  ];
</script>

<Scroller {steps} />
```

## Custom foreground with ArchieML

[Demo](?path=/story/components-graphics-scroller--customforeground-archie-ml)

You can use custom foreground components with ArchieML with a few additional steps.

In your graphics kit project's `App.svelte`, import your custom foregroud components and add them to a `foregroundComponents` object, just as you import ai2svelte background graphics and add them to the `aiCharts` object:

```svelte
<!-- App.svelte -->
<script>
  import content from '$locales/en/content.json';

  // Background ai2svelte graphics
  import AiMap1 from './ai2svelte/my-map-1.svelte';
  import AiMap2 from './ai2svelte/my-map-2.svelte';
  import AiMap3 from './ai2svelte/my-map-3.svelte';

  // Foreground components, which can be ai2svelte or not.
  import Foreground1 from './ai2svelte/my-foreground-1.svelte';

  // Graphics kit only
  import { asset } from '$app/paths'; // 👈 If using in the graphics kit...
  import { truthy } from '$utils/propValidators'; // 👈 If using in the graphics kit...

  // Background ai2svelte graphics components
  const aiCharts = {
    AiMap1,
    AiMap2,
    AiMap3,
    // Other charts...
  };

  // Foreground components
  const foregroundComponents = {
    Foreground1,
    // Other components...
  };
</script>
```

Then add the following structure to your ArchieML doc, making sure that the names of your charts in the `aiCharts` and `foregroundComponents` objects match the names of each step's `background` and `foreground` in the ArchieML doc:

```yaml
# ArchieML doc
[blocks]
type: ai-scroller
id: my-map-scroller
foregroundPosition: left
stackBackground: true

# Array of step objects
  [.steps]
    background: AiMap1
    # You can still use a markdown string even if other step/s use a custom foreground component
    foreground: #### Step 1

    Here's where something happend.
    :end
    altText: A map showing the Upper West side in New York City.
    :end

    background: AiMap2
    foreground: Foreground1 # The name of your custom foreground component
    altText: The same map now highlights 98th Street.
    :end
    background: AiMap3
    foreground: #### Step 3

    ... and now there are multiple protests.
    :end
    altText: The same map now highlights three locations near 98th Street where something particulary important happened.
    :end
  []
[]
```

Then parse the relevant ArchieML block object before passing to the `Scroller` component.

```svelte
<!-- App.svelte -->
{#each content.blocks as block}
  {#if block.type === 'ai-scroller'}
    <Scroller
      id={block.id}
      backgroundWidth={block.width}
      foregroundPosition={block.foregroundPosition}
      stackBackground={truthy(block.stackBackground)}
      steps={block.steps.map((step) => ({
        background: aiCharts[step.background],
        backgroundProps: { assetsPath: asset('/').replace(/\/$/, '') },
        foreground: foregroundComponents[step.foreground] || step.foreground,
        foregroundProps: { assetsPath: asset('/').replace(/\/$/, '') },
        altText: step.altText,
      }))}
    />
  {/if}
{/each}
```

> **Note:** You only need to pass `foregroundProps: { assetsPath: asset('/').replace(/\/$/, '') }` in the graphics kit if your foreground components are ai2svelte graphicss.
