diff --git a/indexes.json b/indexes.json
index 452d5aa2879a..2d883f75c057 100644
--- a/indexes.json
+++ b/indexes.json
@@ -874,6 +874,13 @@
"category": "Browser",
"description": "script tag injecting"
},
+ {
+ "name": "useScroll",
+ "package": "core",
+ "docs": "https://vueuse.org/core/useScroll/",
+ "category": "Sensors",
+ "description": "reactive scroll position and state"
+ },
{
"name": "useSessionStorage",
"package": "core",
diff --git a/packages/core/index.ts b/packages/core/index.ts
index bd7869a5d0ef..a84c7de0947e 100644
--- a/packages/core/index.ts
+++ b/packages/core/index.ts
@@ -58,6 +58,7 @@ export * from './useRafFn'
export * from './useRefHistory'
export * from './useResizeObserver'
export * from './useScriptTag'
+export * from './useScroll'
export * from './useSessionStorage'
export * from './useShare'
export * from './useSpeechRecognition'
diff --git a/packages/core/useScroll/demo.vue b/packages/core/useScroll/demo.vue
new file mode 100644
index 000000000000..aa0c8860b02e
--- /dev/null
+++ b/packages/core/useScroll/demo.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
Position
+
+ {{ x }}, {{ y }}
+
+
isScrolling
+
+
+ Top Arrived
+
+
+
+ Right Arrived
+
+
+
+ Bottom Arrived
+
+
+
+ Left Arrived
+
+
+
+
+
diff --git a/packages/core/useScroll/index.md b/packages/core/useScroll/index.md
new file mode 100644
index 000000000000..0ee251ab2db5
--- /dev/null
+++ b/packages/core/useScroll/index.md
@@ -0,0 +1,22 @@
+---
+category: Sensors
+---
+
+# useScroll
+
+Reactive scroll position and state
+
+## Usage
+
+```html
+
+
+
+
+
+```
diff --git a/packages/core/useScroll/index.ts b/packages/core/useScroll/index.ts
new file mode 100644
index 000000000000..72e29d480f07
--- /dev/null
+++ b/packages/core/useScroll/index.ts
@@ -0,0 +1,117 @@
+import { ref, reactive } from 'vue-demi'
+import { useThrottleFn, useDebounceFn, noop, MaybeRef } from '@vueuse/shared'
+import { useEventListener } from '../useEventListener'
+
+export interface UseScrollOptions {
+ /**
+ * Throttle time for scroll event,it’s disabled by default.
+ *
+ * @default 0
+ */
+ throttle?: number
+
+ /**
+ * The check time when scrolling ends.
+ * This configuration will be setting to (throttle + idle) when the `throttle` is configured.
+ *
+ * @default 200
+ */
+ idle?: number
+
+ /**
+ * Trigger it when scrolling.
+ *
+ */
+ onScroll?: () => void
+
+ /**
+ * Trigger it when scrolling ends.
+ *
+ */
+ onStop?: () => void
+
+ /**
+ * Listener options for scroll event.
+ *
+ * @default {capture: false, passive: true}
+ */
+ eventListenerOptions?: boolean | AddEventListenerOptions
+}
+
+/**
+ * Reactive scroll.
+ *
+ * @see https://vueuse.org/useScroll
+ * @param element
+ * @param options
+ */
+
+export function useScroll(
+ element: MaybeRef,
+ options: UseScrollOptions = {},
+) {
+ const {
+ throttle = 0,
+ idle = 200,
+ onStop = noop,
+ onScroll = noop,
+ eventListenerOptions = {
+ capture: false,
+ passive: true,
+ },
+ }
+ = options
+
+ const x = ref(0)
+ const y = ref(0)
+ const isScrolling = ref(false)
+ const arrivedState = reactive({
+ left: true,
+ right: false,
+ top: true,
+ bottom: false,
+ })
+
+ if (element) {
+ const onScrollEnd = useDebounceFn(() => {
+ isScrolling.value = false
+ onStop()
+ }, throttle + idle)
+
+ const onScrollHandler = (e: Event) => {
+ const eventTarget = (e.target === document ? (e.target as Document).documentElement : e.target) as HTMLElement
+
+ const scrollLeft = eventTarget.scrollLeft
+ arrivedState.left = scrollLeft <= 0
+ arrivedState.right = scrollLeft + eventTarget.clientWidth >= eventTarget.scrollWidth
+ x.value = scrollLeft
+
+ const scrollTop = eventTarget.scrollTop
+ arrivedState.top = scrollTop <= 0
+ arrivedState.bottom = scrollTop + eventTarget.clientHeight >= eventTarget.scrollHeight
+ y.value = scrollTop
+
+ isScrolling.value = true
+ onScrollEnd()
+ onScroll()
+ }
+
+ useEventListener(
+ element,
+ 'scroll',
+ throttle
+ ? useThrottleFn(onScrollHandler, throttle)
+ : onScrollHandler,
+ eventListenerOptions,
+ )
+ }
+
+ return {
+ x,
+ y,
+ isScrolling,
+ arrivedState,
+ }
+}
+
+export type UseScrollReturn = ReturnType
diff --git a/packages/functions.md b/packages/functions.md
index bd643a301716..95b2be6b0745 100644
--- a/packages/functions.md
+++ b/packages/functions.md
@@ -90,6 +90,7 @@
- [`usePointer`](https://vueuse.org/core/usePointer/) — reactive [pointer state](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events)
- [`usePointerSwipe`](https://vueuse.org/core/usePointerSwipe/) — reactive swipe detection based on [PointerEvents](https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent)
- [`useResizeObserver`](https://vueuse.org/core/useResizeObserver/) — reports changes to the dimensions of an Element's content or the border-box
+ - [`useScroll`](https://vueuse.org/core/useScroll/) — reactive scroll position and state
- [`useSpeechRecognition`](https://vueuse.org/core/useSpeechRecognition/) — reactive [SpeechRecognition](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition)
- [`useSwipe`](https://vueuse.org/core/useSwipe/) — reactive swipe detection based on [`TouchEvents`](https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent)
- [`useUserMedia`](https://vueuse.org/core/useUserMedia/) — reactive [`mediaDevices.getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) streaming