UNPKG

@erichsia7/ripple

Version:
164 lines (146 loc) 6.76 kB
export interface RippleElement { id: string; classList: DOMTokenList; appendChild(child: Node): void; removeChild(child: Node): void; } export interface WindowWithDocument extends Window { document: Document; } function supportTouch(): boolean { return 'ontouchstart' in document.documentElement; } function addTo( selector: string, color: string, duration: number, callback?: Function | Function[] ): string { const Allelements = document.querySelectorAll(selector); if (Allelements.length === 0) { throw new Error(`Element was not found.`); } if (typeof callback === 'function') { callback = [callback]; } else { if (!Array.isArray(callback) || callback.length === undefined) { callback = []; } } for (let k = 0; k < Allelements.length; k++) { ripple.__addToSingleElement(Allelements[k], color, duration, callback[k]); } return `Ripple effect was added to ${Allelements.length} element${Allelements.length > 1 ? 's' : ''}.`; } function __addToSingleElement(element: HTMLElement, color: string, duration: number, callback?: Function): void { const eventlistener = supportTouch() ? 'touchstart' : 'mousedown'; element.addEventListener(eventlistener, (event) => { const idchars = '0123456789abcdefghijklmnopqrstuvwxyz'; let ripple_id = ''; for (let i = 0; i < 16; i++) { const idrandomNumber = Math.floor(Math.random() * idchars.length); ripple_id += idchars.substring(idrandomNumber, idrandomNumber + 1); } const scroll_x = document.documentElement.scrollLeft; const scroll_y = document.documentElement.scrollTop; const x = event.pageX; const y = event.pageY; const element_rect = element.getBoundingClientRect(); const element_x = element_rect.x + scroll_x; const element_y = element_rect.y + scroll_y; const element_width = element.clientWidth; const element_height = element.clientHeight; const relative_x = x - element_x; const relative_y = y - element_y; const ripple_size = Math.max(element_width, element_height); const ripple_boundary_x = relative_x - 0.5 * ripple_size; const ripple_boundary_y = relative_y - 0.5 * ripple_size; const distance_top = relative_y - 0; const distance_left = relative_x - 0; const distance_right = element_width - relative_x; const distance_bottom = element_height - relative_y; const distance_top_left_corner = Math.sqrt(Math.pow(distance_top, 2) + Math.pow(distance_left, 2)); const distance_top_right_corner = Math.sqrt(Math.pow(distance_top, 2) + Math.pow(distance_right, 2)); const distance_bottom_left_corner = Math.sqrt(Math.pow(distance_bottom, 2) + Math.pow(distance_left, 2)); const distance_bottom_right_corner = Math.sqrt(Math.pow(distance_bottom, 2) + Math.pow(distance_right, 2)); const ripple_zoom = Math.max( 2, Math.max(distance_top_left_corner, distance_top_right_corner, distance_bottom_left_corner, distance_bottom_right_corner) / (ripple_size / 2) ); let element_position = getComputedStyle(element).getPropertyValue('position'); if (!(element_position === 'absolute') && !(element_position === 'fixed')) { element_position = 'relative'; } const css = `.ripple-element-${ripple_id} {position: ${element_position};overflow: hidden;width: ${element_width}px;height: ${element_height}px;outline: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); -webkit-mask-image: -webkit-radial-gradient(white, black);mask-image: -webkit-radial-gradient(white, black);}.ripple-element-ripple-${ripple_id} {background:${color};width: ${ripple_size}px; height:${ripple_size}px;border-radius: 50%;position: absolute; top: ${ripple_boundary_y}px; left:${ripple_boundary_x}px;transform: scale(0); opacity: 0;animation-duration: ${duration}ms;animation-name: ripple-animation-opacity-${ripple_id},ripple-animation-zoom-${ripple_id};animation-iteration-count: forward;animation-timing-function:linear;-webkit-user-select: none;user-select: none;}@keyframes ripple-animation-opacity-${ripple_id} {0% {opacity: 0.15;}60% {opacity: 0.15;}100% {opacity: 0;} } @keyframes ripple-animation-zoom-${ripple_id} {0% {transform: scale(0.1)} 65% {transform: scale(${ripple_zoom})} 100% {transform: scale(${ripple_zoom})}}`; element.classList.add(`ripple-element-${ripple_id}`); const css_style_element = document.createElement('style'); css_style_element.innerHTML = css; css_style_element.id = `ripple-css-${ripple_id}`; element.appendChild(css_style_element); const ripple_element_ripple = document.createElement('div'); ripple_element_ripple.id = `ripple-element-ripple-${ripple_id}`; ripple_element_ripple.classList.add(`ripple-element-ripple-${ripple_id}`); element.appendChild(ripple_element_ripple); if (typeof callback === 'function') { document.getElementById(`ripple-element-ripple-${ripple_id}`)?.addEventListener( 'animationstart', () => { setTimeout(() => { callback!(); const rippleElement = document.getElementById(`ripple-element-ripple-${ripple_id}`); const cssStyleElement = document.getElementById(`ripple-css-${ripple_id}`); if (rippleElement) { element.classList.remove(`ripple-element-${ripple_id}`); rippleElement.remove(); } if (cssStyleElement) { cssStyleElement.remove(); } }, duration * 1); }, { once: true } ); } else { document.getElementById(`ripple-element-ripple-${ripple_id}`)?.addEventListener( 'animationend', () => { const rippleElement = document.getElementById(`ripple-element-ripple-${ripple_id}`); const cssStyleElement = document.getElementById(`ripple-css-${ripple_id}`); if (rippleElement) { element.classList.remove(`ripple-element-${ripple_id}`); rippleElement.remove(); } if (cssStyleElement) { cssStyleElement.remove(); } }, { once: true } ); setTimeout(() => { const rippleElement = document.getElementById(`ripple-element-ripple-${ripple_id}`); const cssStyleElement = document.getElementById(`ripple-css-${ripple_id}`); if (rippleElement) { element.classList.remove(`ripple-element-${ripple_id}`); rippleElement.remove(); } if (cssStyleElement) { cssStyleElement.remove(); } }, duration + 50); } }); } /* declare global { interface Window { ripple: typeof ripple; } } */ const ripple = { supportTouch, addTo, __addToSingleElement, }; export default ripple;