Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ export default Vue.extend({
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usemouseleavepage--demo)
- [`useOrientation`](./src/functions/useOrientation/stories/useOrientation.md) — tracks state of device's screen orientation.
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-useorientation--demo)
- [`useScroll`](./src/functions/useScroll/stories/useScroll.md) — tracks an HTML element's scroll position.
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usescroll--demo)
- [`useSearchParams`](./src/functions/useSearchParams/stories/useSearchParams.md) — tracks browser's location search params.
[![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usesearchparams--demo)
- Animations
Expand Down
2 changes: 1 addition & 1 deletion src/functions/useMouse/stories/useMouse.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function useMouse(): {
import { useMouse } from 'vue-use-kit'

export default Vue.extend({
name: 'useMouse',
name: 'useMouseDemo',
setup() {
const { docX, docY } = useMouse()
return { docX, docY }
Expand Down
7 changes: 6 additions & 1 deletion src/functions/useOrientation/stories/useOrientation.story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ const basicDemo = () => ({
demo-name="UseOrientationDemo.vue"
>
<template v-slot:title></template>
<template v-slot:intro></template>
<template v-slot:intro>
<p>
<strong>Try to rotate your device</strong> to see the value changes.
Please note that this will work only on supported devices.
</p>
</template>
</story-title>
<demo />
</div>`
Expand Down
1 change: 1 addition & 0 deletions src/functions/useScroll/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './useScroll'
69 changes: 69 additions & 0 deletions src/functions/useScroll/stories/UseScrollDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<template>
<div>
<table class="table is-fullwidth">
<thead>
<tr>
<th>Prop</th>
<th>Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>isScrolling</td>
<td>{{ isScrolling }}</td>
</tr>
<tr>
<td>x, y</td>
<td>{{ x }}px - {{ y }}px</td>
</tr>
<tr>
<td colspan="2">
<button class="button is-primary" @click="start" v-if="!isTracking">
Enable scroll tracking
</button>
<button class="button is-danger" @click="stop" v-else>
Disable scroll tracking
</button>
</td>
</tr>
</tbody>
</table>
<div class="scrollme" ref="scrollRef">
<span class="scrollme__msg">Scroll me!</span>
<div class="scrollme__height"></div>
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import { useScroll } from '@src/vue-use-kit'
import { ref } from '@src/api'

export default Vue.extend({
name: 'UseScrollDemo',
setup() {
const scrollRef = ref(null)
const { x, y, isScrolling, isTracking, start, stop } = useScroll(scrollRef)
return { scrollRef, x, y, isScrolling, isTracking, start, stop }
}
})
</script>

<style scoped>
.scrollme {
position: relative;
overflow-y: scroll;
height: 200px;
background: #f1f1f1;
}

.scrollme__msg {
padding: 10px;
}

.scrollme__height {
width: 2000px;
height: 40000px;
}
</style>
80 changes: 80 additions & 0 deletions src/functions/useScroll/stories/useScroll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# useScroll

Vue function that tracks an HTML element's scroll position.

## Reference

```typescript
function useScroll(
elRef: Ref<null | HTMLElement | Window>,
ms?: number,
runOnMount?: boolean
): {
x: Ref<number>
y: Ref<number>
isTracking: Ref<boolean>
isScrolling: Ref<boolean>
start: () => void
stop: () => void
}
```

### Parameters

- `elRef: Ref<null | HTMLElement | Window>` target element used for tracking the `x` and `y` scroll position
- `ms: number` how many milliseconds of delay before `isScrolling` goes back to false (basically when user is idle), `150` by default
- `runOnMount: boolean` whether to run the scroll tracking on mount, `true` by default

### Returns

- `x: Ref<number>` the `x` scroll position relative to the elRef
- `y: Ref<number>` the `y` scroll position relative to the elRef
- `isScrolling: Ref<boolean>` whether the element is currently being scrolled or not
- `isTracking: Ref<boolean>` whether this function events are running or not
- `start: Function` the function used for starting the scroll tracking
- `stop: Function` the function used for stopping the scroll tracking

## Usage

```html
<template>
<div>
<div>x, y: {{ x }}px - {{ y }}px</div>
<div>isScrolling: {{ isScrolling }}</div>
<button @click="start" v-if="!isTracking">Start tracking</button>
<button @click="stop" v-else>Stop tracking</button>
<div ref="scrollRef" class="scrollme">
<div class="scrollme__height"></div>
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import { useScroll } from 'vue-use-kit'
import { ref } from '@src/api'

export default Vue.extend({
name: 'useScrollDemo',
setup() {
const scrollRef = ref(null)
const { x, y, isScrolling, isTracking, start, stop } = useScroll(scrollRef)
return { scrollRef, x, y, isScrolling, isTracking, start, stop }
}
})
</script>

<style scoped>
.scrollme {
position: relative;
overflow-y: scroll;
height: 200px;
background: #f1f1f1;
}

.scrollme__height {
width: 2000px;
height: 40000px;
}
</style>
```
28 changes: 28 additions & 0 deletions src/functions/useScroll/stories/useScroll.story.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { storiesOf } from '@storybook/vue'
import path from 'path'
import StoryTitle from '@src/helpers/StoryTitle.vue'
import UseScrollDemo from './UseScrollDemo.vue'

const functionName = 'useScroll'
const functionPath = path.resolve(__dirname, '..')
const notes = require(`./${functionName}.md`).default

const basicDemo = () => ({
components: { StoryTitle, demo: UseScrollDemo },
template: `
<div class="container">
<story-title
function-path="${functionPath}"
source-name="${functionName}"
demo-name="UseScrollDemo.vue"
>
<template v-slot:title></template>
<template v-slot:intro></template>
</story-title>
<demo />
</div>`
})

storiesOf('sensors|useScroll', module)
.addParameters({ notes })
.add('Demo', basicDemo)
83 changes: 83 additions & 0 deletions src/functions/useScroll/useScroll.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
checkElementExistenceOnMount,
checkOnStartEvents,
checkOnStopEvents,
mount
} from '@src/helpers/test'
import { ref } from '@src/api'
import { useScroll } from '@src/vue-use-kit'

afterEach(() => {
jest.clearAllMocks()
})

const testComponent = (onMount = true) => ({
template: `
<div>
<div id="isTracking" v-if="isTracking"></div>
<div id="isScrolling" v-if="isScrolling"></div>
<div id="x" v-if="x === 0">{{ x }}px</div>
<div id="y" v-if="y === 0">{{ y }}px</div>
<button id="start" @click="start"></button>
<button id="stop" @click="stop"></button>
</div>
`,
setup() {
const { x, y, isScrolling, isTracking, start, stop } = useScroll(
ref(window),
150,
onMount
)
return { x, y, isScrolling, isTracking, start, stop }
}
})

describe('useScroll', () => {
const events = ['scroll']

it('should add events on mounted and remove them on unmounted', async () => {
const addEventListenerSpy = jest.spyOn(window, 'addEventListener')
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener')
const wrapper = mount(testComponent())
await wrapper.vm.$nextTick()
expect(addEventListenerSpy).toHaveBeenCalledTimes(events.length)
events.forEach(event => {
expect(addEventListenerSpy).toBeCalledWith(
event,
expect.any(Function),
expect.any(Object)
)
})

// Destroy instance to check if the remove event listener is being called
wrapper.destroy()
expect(removeEventListenerSpy).toHaveBeenCalledTimes(events.length)
events.forEach(event => {
expect(removeEventListenerSpy).toBeCalledWith(event, expect.any(Function))
})
})

it('should add events again when start is called', async () => {
await checkOnStartEvents(window, events, testComponent)
})

it('should remove events when stop is called', async () => {
await checkOnStopEvents(window, events, testComponent)
})

it('should show #isTracking when runOnMount is true', async () => {
await checkElementExistenceOnMount(true, testComponent(true))
})

it('should not show #isTracking when runOnMount is false', async () => {
await checkElementExistenceOnMount(false, testComponent(false))
})

it('should show #x and #y elements and not show #isScrolling', async () => {
const wrapper = mount(testComponent())
await wrapper.vm.$nextTick()
expect(wrapper.find('#x').exists()).toBe(true)
expect(wrapper.find('#y').exists()).toBe(true)
expect(wrapper.find('#isScrolling').exists()).toBe(false)
})
})
63 changes: 63 additions & 0 deletions src/functions/useScroll/useScroll.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { ref, onMounted, onUnmounted, Ref } from '@src/api'

export function useScroll(
elRef: Ref<null | HTMLElement | Window>,
ms = 150,
runOnMount = true
) {
const isTracking = ref(false)
const isScrolling = ref(false)

const x = ref(0)
const y = ref(0)

let scrollingTimeout: any = null
const updateScrollStatus = () => {
isScrolling.value = true
clearTimeout(scrollingTimeout)
scrollingTimeout = setTimeout(() => (isScrolling.value = false), ms)
}

const updateWindowElement = () => {
x.value = window.pageXOffset
y.value = window.pageYOffset
}

const updateHTMLElement = () => {
x.value = (elRef.value as HTMLElement).scrollLeft
y.value = (elRef.value as HTMLElement).scrollTop
}

const updateElScrollPos = () => {
if (!elRef.value) return
elRef.value === window ? updateWindowElement() : updateHTMLElement()
}

const handleScroll = () => {
updateElScrollPos()
updateScrollStatus()
}

const start = () => {
if (isTracking.value) return
if (elRef.value) {
elRef.value.addEventListener('scroll', handleScroll, {
capture: false,
passive: true
})
updateElScrollPos()
}
isTracking.value = true
}

const stop = () => {
if (!isTracking.value) return
if (elRef.value) elRef.value.removeEventListener('scroll', handleScroll)
isTracking.value = false
}

onMounted(() => runOnMount && start())
onUnmounted(stop)

return { x, y, isTracking, isScrolling, start, stop }
}
1 change: 1 addition & 0 deletions src/vue-use-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from './functions/useMouseElement'
export * from './functions/useMouseLeavePage'
export * from './functions/useOrientation'
export * from './functions/useSearchParams'
export * from './functions/useScroll'
// Animations
export * from './functions/useIntervalFn'
export * from './functions/useInterval'
Expand Down