The useScroll() composable tracks the scrolling of the page (or of a scrollable container) through reactive state: the scrollPosition, the scrollDirection of the last scroll, the scrollDelta since the previous report and the scrollInflectionPoint where the direction last changed.
It is the setup-code counterpart of the QScrollObserver component, which is built on it, and of the v-scroll directive. Use the composable when you want the scroll details on your component, or on any scrollable container, without adding an extra node to your template.
On the server-side of SSR or SSG modes, the composable never listens to anything: the state keeps its initial values until the client takes over.
The composable can also be called outside of setup(): in a boot file, a store or a plain module. There is no component root to start the detection from and no mount to wait for, so supply a scrollTarget (or a target element); the tracking starts right away and nothing stops it by itself: call stopScroll() when you are done.
Syntax
import { useTemplateRef } from 'vue'
import { useScroll } from 'quasar'
setup () {
const scrollTarget = useTemplateRef('scrollTarget') // an Element or a component
const {
scrollPosition, scrollDirection, scrollDirectionChanged,
scrollDelta, scrollInflectionPoint, refreshScroll, stopScroll
} = useScroll({
// all optional:
scrollTarget, // the scroll container (default: auto detected from the component's own root element)
axis: 'both', // 'vertical', 'horizontal' or 'both' (default: 'vertical')
debounce: 100, // ms per report; 0 for one on every scroll event (default: one per animation frame)
disabled: true, // pause listening (default: false)
onScroll (details) { // called with the scroll details on every change
// ...
}
})
// ...
}function useScroll(
options?: MaybeRefOrGetter<{
target?: MaybeRefOrGetter<
Element | ComponentPublicInstance | null | undefined
>
scrollTarget?: MaybeRefOrGetter<
Element | Window | string | ComponentPublicInstance | null | undefined
>
axis?: 'vertical' | 'horizontal' | 'both'
debounce?: string | number
disabled?: boolean
onScroll?: (details: {
position: { top: number; left: number }
direction: 'up' | 'down' | 'left' | 'right'
directionChanged: boolean
delta: { top: number; left: number }
inflectionPoint: { top: number; left: number }
}) => void
}>
): {
scrollPosition: ShallowRef<{ top: number; left: number }>
scrollDirection: Ref<'up' | 'down' | 'left' | 'right'>
scrollDirectionChanged: Ref<boolean>
scrollDelta: ShallowRef<{ top: number; left: number }>
scrollInflectionPoint: ShallowRef<{ top: number; left: number }>
refreshScroll: () => void
stopScroll: () => void
}The reactive state mirrors the details that QScrollObserver emits (and that onScroll receives): scrollPosition, scrollDelta and scrollInflectionPoint are Objects with top and left offsets (in pixels), scrollDirection is the direction of the last scroll and scrollDirectionChanged tells whether that last scroll reversed the direction.
Which container gets tracked
Without options, the composable follows the same algorithm as every scrolling component and directive of Quasar: starting from the root element of the component it is called in (as of the moment the component gets mounted), it looks for the closest parent with the scroll, scroll-y or overflow-auto CSS class and, if none is found, it listens to the page itself.
scrollTargetnames the container directly: an Element (orwindow), a CSS selector, or a component instance (standing for its root element), the same as thescroll-targetprop of the scrolling components.targetchanges where the auto detection starts from: an element or component ref whose closest scrollable parent is the container you are after. This is what you need in a component rendering a fragment (multiple root nodes), which has no root element to start from.
The first report happens as soon as the container is available, should it be scrolled already; onScroll is called for it as well, then only when the position changes on the watched axis.
Without a debounce, the composable reports at most once per animation frame, no matter how many scroll events the browser fires in between. With debounce: 0 it reports on every scroll event. With a debounce of some milliseconds, it reports at most once per window of that many milliseconds; the last change is never missed.
refreshScroll() reads the position right away, skipping the debounce. You will rarely need it, since the browser reports every scroll on its own.
stopScroll() ends the tracking for good. You will rarely need it either, as the composable stops by itself when the component gets destroyed.
Changing the options while running
The options can be a plain Object, a Ref or a getter Function. A plain Object is read once. With a Ref or a getter, the composable tracks whatever reactive state the options read and re-applies them whenever that state changes, so you never call anything to “update” it:
- toggling
disabledpauses and resumes the tracking (the state keeps its last values while paused, and resuming reports right away if the position moved meanwhile) - pointing
scrollTarget(ortarget) to another container follows it, starting from scratch - changing
axisordebounceapplies from the next scroll - swapping
onScrolltakes effect from the next scroll
import { ref } from 'vue'
import { useScroll } from 'quasar'
setup () {
const paused = ref(false)
const { scrollPosition } = useScroll(() => ({
disabled: paused.value
}))
function pause () { paused.value = true }
function resume () { paused.value = false }
// ...
}Example
Tracking a scrollable container through a template ref, with every detail the composable reports:
Without options, the auto detection starts from the component’s root element and, on a standard layout, ends up on the page itself. Scroll this page down past the example below and back up a bit: the “back to top” button gets enabled once the page has been scrolled down and the user is scrolling up again: