vue 开辟波纹点击殊效组件

最近在运用 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 参数来从外部掌握它的款式。完成代码以下。

<template>
  <transition name="mu-ripple">
    <div class="mu-circle-ripple" :style="styles"></div>
  </transition>
</template>

<script>
import {merge} from '../utils'
export default {
  props: {
    mergeStyle: {
      type: Object,
      default () {
        return {}
      }
    },
    color: {
      type: String,
      default: ''
    },
    opacity: {
      type: Number
    }
  },
  computed: {
    styles () {
      return merge({}, {color: this.color, opacity: this.opacity}, this.mergeStyle)
    }
  }
}
</script>

<style lang="less">
@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;
}
</style>

vue2 关于动画方面做了比较大的修正,除了把指令换成组件外,它还能够完成更庞杂的动画结果,详细能够看这里 vue2 transition

TouchRipple.vue

TouchRipple 须要掌握 circleRipple 的显现。完成以下内容:

  1. 监听 mousetouch 相干事宜, 掌握 circleRipple 的显现。

  2. 经由过程点击事宜 event 对象, 盘算出 circleRipple 的大小和位置

  3. 假如频仍点击能够涌现多个 circleRipple

起首,基础模板 + 数据模型

<template>
  <!--最外层用div包裹-->
  <div @mousedown="handleMouseDown" @mouseup="end()" @mouseleave="end()" @touchstart="handleTouchStart"  @touchend="end()" @touchcancel="end()">
    <!--外层包裹防备波纹溢出-->
    <div :style="style" ref="holder">
      <!--多个波纹用 v-for 掌握-->
      <circle-ripple :key="ripple.key" :color="ripple.color" :opacity="ripple.opacity" :merge-style="ripple.style" v-for="ripple in ripples"></circle-ripple>
    </div>
    <!--应用slot分发现实内容-->
    <slot></slot>
  </div>
</template>

<script>
import circleRipple from './circleRipple'
export default {
  props: {
    // 是不是从中间散布,设为false会从点击处散布
    centerRipple: {
      type: Boolean,
      default: true
    },
    // 外层包裹的款式
    style: {
      type: Object,
      default () {
        return {
          height: '100%',
          width: '100%',
          position: 'absolute',
          top: '0',
          left: '0',
          overflow: 'hidden'
        }
      }
    },
    // 波纹色彩
    color: {
      type: String,
      default: ''
    },
    // 波纹透明度
    opacity: {
      type: Number
    }
  },
  data () {
    return {
      nextKey: 0, // 纪录下一个波纹元素的key值, 相当于uuid,不设置的话会使动画失效
      ripples: [] // 波纹元素参数数组
    }
  },
  mounted () {
    this.ignoreNextMouseDown = false // 防备既有 touch 又有 mouse点击的状况
  },
  methods: {
    start (event, isRippleTouchGenerated) {
      // 最先波纹结果
    },
    end () {
      // 完毕波纹结果
    },
    handleMouseDown (event) {
      // 监听 鼠标单击
    },
    handleTouchStart (event) {
      // 监听 touchstart 要领
    }
  },
  components: {
    'circle-ripple': circleRipple
  }
}
</script>

最先和完毕波纹结果

增添一个波纹元素只须要在 ripple 增添一个 object 即可,差别的是当须要从点击处扩大时,须要盘算一下波纹元素的大小和位置。

{
  // isRippleTouchGenerated 是不是是touch 事宜最先的
  start (event, isRippleTouchGenerated) {
    // 过滤 touchstart 和 mousedown 同时存在的状况
    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() // 完毕 touch 转动的处置惩罚
  }
}

因为 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) // 启动 touchmove 触发转动处置惩罚
        this.startTime = Date.now()
      }
      this.start(event.touches[0], true)
    }
}

touchmove掌握

当发作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)
    // 滑动范围在 > 6px 完毕波纹点击结果
    if (deltaY > 6 || deltaX > 6) this.end()
  }
}

盘算波纹的位置和大小

须要从点击处散布的波纹结果,须要盘算波纹元素的大小和位置

{
  getRippleStyle (event) {
    let holder = this.$refs.holder
    //  这个要领返回一个矩形对象,包括四个属性:left、top、right和bottom。离别示意元素各边与页面上边和左侧的间隔。
    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掌握波纹中间点和点击点一致
      'margin-left': -max + x + 'px',
      'margin-top': -max + y + 'px'
    }
  }
}

运用

因为 touchRipple 内部都是 position:absolute 规划,运用时,须要在外部加上 position:relative

// listItem.vue
<a :href="href" @mouseenter="hover = true" @mouseleave="hover = false" @touchend="hover = false"
    @touchcancel="hover = false" class="mu-item-wrapper" :class="{'hover': hover}">
    <touch-ripple class="mu-item" :class="{'mu-item-link': link}" :center-ripple="false">
      <div class="mu-item-media">
        <slot name="media"></slot>
      </div>
      <div class="mu-item-content">
        // ...
      </div>
    </touch-ripple>
</a>
<style>

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

末了

到这点击波纹组件就开辟完了, 这些代码自创了 keen-uimaterial-ui 的完成体式格局。

    原文作者:锦鲤
    原文地址: https://segmentfault.com/a/1190000006931367
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞