diff --git a/README.md b/README.md index 7d6f877..479ca87 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # useScramble -A lightweight (<1KB), high performance, react-hook for text animations. +A lightweight (1KB), high performance, react-hook for random text animations. The hook receives a set of parameteres that allows you to customize the pace, and style of the animation. @@ -47,22 +47,23 @@ export const App = () => { ## Props -| Property | type | default | range | description | -| ---------------- | --------------- | -------- | ----- | ------------------------------------------------------------------------------------------------ | -| playOnMount | boolean | true | | Skip the animation on the first text input | -| text | string | - | | Text value to scramble to | -| speed | number | 1 | 0-1 | Animation framerate. 1 will redraw 60 times a second. 0 will pause the animation | -| tick | number | 1 | 1-∞ | Frames per tick, combined with `speed`, you can fully control the pace rate | -| step | number | 1 | 1-∞ | Moves the animation `step` characters forward, on every tick | -| scramble | number | 1 | 0-∞ | How many times to randomize each character. A value of 0 will emulate a typewriter effect. | -| seed | number | 1 | 0-∞ | Adds random characters ahead of the animation sequence | -| chance | number | 1 | 0-1 | Chance of scrambling a character, range from 0 to 1, 0 being no chance, and 1 being 100% chance. | -| range | number[] | [65,125] | | Unicode characters range to select random characters from | -| overdrive | boolean, number | true | | Defaults to underscore (95) unicode character, or provide a custom unicode value | -| overflow | boolean | true | | Set to false to always restart the animation from an empty string | -| onAnimationStart | function | - | | callback invoked when the animation starts playing | -| onAnimationFrame | function | - | | callback invoked on every rerender | -| onAnimationEnd | function | - | | callback invoked on when the animation ends | +| Property | type | default | range | description | +| ---------------- | --------------- | -------- | ----- | -------------------------------------------------------------------------------------------------------------------------- | +| playOnMount | boolean | true | | Skip the animation on the first text input | +| text | string | - | | Text value to scramble to | +| speed | number | 1 | 0-1 | Animation framerate. 1 will redraw 60 times a second. 0 will pause the animation | +| tick | number | 1 | 1-∞ | Frames per tick, combined with `speed`, you can fully control the pace rate | +| step | number | 1 | 1-∞ | Moves the animation `step` characters forward, on every tick | +| scramble | number | 1 | 0-∞ | How many times to randomize each character. A value of 0 will emulate a typewriter effect. | +| seed | number | 1 | 0-∞ | Adds random characters ahead of the animation sequence | +| chance | number | 1 | 0-1 | Chance of scrambling a character, range from 0 to 1, 0 being no chance, and 1 being 100% chance. | +| range | number[] | [65,125] | | Unicode characters range to select random characters from | +| overdrive | boolean, number | true | | Defaults to underscore (95) unicode character, or provide a custom unicode value | +| overflow | boolean | true | | Set to false to always restart the animation from an empty string | +| ignore | string[] | [" "] | | Ignore specific characters when animating a string. By default only spaces are ignored, to maintain the shape of the text. | +| onAnimationStart | function | - | | callback invoked when the animation starts playing | +| onAnimationFrame | function | - | | callback invoked on every rerender | +| onAnimationEnd | function | - | | callback invoked on when the animation ends | ## Return Values diff --git a/package.json b/package.json index 38eb405..74ff057 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "randomizer", "yugop" ], - "version": "2.2.9", + "version": "2.2.10", "license": "MIT", "module": "dist/use-scramble.esm.js", "main": "dist/index.js", diff --git a/src/index.ts b/src/index.ts index 19830c2..e5a239e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -64,6 +64,10 @@ export type UseScrambleProps = { * @default 1 */ scramble?: number; + /** + * Characters to avoid scrambling + */ + ignore?: string[]; /** * Unicode character range for scrambler. @@ -120,6 +124,7 @@ export const useScramble = (props: UseScrambleProps) => { onAnimationStart, onAnimationFrame, onAnimationEnd, + ignore = [' '], } = props; const prefersReducedMotion = window.matchMedia( @@ -128,8 +133,7 @@ export const useScramble = (props: UseScrambleProps) => { if (prefersReducedMotion) { step = text.length; - scramble = 0; - seed = 0; + chance = 0; overdrive = false; } @@ -155,6 +159,11 @@ export const useScramble = (props: UseScrambleProps) => { // overdrive control index const overdriveRef = useRef(0); + const setIfNotIgnored = ( + value: string | number | null | number, + replace: string | number | null + ) => (ignore.includes(`${value}`) ? value : replace); + // pick random character ahead in the string, and add them to the randomizer const seedForward = () => { if (scrambleIndexRef.current === text.length) return; @@ -168,12 +177,10 @@ export const useScramble = (props: UseScrambleProps) => { typeof controlRef.current[index] !== 'number' && typeof controlRef.current[index] !== 'undefined' ) { - controlRef.current[index] = - controlRef.current[index] === ' ' - ? ' ' - : getRandomInt(0, 10) > (1 - chance) * 10 - ? scramble || seed - : 0; + controlRef.current[index] = setIfNotIgnored( + controlRef.current[index], + getRandomInt(0, 10) > (1 - chance) * 10 ? scramble || seed : 0 + ); } } }; @@ -186,12 +193,10 @@ export const useScramble = (props: UseScrambleProps) => { const shouldScramble = getRandomInt(0, 10) > (1 - chance) * 10; - controlRef.current[currentIndex] = - text[scrambleIndexRef.current] === ' ' - ? ' ' - : shouldScramble - ? scramble - : 0; + controlRef.current[currentIndex] = setIfNotIgnored( + text[scrambleIndexRef.current], + shouldScramble ? scramble : 0 + ); scrambleIndexRef.current++; } } @@ -205,7 +210,7 @@ export const useScramble = (props: UseScrambleProps) => { for (var i = 0; i < step; i++) { if (controlRef.current.length < text.length) { controlRef.current.push( - text[controlRef.current.length + 1] === ' ' ? ' ' : null + setIfNotIgnored(text[controlRef.current.length + 1], null) ); } } @@ -217,12 +222,10 @@ export const useScramble = (props: UseScrambleProps) => { for (var i = 0; i < step; i++) { const max = Math.max(controlRef.current.length, text.length); if (overdriveRef.current < max) { - controlRef.current[overdriveRef.current] = - text[overdriveRef.current] === ' ' - ? ' ' - : String.fromCharCode( - typeof overdrive === 'boolean' ? 95 : overdrive - ); + controlRef.current[overdriveRef.current] = setIfNotIgnored( + text[overdriveRef.current], + String.fromCharCode(typeof overdrive === 'boolean' ? 95 : overdrive) + ); overdriveRef.current++; } } @@ -313,9 +316,7 @@ export const useScramble = (props: UseScrambleProps) => { // set text nodeRef.current.innerHTML = result; - if (onAnimationFrame) { - onAnimationFrame(result); - } + onAnimationFrame && onAnimationFrame(result); /** * Exit if the result is equal to the input @@ -325,9 +326,8 @@ export const useScramble = (props: UseScrambleProps) => { */ if (result === text) { controlRef.current.splice(text.length, controlRef.current.length); - if (onAnimationEnd) { - onAnimationEnd(); - } + onAnimationEnd && onAnimationEnd(); + cancelAnimationFrame(rafRef.current); } @@ -356,9 +356,7 @@ export const useScramble = (props: UseScrambleProps) => { const play = () => { cancelAnimationFrame(rafRef.current); reset(); - if (onAnimationStart) { - onAnimationStart(); - } + onAnimationStart && onAnimationStart(); rafRef.current = requestAnimationFrame(animate); }; @@ -374,6 +372,9 @@ export const useScramble = (props: UseScrambleProps) => { } else { reset(); } + return () => { + cancelAnimationFrame(rafRef.current); + }; }, [text, overdrive, overflow]); /** @@ -388,7 +389,7 @@ export const useScramble = (props: UseScrambleProps) => { return () => { cancelAnimationFrame(rafRef.current); }; - }, [text, speed, animate]); + }, [animate]); return { ref: nodeRef, replay: play }; };