最近在运用 vue2 做一个新的 material ui 库,波纹点击结果在 material design 中被屡次运用到,因而决议把它封装成一个大众的组件,运用时直接挪用就好啦。
开辟之前的思索
罕见的波纹点击结果的完成体式格局是监听元素的 mousedown 事宜,在元素内部建立一个 波纹元素 ,并调解元素的 transform: scale(0);
到 transform: scale(1);
, 经由过程盘算点击的位置来设置 波纹元素 的大小和位置,以到达波纹散布的结果。
我将组件分为两个部份, circleRipple.vue
和 TouchRipple.vue
各自完成差别的功用
circleRipple.vue
波纹散布组件,完成波纹散布的结果TouchRipple.vue
监听mouse
和touch
相干事宜,掌握circleRipple
的显现,位置。
circleRipple.vue
circleRipple
须要完成波纹扩大的结果,而且能够从外部掌握它的大小和位置, 所以应用 vue
的 transition
动画完成结果, 供应 mergeStyle
、 color
、opacity
参数来从外部掌握它的款式。完成代码以下。
<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
的显现。完成以下内容:
监听
mouse
和touch
相干事宜, 掌握circleRipple
的显现。经由过程点击事宜 event 对象, 盘算出
circleRipple
的大小和位置假如频仍点击能够涌现多个
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-ui 和 material-ui 的完成体式格局。