Skip to content

Commit

Permalink
feat(useScroll): Adding useScroll feature
Browse files Browse the repository at this point in the history
  • Loading branch information
microcipcip committed Mar 19, 2020
1 parent 0c142fa commit cf14283
Show file tree
Hide file tree
Showing 10 changed files with 334 additions and 2 deletions.
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

0 comments on commit cf14283

Please sign in to comment.