Skip to content

Commit

Permalink
feat(useWindowScroll): support setting scroll position and toggling s…
Browse files Browse the repository at this point in the history
…mooth scrolling

Add ability to set scroll values and turn on or off smooth scrolling.

fix #1978
  • Loading branch information
curtgrimes committed Jul 30, 2022
1 parent 86358ff commit dbeb578
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 14 deletions.
5 changes: 5 additions & 0 deletions packages/.vitepress/theme/styles/demo.css
Expand Up @@ -60,6 +60,11 @@
padding: 0.25em 0.7em 0.2em 0.7em;
}

button.extra-small {
padding: 0.1em 0.25rem;
font-size: 0.7rem;
}

button.orange {
--vp-c-brand: #db8742;
--vp-c-brand-dark: #ad6e39;
Expand Down
34 changes: 30 additions & 4 deletions packages/core/useWindowScroll/demo.vue
@@ -1,21 +1,41 @@
<script setup lang="ts">
import { computed } from '@vue/reactivity'
import { useWindowScroll } from '@vueuse/core'
import { ref } from 'vue'
const { x, y } = useWindowScroll()
const smooth = ref(false)
const behavior = computed(() => smooth.value ? 'smooth' : 'auto')
const { x, y } = useWindowScroll({ behavior })
</script>

<template>
<div>
<div>
See scroll values in the lower right corner of the screen.
See and control scroll values in the lower right corner of the screen.
</div>
<div class="scroller" />
<div class="float">
<note class="mb-2">
Scroll value
</note>
x: {{ x }}<br>
y: {{ y }}
<label>
<input v-model="smooth" type="checkbox"> Smooth scroll
</label>
<p>x: {{ x }}</p>
<button class="extra-small" @click="x -= 200">
-200
</button>
<button class="extra-small" @click="x += 200">
+200
</button>
<hr>
<p>y: {{ y }}</p>
<button class="extra-small" @click="y -= 200">
-200
</button>
<button class="extra-small" @click="y += 200">
+200
</button>
</div>
</div>
</template>
Expand All @@ -28,4 +48,10 @@ const { x, y } = useWindowScroll()
width: 10000px;
height: 10000px;
}
label {
font-size: 0.8rem;
display: flex;
gap: 0.2rem;
}
</style>
46 changes: 44 additions & 2 deletions packages/core/useWindowScroll/index.md
Expand Up @@ -4,12 +4,54 @@ category: Elements

# useWindowScroll

Reactive window scroll
Reactive window scroll.

## Usage

```js
```vue
<script setup lang="ts">
import { useWindowScroll } from '@vueuse/core'
const { x, y } = useWindowScroll()
</script>
<template>
Your current position: {{ x }}, {{ y }}<br>
<button @click="y += 500">
Scroll down 500px
</button><br>
<button @click="y -= 500">
Scroll up 500px
</button>
</template>
```


### Smooth scrolling

Set `behavior: smooth` to enable smooth scrolling. The `behavior` option defaults to `auto`, which means no smooth scrolling. See the `behavior` option on [`window.scrollTo()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollTo) for more information.
```ts
import { useWindowScroll } from '@vueuse/core'

const { x, y } = useWindowScroll({ behavior: 'smooth' })

// Or as a `ref`:
const smooth = ref(false)
const behavior = computed(() => smooth.value ? 'smooth' : 'auto')
const { x, y } = useWindowScroll({ behavior })
```


### Custom window

You can use a custom window with the `window` option:

```ts
import { useWindowScroll } from '@vueuse/core'

const { x, y } = useWindowScroll({ window: someOtherWindow })

// Or as a `ref`:
const myWindow = ref(window)
const { x, y } = useWindowScroll({ window: myWindow })
```
69 changes: 61 additions & 8 deletions packages/core/useWindowScroll/index.ts
@@ -1,30 +1,85 @@
import { ref } from 'vue-demi'
import type { MaybeComputedRef } from '@vueuse/shared'
import { resolveRef, resolveUnref } from '@vueuse/shared'
import type { Ref } from 'vue-demi'
import { computed, ref } from 'vue-demi'
import { useEventListener } from '../useEventListener'
import type { ConfigurableWindow } from '../_configurable'
import { defaultWindow } from '../_configurable'

export interface UseWindowScrollOptions extends ConfigurableWindow {
/**
* Optionally specify a scroll behavior of `auto` (default, not smooth scrolling) or `smooth` (for smooth scrolling)
*
* @default 'auto'
*/
behavior?: MaybeComputedRef<ScrollBehavior>
}

export interface UseWindowScrollReturn {
/**
* The X scroll position of the page
*/
x: Ref<number>
/**
* The Y scroll position of the page
*/
y: Ref<number>
}

/**
* Reactive window scroll.
*
* @see https://vueuse.org/useWindowScroll
* @param options
*/
export function useWindowScroll({ window = defaultWindow }: ConfigurableWindow = {}) {
export function useWindowScroll(options: UseWindowScrollOptions = {}): UseWindowScrollReturn {
const { window = defaultWindow } = options
const behavior = resolveRef(options.behavior || 'auto')

if (!window) {
return {
x: ref(0),
y: ref(0),
}
}

const x = ref(window.pageXOffset)
const y = ref(window.pageYOffset)
const internalX: Ref<number> = ref(0)
const internalY: Ref<number> = ref(0)

// Use a computed for x and y because we want to write the value to the refs
// during a `scrollTo()` without firing additional `scrollTo()`s in the process.
const x = computed({
get() {
return internalX.value ?? window.scrollX
},
set(x) {
scrollTo(x, undefined)
},
})

const y = computed({
get() {
return internalY.value ?? window.scrollY
},
set(y) {
scrollTo(undefined, y)
},
})

function scrollTo(_x: number | undefined, _y: number | undefined) {
window?.scrollTo({
top: resolveUnref(_y) ?? y.value,
left: resolveUnref(_x) ?? x.value,
behavior: resolveUnref(behavior),
})
}

useEventListener(
window,
'scroll',
() => {
x.value = window.pageXOffset
y.value = window.pageYOffset
internalX.value = window.scrollX || 0
internalY.value = window.scrollY || 0
},
{
capture: false,
Expand All @@ -34,5 +89,3 @@ export function useWindowScroll({ window = defaultWindow }: ConfigurableWindow =

return { x, y }
}

export type UseWindowScrollReturn = ReturnType<typeof useWindowScroll>

0 comments on commit dbeb578

Please sign in to comment.