import type { Directive } from 'vue' import './index.less' export interface RippleOptions { event: string transition: number } export interface RippleProto { background?: string zIndex?: string } export type EventType = Event & MouseEvent & TouchEvent const options: RippleOptions = { event: 'mousedown', transition: 400, } const RippleDirective: Directive & RippleProto = { beforeMount: (el: HTMLElement, binding) => { if (binding.value === false) return const bg = el.getAttribute('ripple-background') setProps(Object.keys(binding.modifiers), options) const background = bg || RippleDirective.background const zIndex = RippleDirective.zIndex el.addEventListener(options.event, (event: EventType) => { rippler({ event, el, background, zIndex, }) }) }, updated(el, binding) { if (!binding.value) { el?.clearRipple?.() return } const bg = el.getAttribute('ripple-background') el?.setBackground?.(bg) }, } function rippler({ event, el, zIndex, background }: { event: EventType; el: HTMLElement } & RippleProto) { const targetBorder = Number.parseInt(getComputedStyle(el).borderWidth.replace('px', '')) const clientX = event.clientX || event.touches[0].clientX const clientY = event.clientY || event.touches[0].clientY const rect = el.getBoundingClientRect() const { left, top } = rect const { offsetWidth: width, offsetHeight: height } = el const { transition } = options const dx = clientX - left const dy = clientY - top const maxX = Math.max(dx, width - dx) const maxY = Math.max(dy, height - dy) const style = window.getComputedStyle(el) const radius = Math.sqrt(maxX * maxX + maxY * maxY) const border = targetBorder > 0 ? targetBorder : 0 const ripple = document.createElement('div') const rippleContainer = document.createElement('div') // Styles for ripple ripple.className = 'ripple' Object.assign(ripple.style ?? {}, { marginTop: '0px', marginLeft: '0px', width: '1px', height: '1px', transition: `all ${transition}ms cubic-bezier(0.4, 0, 0.2, 1)`, borderRadius: '50%', pointerEvents: 'none', position: 'relative', zIndex: zIndex ?? '9999', backgroundColor: background ?? 'rgba(0, 0, 0, 0.12)', }) // Styles for rippleContainer rippleContainer.className = 'ripple-container' Object.assign(rippleContainer.style ?? {}, { position: 'absolute', left: `${0 - border}px`, top: `${0 - border}px`, height: '0', width: '0', pointerEvents: 'none', overflow: 'hidden', }) const storedTargetPosition = el.style.position.length > 0 ? el.style.position : getComputedStyle(el).position if (storedTargetPosition !== 'relative') el.style.position = 'relative' rippleContainer.appendChild(ripple) el.appendChild(rippleContainer) Object.assign(ripple.style, { marginTop: `${dy}px`, marginLeft: `${dx}px`, }) const { borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius } = style Object.assign(rippleContainer.style, { width: `${width}px`, height: `${height}px`, direction: 'ltr', borderTopLeftRadius, borderTopRightRadius, borderBottomLeftRadius, borderBottomRightRadius, }) setTimeout(() => { const wh = `${radius * 2}px` Object.assign(ripple.style ?? {}, { width: wh, height: wh, marginLeft: `${dx - radius}px`, marginTop: `${dy - radius}px`, }) }, 0) function clearRipple() { setTimeout(() => { ripple.style.backgroundColor = 'rgba(0, 0, 0, 0)' }, 250) setTimeout(() => { rippleContainer?.parentNode?.removeChild(rippleContainer) }, 850) el.removeEventListener('mouseup', clearRipple, false) el.removeEventListener('mouseleave', clearRipple, false) el.removeEventListener('dragstart', clearRipple, false) setTimeout(() => { let clearPosition = true for (let i = 0; i < el.childNodes.length; i++) { if ((el.childNodes[i] as Recordable).className === 'ripple-container') clearPosition = false } if (clearPosition) el.style.position = storedTargetPosition !== 'static' ? storedTargetPosition : '' }, options.transition + 260) } if (event.type === 'mousedown') { el.addEventListener('mouseup', clearRipple, false) el.addEventListener('mouseleave', clearRipple, false) el.addEventListener('dragstart', clearRipple, false) } else { clearRipple() } ;(el as Recordable).setBackground = (bgColor: string) => { if (!bgColor) return ripple.style.backgroundColor = bgColor } } function setProps(modifiers: Recordable, props: Recordable) { modifiers.forEach((item: Recordable) => { if (isNaN(Number(item))) props.event = item else props.transition = item }) } export default RippleDirective