vr-shopxo-uniapp/pages/plugins/live/pull/components/like-button/like-button.vue

300 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="like-button">
<!-- #ifdef APP-NVUE -->
<!-- 在NVUE环境下使用list容器显示动画元素 -->
<list class="animate-wrap">
<!-- 循环渲染点赞动画元素 -->
<cell class="a-img" v-for="(item,index) in viewList" :key="item.elId" :ref="item.elId" :style="{ 'right': propSite.x || propSite[0] + 'rpx', 'bottom': propSite.y || propSite[1] + 'rpx'}">
<image :style="{ 'width': propImgWidth + 'rpx', 'height': propImgHeight + 'rpx'}" mode="widthFix" :src="item.src" :animation="item.animation"></image>
</cell>
</list>
<!-- #endif -->
<!-- #ifndef APP-NVUE -->
<!-- 在非NVUE环境下使用view容器显示动画元素 -->
<view class="animate-wrap">
<!-- 循环渲染点赞动画元素 -->
<view class="a-img" v-for="(item,index) in viewList" :key="item.elId" :ref="item.elId" :style="{ 'right': propSite.x || propSite[0] + 'rpx', 'bottom': propSite.y || propSite[1] + 'rpx'}">
<image :style="{'width': propImgWidth + 'rpx', 'height': propImgHeight + 'rpx'}" mode="widthFix" :src="item.src" :animation="item.animation"></image>
</view>
</view>
<!-- #endif -->
<!-- 点赞按钮区域 -->
<view class="on-button">
<view ref="likeBtn" id="like-btn" class="el-like-btn" @tap="handleClick">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
/**
* 点赞动画按钮组件
* 实现点击按钮后产生飞出图标动画的效果,常用于直播场景中的点赞功能
*/
export default {
props: {
/**
* 显示图标路径数组
* @type {Array}
* @default []
*/
propShowImgs: { // 显示图标路径
type: Array,
default: () => {
return []
}
},
/**
* 动画持续时间(毫秒)
* @type {Number}
* @default 5000
*/
propDuration: { // 动画效果时间
type: Number,
default: 5000
},
/**
* X轴方向随机幅度范围
* @type {Number}
* @default 50
*/
propRange: { // x 间隔幅度
type: Number,
default: 50
},
/**
* 图标飞行最大高度
* @type {Number}
* @default 200
*/
propHigh: { // 图标高度
type: Number,
default: 200
},
/**
* 组件宽度
* @type {Number|String}
* @default 52
*/
propWidth: { // 图标宽度
type: Number || String,
default: 52
},
/**
* 组件高度
* @type {Number|String}
* @default 52
*/
propHeight: { // 图标高度
type: Number || String,
default: 52
},
/**
* 图标宽度
* @type {Number|String}
* @default 52
*/
propImgWidth: { // 图标宽度
type: Number || String,
default: 52
},
/**
* 图标高度
* @type {Number|String}
* @default 52
*/
propImgHeight: { // 图标高度
type: Number || String,
default: 52
},
/**
* 点击节流时间(毫秒),防止过快点击
* @type {Number}
* @default 200
*/
propThrottle: { // 点击节流 ms
type: Number,
default: 200
},
/**
* 图标出现位置坐标
* 可以是[x, y]数组或{x, y}对象格式
* @type {Array|Object}
* @default [122, 95]
*/
propSite: { // x y 坐标 [x<Number>, y<Number>]
type: Array || Object,
default: () => {
return [122, 95] || {
x: 122,
y: 95
}
}
},
/**
* 是否启用缩放效果,可以设置为数字指定缩放倍数
* @type {Number|Boolean}
* @default false
*/
propLarge: { // 是否缩放冒泡
type: [Number, Boolean],
default: false
},
/**
* 是否单独移除动画元素true时逐个删除false时统一删除
* @type {Boolean}
* @default true
*/
propAlone: {
type: Boolean,
default: true
}
},
data() {
return {
viewList: [], // 渲染元素列表
elId: 0, // 元素渲染ID计数器
oldTime: 0, // 上次点击时间戳,用于节流
timer: null, // 定时器引用
waitDeleteIndex: 0 // 等待删除的元素索引计数
}
},
watch: {},
methods: {
/**
* 处理点击事件,创建并播放点赞动画
* @param {Event} e 点击事件对象
*/
handleClick(e) {
// 函数节流,防止过快点击
let interval = e.timeStamp - this.oldTime
if (interval < this.propThrottle) return null;
this.oldTime = e.timeStamp
let animation = {}
// 创建animate配置
// #ifdef APP-NVUE
animation = weex.requireModule('animation')
// #endif
// 随机选择一个图标
let randomImg = Math.floor(Math.random() * this.propShowImgs.length)
// 构造动画元素数据
let _item = {
elId: 'el_likeicon_' + this.elId, // 生成元素ref
src: this.propShowImgs[randomImg], // 随机图标
animation: animation, // 每个盒子动画
x: Math.ceil(Math.random() * this.propRange), // 方向间距
q: Math.floor(Math.random() * 2), // 随机方向
}
// 计算随机飞行方向和距离
let _abs = ['-', '']
let _dirX = Number(_abs[_item.q] + _item.x) // 随机的方向和间距
let _dirY = this.propHigh - Math.random() * 10
// 添加到渲染列表
this.elId++
this.viewList.push(_item)
// #ifndef APP-NVUE
// 非NVUE环境下创建动画实例
_item.animation = uni.createAnimation({
duration: this.propDuration,
timingFunction: 'ease-out',
})
// 设置动画结束后的处理逻辑
setTimeout(() => {
// 完成后事件回调
this.$emit('finished')
// 根据propAlone属性决定逐个删除还是一起删除
if (this.propAlone) {
this.waitDeleteIndex++
this.onThrottle(this.deleteView, this.propDuration)
return null;
} else {
// 完成动画后在n秒后清空
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.viewList = []
}, this.propDuration)
return null;
}
}, this.propDuration)
// #endif
// 执行动画
setTimeout(() => {
this.$nextTick(() => {
// 确定缩放倍数
let _n = 1
if (this.propLarge) _n = typeof(this.propLarge) === 'number' ? this.propLarge : 2;
// #ifndef APP-NVUE
// 非NVUE环境下的动画实现
_item.animation.translateY(-_dirY).translateX(_dirX).scale(_n, _n).opacity(0).step()
_item.animation = _item.animation.export()
// #endif
// #ifdef APP-NVUE
// NVUE环境下的动画实现
let el = this.$refs[_item.elId][0];
clearTimeout(this.timer)
_item.animation.transition(el, {
styles: {
transform: `translate(${_dirX}rpx, -${_dirY}rpx) scale(${_n}, ${_n}])`,
transformOrigin: 'center center',
opacity: 0
},
duration: this.propDuration, // ms
timingFunction: 'ease-out',
delay: 0 // ms
}, () => {
// 完成后事件回调
this.$emit('finished')
// 根据propAlone属性决定逐个删除还是一起删除
if (this.propAlone) {
this.waitDeleteIndex++
this.onThrottle(this.deleteView, this.propDuration)
return null
} else {
// 完成动画后在n秒后清空
clearTimeout(this.timer)
this.timer = setTimeout(() => {
this.viewList = []
}, this.duration)
}
})
// #endif
})
}, 0)
// 点击立即触发组件事件
this.$emit('handleClick', this.elId)
},
/**
* 删除已完成动画的视图元素
*/
deleteView() {
this.viewList.splice(0, this.waitDeleteIndex)
this.waitDeleteIndex = 0
},
/**
* 节流函数,控制函数执行频率
* @param {Function} fn 需要节流的函数
* @param {Number} delay 延迟时间(毫秒)
*/
onThrottle(fn, delay) {
let verifi = true
return (function() {
if (!verifi) return false;
verifi = false
setTimeout(() => {
fn()
verifi = true
}, delay)
})()
}
}
}
</script>
<style lang="scss" scoped>
/* 动画元素固定定位样式 */
.a-img {
position: absolute;
}
</style>