Skip to page content

useScroll composable
v2.34+

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.

NOTE

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.

Outside of a component

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.

  • scrollTarget names the container directly: an Element (or window), a CSS selector, or a component instance (standing for its root element), the same as the scroll-target prop of the scrolling components.
  • target changes 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 disabled pauses and resumes the tracking (the state keeps its last values while paused, and resuming reports right away if the position moved meanwhile)
  • pointing scrollTarget (or target) to another container follows it, starting from scratch
  • changing axis or debounce applies from the next scroll
  • swapping onScroll takes 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: