import type { CSSProperties } from 'vue'; import { onBeforeUnmount, onMounted, Transition, render, defineComponent, shallowRef } from 'vue'; import useState from '../hooks/useState'; import { objectType } from '../type'; import { getTargetWaveColor } from './util'; import wrapperRaf from '../raf'; function validateNum(value: number) { return Number.isNaN(value) ? 0 : value; } export interface WaveEffectProps { className: string; target: HTMLElement; } const WaveEffect = defineComponent({ props: { target: objectType(), className: String, }, setup(props) { const divRef = shallowRef(null); const [color, setWaveColor] = useState(null); const [borderRadius, setBorderRadius] = useState([]); const [left, setLeft] = useState(0); const [top, setTop] = useState(0); const [width, setWidth] = useState(0); const [height, setHeight] = useState(0); const [enabled, setEnabled] = useState(false); function syncPos() { const { target } = props; const nodeStyle = getComputedStyle(target); // Get wave color from target setWaveColor(getTargetWaveColor(target)); const isStatic = nodeStyle.position === 'static'; // Rect const { borderLeftWidth, borderTopWidth } = nodeStyle; setLeft(isStatic ? target.offsetLeft : validateNum(-parseFloat(borderLeftWidth))); setTop(isStatic ? target.offsetTop : validateNum(-parseFloat(borderTopWidth))); setWidth(target.offsetWidth); setHeight(target.offsetHeight); // Get border radius const { borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius, } = nodeStyle; setBorderRadius( [ borderTopLeftRadius, borderTopRightRadius, borderBottomRightRadius, borderBottomLeftRadius, ].map(radius => validateNum(parseFloat(radius))), ); } // Add resize observer to follow size let resizeObserver: ResizeObserver; let rafId: number; let timeoutId: any; const clear = () => { clearTimeout(timeoutId); wrapperRaf.cancel(rafId); resizeObserver?.disconnect(); }; const removeDom = () => { const holder = divRef.value?.parentElement; if (holder) { render(null, holder); if (holder.parentElement) { holder.parentElement.removeChild(holder); } } }; onMounted(() => { clear(); timeoutId = setTimeout(() => { removeDom(); }, 5000); const { target } = props; if (target) { // We need delay to check position here // since UI may change after click rafId = wrapperRaf(() => { syncPos(); setEnabled(true); }); if (typeof ResizeObserver !== 'undefined') { resizeObserver = new ResizeObserver(syncPos); resizeObserver.observe(target); } } }); onBeforeUnmount(() => { clear(); }); const onTransitionend = (e: TransitionEvent) => { if (e.propertyName === 'opacity') { removeDom(); } }; return () => { if (!enabled.value) { return null; } const waveStyle = { left: `${left.value}px`, top: `${top.value}px`, width: `${width.value}px`, height: `${height.value}px`, borderRadius: borderRadius.value.map(radius => `${radius}px`).join(' '), } as CSSProperties & { [name: string]: number | string; }; if (color) { waveStyle['--wave-color'] = color.value as string; } return (
); }; }, }); function showWaveEffect(node: HTMLElement, className: string) { // Create holder const holder = document.createElement('div'); holder.style.position = 'absolute'; holder.style.left = `0px`; holder.style.top = `0px`; node?.insertBefore(holder, node?.firstChild); render(, holder); return () => { render(null, holder); if (holder.parentElement) { holder.parentElement.removeChild(holder); } }; } export default showWaveEffect;