水波特效

662 阅读2分钟

1.前言

最近关注了一款react的ui库Material-UI,感觉效果不错,有点类似angular的MaterialUI。当我看到它按钮的水波特效时:

动画.gif

我的内心在想:

u=2558417781,930715056&fm=26&fmt=auto&gp=0.webp

我也要实现一个!

2.实现

2.1 分析过程

无标题.png

过程还是比较清晰的,连续点击产生水波。但是长按时保持水波散开后的最后状态,但是要注意的是,每个水波是独立的。而且,为了水波动画的流畅,这里使用js的requestAnimationFrame来实现高刷,用scale来实现扩散。

2.2 开始code

接下来开始写代码,这里使用的是vue3.0typescript,为了方便使用,这里把它写成指令:

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;

最后是当mouseupmouseout时,结束这个水波:

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;

到此水波特效完成,看一下效果:

1.gif

样式比较简单,就不提了,在theme-brush下的ripple.scss中,需要的请自行查看,gitee地址:gitee.com/Agrement/ra…