1.前言
最近关注了一款react的ui库Material-UI,感觉效果不错,有点类似angular的MaterialUI。当我看到它按钮的水波特效时:
我的内心在想:
我也要实现一个!
2.实现
2.1 分析过程
过程还是比较清晰的,连续点击产生水波。但是长按时保持水波散开后的最后状态,但是要注意的是,每个水波是独立的。而且,为了水波动画的流畅,这里使用js的requestAnimationFrame来实现高刷,用scale来实现扩散。
2.2 开始code
接下来开始写代码,这里使用的是vue3.0和typescript,为了方便使用,这里把它写成指令:
const ripple: TRadiumDirective<HTMLElement, boolean> = {
name: 'ripple',
mounted(el, binding) {
},
};
export default ripple;
可以看到这里有一个TRadiumDirective类,实际上这是我自定义的类:
export type TRadiumDirective<T, Y> = Directive<T, Y> & { name: string } & {
[key: string]: unknown;
};
然后这里使用的指令的生命周期为mounted,我们要在它挂载的时候进行一系列初始化操作。然后是确定水波的直径,这个一开始我没有想好,但是后来发现可以用对角线来作为直径是最好的,能保证绝对覆盖dom。然后创建一个dom容器来装水波:
const ripple: TRadiumDirective<HTMLElement, boolean> = {
name: 'ripple',
mounted(el, binding) {
if (binding.value !== undefined && !binding.value) {
return;
}
const rippleContainer = document.createElement('div');
let diameter = 0;
rippleContainer.className = 'ra-ripple';
el.appendChild(rippleContainer);
},
};
export default ripple;
当然容器是absolute定位,用以覆盖对象的同时不对其它元素的定位产生影响,然后是监听容器的mousedown,当鼠标左键点击时,初始化水波:
const ripple: TRadiumDirective<HTMLElement, boolean> = {
name: 'ripple',
mounted(el, binding) {
if (binding.value !== undefined && !binding.value) {
return;
}
const rippleContainer = document.createElement('div');
let diameter = 0;
rippleContainer.className = 'ra-ripple';
el.appendChild(rippleContainer);
const startFadeIn = (event: MouseEvent) => {
if (event.button === 2) {
return;
}
const clientReact = el.getBoundingClientRect();
const rippleEl = document.createElement('div');
diameter = RadiumSqrt(el.clientHeight, el.clientWidth) * 2;
rippleEl.style.top = event.clientY - clientReact.y + 'px';
rippleEl.style.left = event.clientX - clientReact.x + 'px';
setTheRippleEL(rippleEl);
setTheRippleElSize(rippleEl);
rippleContainer.appendChild(rippleEl);
change(rippleEl);
};
function setTheRippleEL(rippleEl: HTMLElement) {
if (isNull(rippleEl.onmouseup)) {
rippleEl.onmouseup = () => {
Reflect.set(rippleEl, 'isMouseUp', true);
};
rippleEl.onmouseout = () => {
Reflect.set(rippleEl, 'isMouseUp', true);
};
}
Reflect.set(rippleEl, 'isMouseUp', false);
rippleEl.style.transition = `opacity ${translationDuration /
1000}s ease-in-out`;
rippleEl.style.transform = 'translate(-50%,-50%) scale(0) ';
rippleEl.classList.add('ra-ripple__item');
}
function setTheRippleElSize(rippleEl: HTMLElement) {
rippleEl.style.height = diameter + 'px';
rippleEl.style.width = diameter + 'px';
}
on(rippleContainer, 'mousedown', startFadeIn);
},
};
export default ripple;
最后是当mouseup和mouseout时,结束这个水波:
const ripple: TRadiumDirective<HTMLElement, boolean> = {
name: 'ripple',
mounted(el, binding) {
if (binding.value !== undefined && !binding.value) {
return;
}
const rippleContainer = document.createElement('div');
let diameter = 0;
rippleContainer.className = 'ra-ripple';
el.appendChild(rippleContainer);
const startFadeIn = (event: MouseEvent) => {
if (event.button === 2) {
return;
}
const clientReact = el.getBoundingClientRect();
const rippleEl = document.createElement('div');
diameter = RadiumSqrt(el.clientHeight, el.clientWidth) * 2;
rippleEl.style.top = event.clientY - clientReact.y + 'px';
rippleEl.style.left = event.clientX - clientReact.x + 'px';
setTheRippleEL(rippleEl);
setTheRippleElSize(rippleEl);
rippleContainer.appendChild(rippleEl);
change(rippleEl);
};
function setTheRippleEL(rippleEl: HTMLElement) {
if (isNull(rippleEl.onmouseup)) {
rippleEl.onmouseup = () => {
Reflect.set(rippleEl, 'isMouseUp', true);
};
rippleEl.onmouseout = () => {
Reflect.set(rippleEl, 'isMouseUp', true);
};
}
Reflect.set(rippleEl, 'isMouseUp', false);
rippleEl.style.transition = `opacity ${translationDuration /
1000}s ease-in-out`;
rippleEl.style.transform = 'translate(-50%,-50%) scale(0) ';
rippleEl.classList.add('ra-ripple__item');
}
function setTheRippleElSize(rippleEl: HTMLElement) {
rippleEl.style.height = diameter + 'px';
rippleEl.style.width = diameter + 'px';
}
function endFadeOut(rippleEl: HTMLElement) {
rippleEl.style.opacity = '0';
const timerSign = setTimeout(() => {
rippleContainer.removeChild(rippleEl);
rippleEl.onmouseup = null;
clearTimeout(timerSign);
}, 400);
}
on(rippleContainer, 'mousedown', startFadeIn);
},
};
export default ripple;
到此水波特效完成,看一下效果:
样式比较简单,就不提了,在theme-brush下的ripple.scss中,需要的请自行查看,gitee地址:gitee.com/Agrement/ra… 。