Skip to content

Instantly share code, notes, and snippets.

@asvetly
Last active July 15, 2022 13:24
Show Gist options
  • Save asvetly/c2e1f37cfb4e06641e2f6b856eed3ae8 to your computer and use it in GitHub Desktop.
Save asvetly/c2e1f37cfb4e06641e2f6b856eed3ae8 to your computer and use it in GitHub Desktop.
import { useSpring } from 'react-spring';
function useBoop({
x = 0,
y = 0,
rotation = 0,
scale = 1,
timing = 150,
springConfig = {
tension: 300,
friction: 10,
},
}) {
const prefersReducedMotion = usePrefersReducedMotion();
const [isBooped, setIsBooped] = React.useState(false);
const style = useSpring({
transform: isBooped
? `translate(${x}px, ${y}px)
rotate(${rotation}deg)
scale(${scale})`
: `translate(0px, 0px)
rotate(0deg)
scale(1)`,
config: springConfig,
});
React.useEffect(() => {
if (!isBooped) {
return;
}
const timeoutId = window.setTimeout(() => {
setIsBooped(false);
}, timing);
return () => {
window.clearTimeout(timeoutId);
};
}, [isBooped]);
const trigger = React.useCallback(() => {
setIsBooped(true);
}, []);
let appliedStyle = prefersReducedMotion ? {} : style;
return [appliedStyle, trigger];
}
/*
This hook tracks the size and position for
a given HTML node. Useful for all sorts of
whimsical effects!
*/
const useBoundingBox = (
ref,
scrollDebounce = 60,
resizeThrottle = 60
) => {
const [box, setBox] = React.useState(null);
React.useEffect(() => {
// Don't think this is possible?
if (!ref.current) {
return;
}
// Wait a sec before calculating the initial value.
window.setTimeout(() => {
setBox(ref.current.getBoundingClientRect());
}, 200);
function update() {
const newBox = ref.current?.getBoundingClientRect();
if (newBox) {
setBox(newBox);
}
}
const handleScroll = debounce(update, scrollDebounce);
const handleResize = throttle(update, resizeThrottle);
window.addEventListener('scroll', handleScroll);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('scroll', handleScroll);
window.removeEventListener('resize', handleResize);
};
}, []);
return box;
};
function useEffectOnChange(callback, deps) {
const hasMounted = React.useRef(false);
React.useEffect(() => {
if (!hasMounted.current) {
hasMounted.current = true;
return;
}
callback();
}, deps);
}
function useInterval(callback, delay) {
const savedCallback = useRef();
useEffect(() => {
savedCallback.current = callback;
});
useEffect(() => {
function tick() {
savedCallback.current();
}
let id = setInterval(tick, delay);
return () => clearInterval(id);
}, [delay]);
}
const useIsInView = (margin="0px") => {
const [isIntersecting, setIntersecting] = useState(false)
const ref = useRef()
useEffect(() => {
const observer = new IntersectionObserver(([ entry ]) => {
setIntersecting(entry.isIntersecting)
}, { rootMargin: margin })
if (ref.current) observer.observe(ref.current)
return () => {
observer.unobserve(ref.current)
}
}, [])
return [ref, isIntersecting]
}
export const useIsMounted = () => {
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true
return () => {
isMounted.current = false;
};
}, [])
return isMounted;
}
function useIsOnscreen(
elementRef,
defaultState = false
) {
const [isOnscreen, setIsOnscreen] = React.useState(defaultState);
React.useEffect(() => {
if (!elementRef.current) {
return null;
}
const observer = new window.IntersectionObserver(
(entries, observer) => {
const [entry] = entries;
setIsOnscreen(entry.intersectionRatio > 0);
}
);
observer.observe(elementRef.current);
return () => {
observer.disconnect();
};
}, [elementRef]);
return isOnscreen;
}
function useLocalStorage(key, initialValue) {
// State to store our value
// Pass initial state function to useState so logic is only executed once
const [storedValue, setStoredValue] = useState(() => {
try {
// Get from local storage by key
const item = window.localStorage.getItem(key);
// Parse stored json or if none return initialValue
return item ? JSON.parse(item) : initialValue;
} catch (error) {
// If error also return initialValue
console.log(error);
return initialValue;
}
});
// Return a wrapped version of useState's setter function that ...
// ... persists the new value to localStorage.
const setValue = (value) => {
try {
// Allow value to be a function so we have same API as useState
const valueToStore =
value instanceof Function ? value(storedValue) : value;
// Save state
setStoredValue(valueToStore);
// Save to local storage
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
// A more advanced implementation would handle the error case
console.log(error);
}
};
return [storedValue, setValue];
}
const useOnKeyPress = (targetKey, onKeyDown, onKeyUp, isDebugging=false) => {
const [isKeyDown, setIsKeyDown] = useState(false)
const onKeyDownLocal = useCallback(e => {
if (isDebugging) console.log("key down", e.key, e.key != targetKey ? "- isn't triggered" : "- is triggered")
if (e.key != targetKey) return
setIsKeyDown(true)
if (typeof onKeyDown != "function") return
onKeyDown(e)
})
const onKeyUpLocal = useCallback(e => {
if (isDebugging) console.log("key up", e.key, e.key != targetKey ? "- isn't triggered" : "- is triggered")
if (e.key != targetKey) return
setIsKeyDown(false)
if (typeof onKeyUp != "function") return
onKeyUp(e)
})
useEffect(() => {
addEventListener('keydown', onKeyDownLocal)
addEventListener('keyup', onKeyUpLocal)
return () => {
removeEventListener('keydown', onKeyDownLocal)
removeEventListener('keyup', onKeyUpLocal)
}
}, [])
return isKeyDown
}
const useWindowDimensions = () => {
if (typeof window === 'undefined') {
return { width: undefined, height: undefined, clientWidth: undefined };
}
const [windowDimensions, setWindowDimensions] = React.useState({
width: window.innerWidth,
height: window.innerHeight,
clientWidth: document.documentElement.clientWidth,
});
React.useEffect(() => {
const handleResize = throttle(() => {
setWindowDimensions({
width: window.innerWidth,
height: window.innerHeight,
clientWidth: document.documentElement.clientWidth,
});
}, 250);
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
// For the very first render, for whatever reason, the width of the scrollbars
// is sometimes not taken into account, so we need to wait until after
// the first render to set it.
React.useEffect(() => {
if (windowDimensions.clientWidth !== document.documentElement.clientWidth) {
setWindowDimensions({
width: window.innerWidth,
height: window.innerHeight,
clientWidth: document.documentElement.clientWidth,
});
}
// The whole point is to fix an irregularity on mount, so we only want to
// run this effect on mount.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return windowDimensions;
};
function useWindowSize(delay = 100) {
if (typeof window === 'undefined') {
return { width: 1200, height: 800 };
}
const [windowSize, setWindowSize] = useState({
width: 0,
height: 0,
});
useEffect(() => {
const handleResize = () => setWindowSize({
width: window.innerWidth,
height: window.innerHeight,
});
const debouncedHandleResize = delay ? debounce(handleResize, delay) : handleResize;
debouncedHandleResize();
window.addEventListener('resize', debouncedHandleResize);
return () => {
window.removeEventListener('resize', debouncedHandleResize);
}
}, [delay]);
return [windowSize.width, windowSize.height];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment