/** * Throttle function */ export const throttle = (func: Function, timeout: number) => { let ready: boolean = true return (...args: any) => { if (!ready) return ready = false func(...args) setTimeout(() => ready = true, timeout) } } /** * Debounce function */ export const debounce = (func: Function, timeout: number) => { let timer: NodeJS.Timeout return (...args: any) => { clearTimeout(timer) timer = setTimeout(() => func(...args), timeout) } } /** * Split text * @description Split a string into words or characters * @returns string[] */ export const splitText = (text: string, mode: string = 'words'): string[] => { // Split by words if (mode === 'words') { const words = text .replace(/\\n/g, '\n') .replace(/\s+/g, m => m.includes('\n') ? '\n ' : ' ') .trim() .split(' ') return words } // Split by chars else if (mode === 'chars') { const chars = Array.from(text).map(char => char === ' ' ? '\xa0' : char) return chars } } /** * Capitalize first letter */ export const capitalizeFirstLetter = (string: string) => { return string[0].toUpperCase() + string.slice(1) } /** * Create a delay */ export const sleep = (milliseconds: number) => { return new Promise(resolve => setTimeout(resolve, milliseconds)) } /** * Linear Interpolation */ export const lerp = (start: number, end: number, amount: number): number => { return (1 - amount) * start + amount * end } /** * Re-maps a number from one range to another * @param value the incoming value to be converted * @param start1 lower bound of the value's current range * @param stop1 upper bound of the value's current range * @param start2 lower bound of the value's target range * @param stop2 upper bound of the value's target range * @param [withinBounds] constrain the value to the newly mapped range * @return remapped number */ export const map = (n: number, start1: number, stop1: number, start2: number, stop2: number, withinBounds: boolean): number => { const value = (n - start1) / (stop1 - start1) * (stop2 - start2) + start2 if (!withinBounds) return value if (start2 < stop2) { return clamp(value, start2, stop2) } else { return clamp(value, stop2, start2) } } /** * Clamp a number */ export const clamp = (num: number, a: number, b: number) => { return Math.max(Math.min(num, Math.max(a, b)), Math.min(a, b)) } /** * Return a random element from an array */ export const getRandomItem = (array: T[]): T => { const randomItemIndex = ~~(array.length * Math.random()) return array[randomItemIndex] } /** * Return random elements from an array */ export const getRandomItems = (array: any[], amount: number): T[] => { const shuffled = Array.from(array).sort(() => 0.5 - Math.random()) return shuffled.slice(0, amount) } /** * Scroll back to top after page transition */ export const scrollToTop = (delay?: number) => { const scroll = () => window.scrollTo(0,0) if (delay && delay > 0) { setTimeout(scroll, delay) } else { scroll() } } /** * Copy mailto links to clipboard and show message */ export const mailtoClipboard = (node: HTMLElement) => { const links = node.querySelectorAll('a[href^="mailto:"]') const clickToCopy = (event) => { let emailAddress = event.currentTarget.href.split('mailto:')[1].split('?')[0] // Copy email to clipboard navigator.clipboard.writeText(emailAddress) // Send event node.dispatchEvent(new CustomEvent('copied', { detail: { email: emailAddress } })) event.preventDefault() } links && links.forEach(link => link.addEventListener('click', clickToCopy, true)) return { destroy () { links && links.forEach(link => link.removeEventListener('click', clickToCopy, true)) } } }