@erichsia7/ripple
Version:
Add ripple effect to anything
164 lines (146 loc) • 6.76 kB
text/typescript
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;