-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
1,018 additions
and
756 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
<h1>Navigation</h1> | ||
|
||
- [Quick start](/) | ||
- [Usage](usage.md) | ||
- [Why this package](comparison.md) | ||
- [API](api.md) | ||
- [Advanced usage](advanced-usage.md) | ||
- [Q&A](qa.md) | ||
|
||
## Quick start | ||
|
||
### Installation | ||
|
||
```bash | ||
$ npm i use-listen-on-animation-frame | ||
``` | ||
|
||
### Use | ||
|
||
Library provides 2 hooks which are `useAnimationFrame`, `useListenOnAnimationFrame`. See [Usage](usage.md), [API](api.md) and [Advanced usage](advanced-usage.md) for more. | ||
|
||
#### Basic | ||
|
||
Run function on every animation frame | ||
|
||
```tsx | ||
import React, { useCallback, useState } from "react"; | ||
import { useAnimationFrame } from "use-listen-on-animation-frame"; | ||
|
||
const getDate = () => new Date(); | ||
|
||
const Component = () => { | ||
const [date, setDate] = useState(getDate()); | ||
|
||
const setDateOnAnimationFrame = useCallback(() => { | ||
setDate(getDate()); | ||
}, []); | ||
|
||
useAnimationFrame(setDateOnAnimationFrame); | ||
|
||
return <div>{date}</div>; | ||
}; | ||
``` | ||
|
||
#### Multiple side effects | ||
|
||
Run function once on every animation frame, and apply multiple listeners to it. Stop and start function & listeners when you want. | ||
|
||
```tsx | ||
import React, { useState } from "react"; | ||
import { useListenOnAnimationFrame } from "use-listen-on-animation-frame"; | ||
|
||
const getCostlyRandomNumber = () => { | ||
const random = Math.random() * 300; | ||
|
||
let counter = 0; | ||
|
||
for (; counter < random; counter++) { | ||
counter++; | ||
} | ||
|
||
return counter; | ||
}; | ||
|
||
const Component = () => { | ||
const [number, setNumber] = useState(0); | ||
|
||
const [addListener, _removeListener, stop, start] = useListenOnAnimationFrame( | ||
getCostlyRandomNumber, | ||
{ autoStart: false } // optional | ||
); | ||
|
||
useEffect(() => { | ||
addListener((thisFrameRandom, _previousFrameRandom) => { | ||
setNumber(thisFrameRandom); | ||
}); | ||
|
||
addListener((thisFrameRandom) => { | ||
// do something; | ||
}); | ||
|
||
addListener((thisFrameRandom) => { | ||
for (let i = 0; i < thisFrameRandom; i++) { | ||
// do something | ||
} | ||
}); | ||
}, [addListener]); | ||
|
||
useEffect(() => { | ||
if (somethingBadHappened) { | ||
stop(); | ||
} | ||
}, [stop, somethingBadHappened]); | ||
|
||
useEffect(() => { | ||
if (somethingGoodHappened) { | ||
start(); | ||
} | ||
}, [start, somethingGoodHappened]); | ||
|
||
return ( | ||
<div> | ||
<span>{number}</span> | ||
<AnotherComponent | ||
addFrameListener={addListener} | ||
stopFrameListening={stop} | ||
/> | ||
</div> | ||
); | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<!-- _coverpage.md --> | ||
|
||
# use-listen-on-animation-frame | ||
|
||
> Invoke & track your functions on every animation frame | ||
<p align="center"> | ||
<em> | ||
ESM | ||
· CommonJS | ||
· 1 dependency | ||
</em> | ||
</p> | ||
|
||
<p align="center"> | ||
<a href="https://github.com/artelydev/use-listen-on-animation-frame/actions?query=workflow%3AMain+branch%3Amain"> | ||
<img alt="Github Actions Build Status" src="https://img.shields.io/github/workflow/status/artelydev/use-listen-on-animation-frame/Main?label=Build&style=flat-square"></img></a> | ||
<a href="https://www.npmjs.com/package/use-listen-on-animation-frame"> | ||
<img alt="npm version" src="https://img.shields.io/npm/v/use-listen-on-animation-frame.svg?style=flat-square"></img></a> | ||
<a href="https://github.com/artelydev/use-listen-on-animation-frame/blob/main/LICENSE"> | ||
<img alt="license: MIT" src="https://img.shields.io/github/license/artelydev/use-listen-on-animation-frame"> | ||
</img> | ||
</a> | ||
<img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/use-listen-on-animation-frame"></img> | ||
<img alt="typed" src="https://shields.io/badge/TypeScript-3178C6?logo=TypeScript&logoColor=FFF&style=flat-square"> </img> | ||
</p> | ||
|
||
[GitHub](https://github.com/artelydev/use-listen-on-animation-frame/) | ||
[Get Started](#quick-start) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
- [Quick start](/) | ||
- [Usage](usage.md) | ||
- [Do something on animation frame](usage.md#do-something-on-animation-frames) | ||
- [Animation frame counter](usage.md#animation-frame-counter) | ||
- [Video current time](usage.md#video-amp-current-timer) | ||
- [Side effects](usage.md#track-your-function-return-on-every-animation-frame) | ||
- [Why this package](comparison.md) | ||
- [API](api.md) | ||
- [`useAnimationFrame`](api.md#useanimationframe) | ||
- [`useListenOnAnimationFrame`](api.md#uselistenonanimationframe) | ||
- [When to use `useListenOnAnimationFrame` over `useAnimationFrame`](api.md#when-to-use-uselistenonanimationframe-over-useanimationframe) | ||
- [Advanced usage](advanced-usage.md) | ||
- [Previous animation frame return](advanced-usage.md#access-previous-animation-frame-function-return) | ||
- [Start & stop](advanced-usage.md#start-and-stop-tracking-your-function) | ||
- [Q&A](qa.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
# Advanced usage | ||
|
||
## Access previous animation frame function return | ||
|
||
If you for some reason need previous animation frame return of your function - it is easily possible. | ||
|
||
[Try it on codesandbox](https://codesandbox.io/s/ms-elapsed-from-1970-pwgpft) | ||
|
||
```tsx | ||
import React, { useEffect, useState } from "react"; | ||
import { useListenOnAnimationFrame } from "use-listen-on-animation-frame"; | ||
|
||
const getMsElapsedFrom1970 = (previousFrameTimeElapsed) => { | ||
if (previousFrameTimeElapsed) { | ||
console.log( | ||
"you wouldn't want to do it here but", | ||
previousFrameTimeElapsed | ||
); | ||
} | ||
|
||
return new Date().getTime(); | ||
}; | ||
|
||
const MilisecondsElapsedFrom1970: React.FC = () => { | ||
const [ms, setMs] = useState<number>(new Date().getTime()); | ||
|
||
const [addListener] = useListenOnAnimationFrame(getMsElapsedFrom1970); | ||
|
||
useEffect(() => { | ||
addListener((_, previousFrameTimeElapsed) => { | ||
/** | ||
* Can be undefined, on the first call, | ||
* because there was no previous one | ||
*/ | ||
if (previousFrameTimeElapsed) { | ||
setMs(previousFrameTimeElapsed); | ||
} | ||
}); | ||
}, [addListener]); | ||
|
||
return ( | ||
<> | ||
<p>ms elapsed from 1970 on previous browser frame: {ms}</p> | ||
</> | ||
); | ||
}; | ||
``` | ||
|
||
--- | ||
|
||
## Start and stop tracking your function | ||
|
||
You can stop and start tracking again whenever you want. | ||
|
||
[Try it on codesandbox](https://codesandbox.io/s/controllable-timer-292wmz) | ||
|
||
<em>[Btw, compare the above performance with `setInterval`. You couldn't achieve same smoothness when event loop is busy](https://codesandbox.io/s/interval-vs-animation-frame-065es8)</em> | ||
|
||
```tsx | ||
import React, { useEffect, useState } from "react"; | ||
import { useListenOnAnimationFrame } from "use-listen-on-animation-frame"; | ||
|
||
const formatDate = (date: Date) => { | ||
return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`; | ||
}; | ||
|
||
const getDate = () => { | ||
return new Date(); | ||
}; | ||
|
||
export const ExtremelySmoothTimer: React.FC = () => { | ||
const [currentTime, setCurrentTime] = useState<string>( | ||
formatDate(new Date()) | ||
); | ||
const [isTicking, setIsTicking] = useState(true); | ||
|
||
const [addListener, , stop, start] = useListenOnAnimationFrame(getDate, { | ||
/** | ||
* optionally indicate that the getDate function and | ||
* listeners should not be invoked until you `start()` | ||
*/ | ||
autoStart: false, | ||
}); | ||
|
||
useEffect(() => { | ||
addListener((date) => { | ||
setCurrentTime(formatDate(date)); | ||
}); | ||
}, [addListener]); | ||
|
||
useEffect(() => { | ||
if (isTicking) { | ||
/* start tracking getDate & listeners */ | ||
start(); | ||
} else { | ||
/* stop tracking getDate & listeners */ | ||
stop(); | ||
} | ||
}, [isTicking, start, stop]); | ||
|
||
return ( | ||
<> | ||
<div>Smooth timer: {currentTime}</div> | ||
<div> | ||
{!isTicking && ( | ||
<button | ||
onClick={() => { | ||
setIsTicking(true); | ||
}} | ||
> | ||
Start timer | ||
</button> | ||
)} | ||
{isTicking && ( | ||
<button | ||
onClick={() => { | ||
setIsTicking(false); | ||
}} | ||
> | ||
Stop timer | ||
</button> | ||
)} | ||
</div> | ||
</> | ||
); | ||
}; | ||
``` | ||
|
||
--- | ||
|
||
## Optimize/Unoptimize your listeners | ||
|
||
By default, if you don't provide [`shouldInvokeListeners` option](#optionsshouldinvokelisteners) - listeners will be invoked only if tracked function return changes. It means that a supplied function will still be invoked on every animation frame, but listeners will not. | ||
|
||
[Try it on codesandbox](https://codesandbox.io/s/player-timer-heavy-load-yqz79q) | ||
|
||
```tsx | ||
import React, { useCallback, useEffect, useState, useRef } from "react"; | ||
import { useListenOnAnimationFrame } from "use-listen-on-animation-frame"; | ||
|
||
const conditionallyInvokeListeners = ( | ||
nextValue: number, | ||
_previousValue: number /* previous animation frame value */ | ||
) => { | ||
/* defaults to return nextValue !== previousValue */ | ||
|
||
/** | ||
* invoke only if current animation | ||
* frame current time is bigger than 2 | ||
* second AND lesser than 3 seconds | ||
*/ | ||
return nextValue > 2 && nextValue < 3; | ||
}; | ||
|
||
const alwaysInvokeListeners = () => { | ||
/** | ||
* usually you shouldn't do this, as we try to cut | ||
* performance costs, we don't want to invoke a bunch | ||
* of functions even if tracked function return hasn't changed | ||
* | ||
* Listeners will be invoked even if player current time hasn't changed | ||
*/ | ||
return true; | ||
}; | ||
|
||
const VideoWithCurrentTime: React.FC = () => { | ||
const [videoCurrentTime, setVideoCurrentTime] = useState<number>(0); | ||
const videoRef = useRef<HTMLVideoElement>(null); | ||
|
||
/* better memoized */ | ||
const getVideoTime = useCallback(() => { | ||
if (videoRef.current) { | ||
return videoRef.current.currentTime; | ||
} | ||
}, []); | ||
|
||
const [addOptimizedListener] = useListenOnAnimationFrame(getVideoTime, { | ||
shouldInvokeListeners: conditionallyInvokeListeners, | ||
}); | ||
|
||
const [addNotOptimizedListener] = useListenOnAnimationFrame(getVideoTime, { | ||
shouldInvokeListeners: alwaysInvokeListeners, | ||
}); | ||
|
||
useEffect(() => { | ||
addNotOptimizedListener((currentTime) => { | ||
setVideoCurrentTime(currentTime); | ||
}); | ||
}, [addNotOptimizedListener]); | ||
|
||
useEffect(() => { | ||
addOptimizedListener(() => { | ||
/** | ||
* does something heavy only when video current time | ||
* is between 1 and 2 seconds | ||
*/ | ||
for (let i = 0; i < 1000; i++) { | ||
console.log(":)"); | ||
} | ||
|
||
console.clear(); | ||
}); | ||
}, [addOptimizedListener]); | ||
|
||
return ( | ||
<> | ||
<h1>Player with timer & heavy load between 2 and 3s</h1> | ||
<video | ||
controls | ||
src="https://www.w3schools.com/html/mov_bbb.mp4" | ||
ref={videoRef} | ||
/> | ||
<p>Video time is: {videoCurrentTime}</p> | ||
</> | ||
); | ||
}; | ||
``` |
Oops, something went wrong.