tweb/src/components/ripple.ts

219 lines
6.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import findUpClassName from '../helpers/dom/findUpClassName';
import sequentialDom from '../helpers/sequentialDom';
import IS_TOUCH_SUPPORTED from '../environment/touchSupport';
import rootScope from '../lib/rootScope';
import findUpAsChild from '../helpers/dom/findUpAsChild';
import {fastRaf} from '../helpers/schedulers';
import liteMode from '../helpers/liteMode';
let rippleClickId = 0;
export default function ripple(
elem: HTMLElement,
callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(),
onEnd: (id: number) => void = null,
prepend = false,
attachListenerTo = elem
) {
// return;
if(elem.querySelector('.c-ripple')) return;
elem.classList.add('rp');
const r = document.createElement('div');
r.classList.add('c-ripple');
const isSquare = elem.classList.contains('rp-square');
if(isSquare) {
r.classList.add('is-square');
}
elem[prepend ? 'prepend' : 'append'](r);
let handler: () => void;
// let animationEndPromise: Promise<number>;
const drawRipple = (clientX: number, clientY: number) => {
const startTime = Date.now();
const circle = document.createElement('div');
const clickId = rippleClickId++;
// console.log('ripple drawRipple');
// const auto = elem.classList.contains('row-sortable') && !elem.classList.contains('cant-sort');
const auto = false;
const duration = (auto ? .3 : +window.getComputedStyle(r).getPropertyValue('--ripple-duration').replace('s', '')) * 1000;
// console.log('ripple duration', duration);
const _handler = handler = () => {
// handler = () => animationEndPromise.then((duration) => {
// console.log('ripple animation was:', duration);
// const duration = isSquare || mediaSizes.isMobile ? 200 : 700;
// return;
const elapsedTime = Date.now() - startTime;
const cb = () => {
// console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime);
sequentialDom.mutate(() => {
circle.remove();
});
onEnd?.(clickId);
};
if(elapsedTime < duration) {
const delay = Math.max(duration - elapsedTime, duration / 2);
setTimeout(() => circle.classList.add('hiding'), Math.max(delay - duration / 2, 0));
setTimeout(cb, delay);
} else {
circle.classList.add('hiding');
setTimeout(cb, duration / 2);
}
if(!IS_TOUCH_SUPPORTED) {
window.removeEventListener('contextmenu', handler);
window.removeEventListener('mousemove', handler);
}
handler = null;
touchStartFired = false;
};
// });
callback?.(clickId);
/* callback().then((bad) => {
if(bad) {
span.remove();
return;
} */
// console.log('ripple after promise', Date.now() - startTime);
// console.log('ripple tooSlow:', tooSlow);
/* if(tooSlow) {
span.remove();
return;
} */
fastRaf(() => {
if(_handler !== handler) {
return;
}
const rect = r.getBoundingClientRect();
circle.classList.add('c-ripple__circle');
const clickX = clientX - rect.left;
const clickY = clientY - rect.top;
const radius = Math.sqrt((Math.abs(clickY - rect.height / 2) + rect.height / 2) ** 2 + (Math.abs(clickX - rect.width / 2) + rect.width / 2) ** 2);
const size = radius;
// center of circle
const x = clickX - size / 2;
const y = clickY - size / 2;
// console.log('ripple click', offsetFromCenter, size, clickX, clickY);
circle.style.width = circle.style.height = size + 'px';
circle.style.left = x + 'px';
circle.style.top = y + 'px';
// нижний код выполняется с задержкой
/* animationEndPromise = new Promise((resolve) => {
span.addEventListener('animationend', () => {
// 713 -> 700
resolve(((Date.now() - startTime) / 100 | 0) * 100);
}, {once: true});
}); */
// нижний код не всегда включает анимацию ПРИ КЛИКЕ НА ТАЧПАД БЕЗ ТАПТИК ЭНЖИНА
/* span.style.display = 'none';
r.append(span);
duration = +window.getComputedStyle(span).getPropertyValue('animation-duration').replace('s', '') * 1000;
span.style.display = ''; */
r.append(circle);
if(auto) {
// window.addEventListener('mousemove', handler, {once: true, passive: true});
handler();
}
// r.classList.add('active');
// handler();
});
// });
};
const isRippleUnneeded = (e: Event) => {
return e.target !== elem && (
['BUTTON', 'A'].includes((e.target as HTMLElement).tagName) ||
findUpClassName(e.target as HTMLElement, 'c-ripple') !== r
) && (
attachListenerTo === elem ||
!findUpAsChild(e.target as HTMLElement, attachListenerTo)
) && !findUpClassName(e.target, 'checkbox-field');
};
// TODO: rename this variable
let touchStartFired = false;
if(IS_TOUCH_SUPPORTED) {
const touchEnd = () => {
handler?.();
};
attachListenerTo.addEventListener('touchstart', (e) => {
if(!liteMode.isAvailable('animations')) {
return;
}
// console.log('ripple touchstart', e);
if(e.touches.length > 1 || touchStartFired || isRippleUnneeded(e)) {
return;
}
// console.log('touchstart', e);
touchStartFired = true;
const {clientX, clientY} = e.touches[0];
drawRipple(clientX, clientY);
attachListenerTo.addEventListener('touchend', touchEnd, {once: true});
window.addEventListener('touchmove', (e) => {
e.cancelBubble = true;
e.stopPropagation();
touchEnd();
attachListenerTo.removeEventListener('touchend', touchEnd);
}, {once: true});
}, {passive: true});
} else {
attachListenerTo.addEventListener('mousedown', (e) => {
if(![0, 2].includes(e.button)) { // only left and right buttons
return;
}
if(!liteMode.isAvailable('animations')) {
return;
}
// console.log('ripple mousedown', e, e.target, findUpClassName(e.target as HTMLElement, 'c-ripple') === r);
if(attachListenerTo.dataset.ripple === '0' || isRippleUnneeded(e)) {
return;
} else if(touchStartFired) {
touchStartFired = false;
return;
}
const {clientX, clientY} = e;
drawRipple(clientX, clientY);
window.addEventListener('mouseup', handler, {once: true, passive: true});
window.addEventListener('contextmenu', handler, {once: true, passive: true});
}, {passive: true});
}
}