Skip to content

Commit a165acc

Browse files
committed
feat: Added useRafFn function
1 parent 6ea48de commit a165acc

File tree

9 files changed

+187
-3
lines changed

9 files changed

+187
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Please check the [documentation](https://microcipcip.github.io/vue-use-kit/) to
3737
- [`useMouse`](./src/components/useMouse/stories/useMouse.md) — tracks the mouse position. [![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usemouse--demo)
3838
- [`useMouseElement`](./src/components/useMouseElement/stories/useMouseElement.md) — tracks the mouse position relative to given element. [![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/sensors-usemouseelement--demo)
3939
- Animations
40+
- [`useRafFn`](./src/components/useRafFn/stories/useRafFn.md) — calls function with requestAnimationFrame. [![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/animations-useraffn--demo)
4041
- [`useTimeout`](./src/components/useTimeout/stories/useTimeout.md) — returns `isReady` true when timer is completed. [![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/animations-usetimeout--demo)
4142
- [`useTimeoutFn`](./src/components/useTimeoutFn/stories/useTimeoutFn.md) — calls function when timer is completed. [![Demo](https://img.shields.io/badge/demo-🚀-yellow.svg)](https://microcipcip.github.io/vue-use-kit/?path=/story/animations-usetimeoutfn--demo)
4243
- UI

src/components/useRafFn/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './useRafFn'
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<template>
2+
<table class="table is-fullwidth">
3+
<thead>
4+
<tr>
5+
<th>Prop</th>
6+
<th>Description</th>
7+
<th>Value</th>
8+
</tr>
9+
</thead>
10+
<tbody>
11+
<tr>
12+
<td>callbackCounter</td>
13+
<td>Raf callback counter</td>
14+
<td>
15+
<span>{{ callbackCounter }}</span>
16+
</td>
17+
</tr>
18+
<tr>
19+
<td>animDuration</td>
20+
<td>Raf callback counter</td>
21+
<td>
22+
<span>{{ animDuration }}s</span>
23+
</td>
24+
</tr>
25+
<tr>
26+
<td colspan="2">
27+
<button class="button is-primary" @click="start" v-if="!isRunning">
28+
Resume animation
29+
</button>
30+
<button class="button is-danger" @click="stop" v-else>
31+
Stop animation
32+
</button>
33+
<br />
34+
<br />
35+
<br />
36+
<input type="number" v-model="fpsRef" />
37+
</td>
38+
</tr>
39+
</tbody>
40+
</table>
41+
</template>
42+
43+
<script lang="ts">
44+
import Vue from 'vue'
45+
import { ref } from '../../../api'
46+
import { useRafFn } from '../../../vue-use-kit'
47+
48+
export default Vue.extend({
49+
name: 'UseRafFnDemo',
50+
setup() {
51+
const animDuration = ref(0)
52+
const callbackCounter = ref(0)
53+
const fpsRef = ref(4)
54+
const animHandler = (t: number) => {
55+
callbackCounter.value = callbackCounter.value + 1
56+
animDuration.value = Math.ceil(t)
57+
}
58+
59+
const { isRunning, start, stop } = useRafFn(animHandler, fpsRef, false)
60+
return { isRunning, start, stop, callbackCounter, animDuration, fpsRef }
61+
}
62+
})
63+
</script>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# useRafFn
2+
3+
Vue function that...
4+
5+
## Reference
6+
7+
```typescript
8+
// useRafFn()
9+
```
10+
11+
### Parameters
12+
13+
- `value: string` lorem ipsa
14+
15+
### Returns
16+
17+
- `value: string` lorem ipsa
18+
19+
## Usage
20+
21+
```html
22+
<template></template>
23+
```
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { storiesOf } from '@storybook/vue'
2+
import StoryTitle from '../../../helpers/StoryTitle.vue'
3+
import UseRafFnDemo from './UseRafFnDemo.vue'
4+
5+
const storiesPath = __dirname
6+
const notes = require('./useRafFn.md').default
7+
8+
const basicDemo = () => ({
9+
components: { StoryTitle, demo: UseRafFnDemo },
10+
template: `
11+
<div class="container">
12+
<story-title stories-path="${storiesPath}" file-name="UseRafFnDemo.vue">
13+
<template v-slot:title></template>
14+
<template v-slot:intro></template>
15+
</story-title>
16+
<demo />
17+
</div>`
18+
})
19+
20+
storiesOf('animations|useRafFn', module)
21+
.addParameters({ notes })
22+
.add('Demo', basicDemo)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
describe('useRafFn', () => {
2+
it('should do something', () => {
3+
// Add test here
4+
})
5+
})
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { ref, isRef, watch, onMounted, onUnmounted, Ref } from '../../api'
2+
3+
type TFps = number | Ref<number>
4+
5+
const getFps = (fps: TFps) => (isRef(fps) ? fps.value : fps)
6+
const calcFpsInterval = (fps: number) => 1000 / fps
7+
8+
export function useRafFn(
9+
callback: Function,
10+
fps: TFps = 60,
11+
runOnMount = true
12+
) {
13+
const isRunningRef = ref(false)
14+
const fpsIntervalRef = ref(calcFpsInterval(getFps(fps)))
15+
let startTime = 0
16+
let timeNow = 0
17+
let isPaused = false
18+
let prevTime = 0
19+
let timeLast = 0
20+
function loop(timeStamp: number) {
21+
if (!startTime) startTime = timeStamp
22+
if (!isRunningRef.value) return
23+
24+
if (!isPaused) {
25+
timeNow = timeStamp - startTime - prevTime
26+
} else {
27+
prevTime = timeStamp - startTime - timeNow
28+
timeNow = timeStamp - startTime - prevTime
29+
isPaused = false
30+
}
31+
32+
// Run callback only on the given fps
33+
if (Math.ceil(timeNow - timeLast) > fpsIntervalRef.value) {
34+
callback(timeNow)
35+
timeLast = timeNow
36+
}
37+
38+
requestAnimationFrame(loop)
39+
}
40+
41+
const start = () => {
42+
isRunningRef.value = true
43+
requestAnimationFrame(loop)
44+
}
45+
46+
const stop = () => {
47+
isRunningRef.value = false
48+
isPaused = true
49+
}
50+
51+
// Watch fps value since it could potentially be a ref and we may want
52+
// to change the Raf speed from user's input
53+
const updateFpsInterval = () => {
54+
// If fps is not a ref there is no point in updating it
55+
if (!isRef(fps)) return
56+
watch(fps, () => {
57+
fpsIntervalRef.value = calcFpsInterval(getFps(fps))
58+
})
59+
}
60+
updateFpsInterval()
61+
62+
onMounted(() => runOnMount && start())
63+
onUnmounted(stop)
64+
65+
return {
66+
isRunning: isRunningRef,
67+
start,
68+
stop
69+
}
70+
}

src/components/useTimeoutFn/useTimeoutFn.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ export function useTimeoutFn(callback: Function, ms = 0, runOnMount = true) {
2525
}, ms)
2626
}
2727

28-
onMounted(() => {
29-
if (runOnMount) setTimer()
30-
})
28+
onMounted(() => runOnMount && setTimer())
3129
onUnmounted(cancelTimer)
3230

3331
return { isReady, isIdle, cancelTimer, resetTimer: setTimer }

src/vue-use-kit.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,6 @@ export * from './components/useMedia'
44
export * from './components/useMouse'
55
export * from './components/useMouseElement'
66
export * from './components/useHover'
7+
export * from './components/useRafFn'
78
export * from './components/useTimeoutFn'
89
export * from './components/useTimeout'

0 commit comments

Comments
 (0)