vue 开发波纹点击特效组件

2,958 阅读3分钟

最近在使用 vue2 做一个新的 material ui 库,波纹点击效果在 material design 中被多次使用到,于是决定把它封装成一个公共的组件,使用时直接调用就好啦。

开发之前的思考

常见的波纹点击效果的实现方式是监听元素的 mousedown 事件,在元素内部创建一个 波纹元素 ,并调整元素的 transform: scale(0);transform: scale(1);, 通过计算点击的位置来设置 波纹元素 的大小和位置,以达到波纹扩散的效果。

我将组件分为两个部分, circleRipple.vueTouchRipple.vue 各自实现不同的功能

  1. circleRipple.vue 波纹扩散组件,完成波纹扩散的效果
  2. TouchRipple.vue 监听 mousetouch 相关事件,控制 circleRipple 的显示,位置。

circleRipple.vue

circleRipple 需要完成波纹扩展的效果,而且可以从外部控制它的大小和位置, 所以利用 vuetransition 动画完成效果, 提供 mergeStylecoloropacity 参数来从外部控制它的样式。实现代码如下。






@import "../styles/import.less";
.mu-circle-ripple{
  position: absolute;
  width: 100%;
  height: 100%;
  left: 0;
  top: 0;
  pointer-events: none;
  user-select: none;
  border-radius: 50%;
  background-color: currentColor;
  background-clip: padding-box;
  opacity: 0.1;
}

.mu-ripple-enter-active, .mu-ripple-leave-active{
  transition: transform 1s @easeOutFunction, opacity 2s @easeOutFunction;
}

.mu-ripple-enter {
  transform: scale(0);
}

.mu-ripple-leave-active{
  opacity: 0 !important;
}


vue2 对于动画方面做了比较大的修改,除了把指令换成组件外,它还可以完成更复杂的动画效果,具体可以看这里 vue2 transition

TouchRipple.vue

TouchRipple 需要控制 circleRipple 的显示。完成以下内容:

  1. 监听 mousetouch 相关事件, 控制 circleRipple 的显示。
  2. 通过点击事件 event 对象, 计算出 circleRipple 的大小和位置
  3. 如果频繁点击可能出现多个 circleRipple

首先,基本模板 + 数据模型




开始和结束波纹效果

增加一个波纹元素只需要在 ripple 增加一个 object 即可,不同的是当需要从点击处扩展时,需要计算一下波纹元素的大小和位置。

{
  start (event, isRippleTouchGenerated) {
    if (this.ignoreNextMouseDown && !isRippleTouchGenerated) {
      this.ignoreNextMouseDown = false
      return
    }
    this.ripples.push({
      key: this.nextKey++, 
      color: this.color,
      opacity: this.opacity,
      style: this.centerRipple ? {} : this.getRippleStyle(event) 
    })
    this.ignoreNextMouseDown = isRippleTouchGenerated
 },
 end () {
   if (this.ripples.length === 0) return
   this.ripples.splice(0, 1) 
   this.stopListeningForScrollAbort() 
  }
  }

因为 vue2 基于 Virtual DOM 的, 所以如果没有 key 在增加一个元素又同时删除一个元素的时候,dom tree并没有发生变化,是不会产生动画效果的。

监听 mousedown 和 touchstart

mousedown 和 touchstart 处理上会有所不同,但都是用来启动波纹效果的, touch涉及到多点点击的问题,我们一般取第一个即可。

{
    handleMouseDown (event) {
      if (event.button === 0) {
        this.start(event, false)
      }
    },
    handleTouchStart (event) {
      event.stopPropagation() 
      if (event.touches) {
        this.startListeningForScrollAbort(event) 
        this.startTime = Date.now()
      }
      this.start(event.touches[0], true)
    }
    }

touchmove控制

当发生touchMove事件是需要判断是否,移动的距离和时间,然后结束小波纹点击小姑

{
  stopListeningForScrollAbort () {
    if (!this.handleMove) this.handleMove = this.handleTouchMove.bind(this)
    document.body.removeEventListener('touchmove', this.handleMove, false)
  },
  startListeningForScrollAbort (event) {
    this.firstTouchY = event.touches[0].clientY
    this.firstTouchX = event.touches[0].clientX
    document.body.addEventListener('touchmove', this.handleMove, false)
  },
  handleTouchMove (event) {
    const timeSinceStart = Math.abs(Date.now() - this.startTime)
    if (timeSinceStart > 300) {
      this.stopListeningForScrollAbort()
      return
    }
    const deltaY = Math.abs(event.touches[0].clientY - this.firstTouchY)
    const deltaX = Math.abs(event.touches[0].clientX - this.firstTouchX)
    if (deltaY > 6 || deltaX > 6) this.end()
  }
  }

计算波纹的位置和大小

需要从点击处扩散的波纹效果,需要计算波纹元素的大小和位置

{
  getRippleStyle (event) {
    let holder = this.$refs.holder
    let rect = holder.getBoundingClientRect() 
    let x = event.offsetX
    let y
    if (x !== undefined) {
      y = event.offsetY
    } else {
      x = event.clientX - rect.left
      y = event.clientY - rect.top
    }
    let max
    if (rect.width === rect.height) {
      max = rect.width * 1.412
    } else {
      max = Math.sqrt(
        (rect.width * rect.width) + (rect.height * rect.height)
      )
    }
    const dim = (max * 2) + 'px'
    return {
      width: dim,
      height: dim,
      'margin-left': -max + x + 'px',
      'margin-top': -max + y + 'px'
    }
  }
  }

使用

由于 touchRipple 内部都是 position:absolute 布局,使用时,需要在外部加上 position:relative

// listItem.vue

    
      
// ...
.mu-item-wrapper { display: block; color: inherit; position: relative; }

最后

到这点击波纹组件就开发完了, 这些代码借鉴了 keen-uimaterial-ui 的实现方式。