useScroll
useScroll is used to create scroll-linked animations, like progress indicators and parallax effects.
Note: When scroll-linked animations are powered by the
scrollfunction frommotion, animations usingopacityortransformCSS properties can be hardware accelerated.
<script>
import { useScroll, useSpring } from '@humanspeak/svelte-motion'
const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress)
</script>
<div
style="position: fixed; top: 0; left: 0; right: 0; height: 5px;
background: #ff0088; transform-origin: left;
transform: scaleX({scaleX.current});"
/><script>
import { useScroll, useSpring } from '@humanspeak/svelte-motion'
const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress)
</script>
<div
style="position: fixed; top: 0; left: 0; right: 0; height: 5px;
background: #ff0088; transform-origin: left;
transform: scaleX({scaleX.current});"
/>Usage
Import
<script>
import { useScroll } from '@humanspeak/svelte-motion'
</script><script>
import { useScroll } from '@humanspeak/svelte-motion'
</script>useScroll returns four motion values augmented with a $state-backed .current getter and a Svelte readable .subscribe shim:
| Value | Description |
|---|---|
scrollX | Horizontal scroll position in pixels |
scrollY | Vertical scroll position in pixels |
scrollXProgress | Horizontal scroll progress between 0 and 1 |
scrollYProgress | Vertical scroll progress between 0 and 1 |
Read them with scrollY.current in templates and $derived / $effect, with scrollY.get() in imperative code, or with $scrollY for store-style consumers. They compose with useTransform, useSpring, and every other motion-value-aware hook.
Page scroll
By default, useScroll tracks the page scroll position:
<script>
import { useScroll, useSpring } from '@humanspeak/svelte-motion'
const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress)
</script>
<div
style="position: fixed; top: 0; left: 0; right: 0; height: 5px;
background: #ff0088; transform-origin: left;
transform: scaleX({scaleX.current});"
/><script>
import { useScroll, useSpring } from '@humanspeak/svelte-motion'
const { scrollYProgress } = useScroll()
const scaleX = useSpring(scrollYProgress)
</script>
<div
style="position: fixed; top: 0; left: 0; right: 0; height: 5px;
background: #ff0088; transform-origin: left;
transform: scaleX({scaleX.current});"
/>Element scroll
Track the scroll position of a specific scrollable element by passing it as the container:
<script>
import { useScroll } from '@humanspeak/svelte-motion'
let containerEl
const { scrollYProgress } = useScroll({ container: containerEl })
</script>
<div bind:this={containerEl} style="overflow-y: scroll; height: 300px;">
<!-- tall content -->
</div>
<div>Scroll progress: {scrollYProgress.current}</div><script>
import { useScroll } from '@humanspeak/svelte-motion'
let containerEl
const { scrollYProgress } = useScroll({ container: containerEl })
</script>
<div bind:this={containerEl} style="overflow-y: scroll; height: 300px;">
<!-- tall content -->
</div>
<div>Scroll progress: {scrollYProgress.current}</div>Element position
Track a target element’s position as it scrolls within its container (or the viewport):
<script>
import { useScroll } from '@humanspeak/svelte-motion'
let targetEl
const { scrollYProgress } = useScroll({ target: targetEl })
</script>
<div bind:this={targetEl}>
<div style="opacity: {scrollYProgress.current}">
Fades in as you scroll to this element
</div>
</div><script>
import { useScroll } from '@humanspeak/svelte-motion'
let targetEl
const { scrollYProgress } = useScroll({ target: targetEl })
</script>
<div bind:this={targetEl}>
<div style="opacity: {scrollYProgress.current}">
Fades in as you scroll to this element
</div>
</div>Scroll offsets
When tracking an element’s position, the offset option defines when the tracking starts and ends. Each offset is a pair of intersections — one for the target and one for the container:
<script>
import { useScroll } from '@humanspeak/svelte-motion'
let targetEl
// Start when target enters bottom of viewport,
// end when target reaches top of viewport
const { scrollYProgress } = useScroll({
target: targetEl,
offset: ['start end', 'end start']
})
</script><script>
import { useScroll } from '@humanspeak/svelte-motion'
let targetEl
// Start when target enters bottom of viewport,
// end when target reaches top of viewport
const { scrollYProgress } = useScroll({
target: targetEl,
offset: ['start end', 'end start']
})
</script>Named offset values:
| Value | Description |
|---|---|
"start" | Top edge of the element |
"center" | Center of the element |
"end" | Bottom edge of the element |
You can also use numbers (0 to 1) and pixel values ("100px").
Performance
Scroll animations work best with CSS properties that can be GPU-accelerated:
transform(translateX, translateY, scale, rotate)opacity
These properties don’t trigger layout or paint, so the browser can animate them on the compositor thread for smooth 60fps performance even during rapid scrolling.
scrollXProgress and scrollYProgress, and values mapped from them with useTransform(progress, [input], [output]), run as native scroll-timeline animations when the browser supports them and they are bound to opacity, filter, clipPath, transform, or backgroundColor. The following fall back to a JavaScript update on every scroll event:
- Chained transforms (a transform of another transformed value)
clamp: false- Function transformers, such as
useTransform(progress, (v) => ...) - Input stops outside ascending
0-1(for example, descending ranges)
The x, y, and scale shortcuts also update in JavaScript, to match Framer Motion. Map to a full transform string when you need compositor-driven movement.
Which offset values can run natively depends on whether you pass a target:
- With a
target, the progress values run on a native view timeline when the offset’s two points sit on the container’sstart/endedges with proportional target positions. For example,["start end", "end start"](while the target is in view),["start end", "end end"](entering), and["center end", "center start"]. Pixel andvw/vhlengths, and container edges such ascenter, fall back to JavaScript. - Without a
target, page or container progress runs natively only when nooffsetis set. A scroll timeline can’t express an offset, souseScroll({ offset })always updates in JavaScript.
A native view timeline follows the target’s nearest scrolling ancestor. If your CSS makes body a scroll container that doesn’t actually scroll (for example overflow-y: auto on both html and body), native view-timeline animations stay frozen. Leave body at overflow: visible so the document is the scroller.
Options
| Option | Type | Description |
|---|---|---|
container | HTMLElement | Scrollable element to track. Defaults to the page. |
target | HTMLElement | Target element to track position of within the container. |
offset | string[] | Array of scroll offsets defining when tracking starts and ends. |
axis | 'x' \| 'y' | Which axis to use for the single-axis progress callback. Defaults to 'y'. |
API Reference
Signature
useScroll(options?: UseScrollOptions): {
scrollX: AugmentedMotionValue<number>
scrollY: AugmentedMotionValue<number>
scrollXProgress: AugmentedMotionValue<number>
scrollYProgress: AugmentedMotionValue<number>
}useScroll(options?: UseScrollOptions): {
scrollX: AugmentedMotionValue<number>
scrollY: AugmentedMotionValue<number>
scrollXProgress: AugmentedMotionValue<number>
scrollYProgress: AugmentedMotionValue<number>
}Returns
An object with four AugmentedMotionValue<number>s — real motion-dom MotionValues with .current getter, .subscribe shim, and all standard motion-value methods (get, getVelocity, on, etc.).
See also
- Motion values overview — introduction to motion value stores
- useSpring — smooth scroll progress with spring physics
- useTransform — map scroll progress to visual ranges
- useMotionValueEvent — subscribe to scroll changes
Based on Motion’s useScroll API.