点赞动画效果优化1
parent
6db870a52b
commit
a6b9072a3a
|
|
@ -1,426 +0,0 @@
|
|||
<template>
|
||||
<view class="simple-like-container flex-row box-sizing-border-box">
|
||||
<view class="flex-1 pr">
|
||||
<!-- 点赞动画元素 -->
|
||||
<view
|
||||
v-for="(like, index) in like_list"
|
||||
:key="like.id"
|
||||
class="like-item"
|
||||
:ref="'likeItem' + like.id"
|
||||
:style="{
|
||||
left: like.x + 'px',
|
||||
top: like.y + 'px',
|
||||
color: like.color,
|
||||
opacity: like.opacity
|
||||
}"
|
||||
>
|
||||
<!-- 支持图片或自定义图标 -->
|
||||
<image
|
||||
v-if="like.image_src"
|
||||
:src="like.image_src"
|
||||
class="like-image"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<text v-else>{{ like.icon }}</text>
|
||||
</view>
|
||||
|
||||
<!-- 连续点赞数量提示 -->
|
||||
<text
|
||||
v-if="show_like_count && like_count >= 3"
|
||||
class="like-count"
|
||||
ref="likeCount"
|
||||
:style="{
|
||||
left: like_count_position.x + 'px',
|
||||
top: like_count_position.y + 'px',
|
||||
color: like_count_color,
|
||||
opacity: like_count_opacity,
|
||||
transform: 'scale(' + like_count_scale + ')'
|
||||
}"
|
||||
>
|
||||
x {{ like_count }}
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// #ifdef APP-NVUE
|
||||
const animation = uni.requireNativePlugin('animation');
|
||||
// #endif
|
||||
|
||||
const COLORS = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57', '#ff9ff3', '#ff5252', '#66bb6a'];
|
||||
const ICONS = [];
|
||||
|
||||
export default {
|
||||
name: 'SimpleLikeEffect',
|
||||
props: {
|
||||
// 外部传入的自定义图标数组
|
||||
customIcons: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 外部传入的自定义图片数组
|
||||
customImages: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
like_list: [],
|
||||
// 连续点赞相关数据
|
||||
like_count: 0,
|
||||
show_like_count: false,
|
||||
like_count_position: { x: 0, y: 0 },
|
||||
like_count_color: '#ff6b6b',
|
||||
like_count_opacity: 1,
|
||||
like_count_scale: 1,
|
||||
last_like_time: 0,
|
||||
like_count_timer: null,
|
||||
reset_timer: null, // 重置计数的定时器
|
||||
last_click_time: 0 // 防止双击添加多个图标
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 合并默认图标和自定义图标
|
||||
available_icons() {
|
||||
return [...ICONS, ...this.customIcons];
|
||||
},
|
||||
// 合并默认图片和自定义图片
|
||||
available_images() {
|
||||
return this.customImages;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
add_like(event, options = {}) {
|
||||
// 防抖处理,防止双击添加多个图标
|
||||
const now = Date.now();
|
||||
if (now - this.last_click_time < 200) {
|
||||
return;
|
||||
}
|
||||
this.last_click_time = now;
|
||||
|
||||
// 获取点击坐标
|
||||
let x, y;
|
||||
// 尝试多种方式获取坐标
|
||||
if (event.touches && event.touches.length > 0) {
|
||||
x = event.touches[0].pageX;
|
||||
y = event.touches[0].pageY;
|
||||
} else if (event.changedTouches && event.changedTouches.length > 0) {
|
||||
x = event.changedTouches[0].pageX;
|
||||
y = event.changedTouches[0].pageY;
|
||||
} else {
|
||||
x = event.pageX || event.detail?.x || 0;
|
||||
y = event.pageY || event.detail?.y || 0;
|
||||
}
|
||||
|
||||
// 添加随机偏差 (-10px 到 10px)
|
||||
const offset_x = Math.floor(Math.random() * 41) - 10;
|
||||
const offset_y = Math.floor(Math.random() * 41) - 10;
|
||||
|
||||
// 确保图标位置不超过点击位置20px范围
|
||||
const clamped_offset_x = Math.max(-10, Math.min(10, offset_x));
|
||||
const clamped_offset_y = Math.max(-10, Math.min(10, offset_y));
|
||||
|
||||
// 调整后的坐标
|
||||
const adjusted_x = x + clamped_offset_x - 10;
|
||||
const adjusted_y = y + clamped_offset_y - 10;
|
||||
|
||||
// 随机选择图标、图片和颜色
|
||||
let icon, image_src, color;
|
||||
|
||||
// 检查是否提供了外部图片
|
||||
if (this.available_images && this.available_images.length > 0) {
|
||||
// 随机选择图片
|
||||
image_src = this.available_images[Math.floor(Math.random() * this.available_images.length)];
|
||||
} else if (options.image_src) {
|
||||
// 使用传入的图片
|
||||
image_src = options.image_src;
|
||||
}
|
||||
|
||||
// 如果没有图片,则使用图标
|
||||
if (!image_src) {
|
||||
if (options.icon) {
|
||||
// 使用传入的图标
|
||||
icon = options.icon;
|
||||
} else {
|
||||
// 随机选择图标
|
||||
icon = this.available_icons[Math.floor(Math.random() * this.available_icons.length)];
|
||||
}
|
||||
}
|
||||
|
||||
// 随机选择颜色
|
||||
if (options.color) {
|
||||
color = options.color;
|
||||
} else {
|
||||
color = COLORS[Math.floor(Math.random() * COLORS.length)];
|
||||
}
|
||||
|
||||
// 创建点赞元素
|
||||
const new_like = {
|
||||
id: Date.now() + Math.random(), // 使用时间戳+随机数作为ID
|
||||
x: adjusted_x,
|
||||
y: adjusted_y,
|
||||
color: color,
|
||||
icon: icon,
|
||||
image_src: image_src,
|
||||
opacity: 0 // 显式初始化 opacity
|
||||
};
|
||||
// 添加到列表
|
||||
this.like_list.push(new_like);
|
||||
// #ifdef APP-NVUE
|
||||
// 执行点赞动画
|
||||
setTimeout(() => {
|
||||
this.animate_like_item(new_like.id);
|
||||
}, 0);
|
||||
// #endif
|
||||
// 2秒后移除
|
||||
setTimeout(() => {
|
||||
this.remove_like(new_like.id);
|
||||
}, 2000);
|
||||
|
||||
// 处理连续点赞数量提示
|
||||
this.handle_like_count(x, y, color);
|
||||
},
|
||||
|
||||
remove_like(id) {
|
||||
this.like_list = this.like_list.filter(item => item.id !== id);
|
||||
},
|
||||
|
||||
// 处理连续点赞数量提示
|
||||
handle_like_count(x, y, color) {
|
||||
const current_time = Date.now();
|
||||
|
||||
// 清除之前的重置定时器
|
||||
if (this.reset_timer) {
|
||||
clearTimeout(this.reset_timer);
|
||||
this.reset_timer = null;
|
||||
}
|
||||
|
||||
// 清除之前的隐藏定时器
|
||||
if (this.like_count_timer) {
|
||||
clearTimeout(this.like_count_timer);
|
||||
this.like_count_timer = null;
|
||||
}
|
||||
|
||||
// 如果距离上次点赞超过1秒,重置计数
|
||||
if (current_time - this.last_like_time > 1000) {
|
||||
this.like_count = 1;
|
||||
this.show_like_count = false; // 重置时隐藏数量显示
|
||||
} else {
|
||||
// 否则增加计数
|
||||
this.like_count++;
|
||||
}
|
||||
|
||||
// 更新最后点赞时间
|
||||
this.last_like_time = current_time;
|
||||
|
||||
// 固定位置在当前点击位置正上方40px处(避免与图标重叠)
|
||||
this.like_count_position = {
|
||||
x: x, // 水平位置与点击位置对齐
|
||||
y: y - 40 // 垂直位置在点击位置正上方30px(增加10px避免重叠)
|
||||
};
|
||||
this.like_count_color = color;
|
||||
|
||||
// 显示数量提示(只有当数量大于等于3时才显示)
|
||||
if (this.like_count >= 3) {
|
||||
this.show_like_count = true;
|
||||
this.like_count_opacity = 1;
|
||||
this.like_count_scale = 1;
|
||||
|
||||
// 执行动画
|
||||
setTimeout(() => {
|
||||
this.animate_like_count();
|
||||
}, 0)
|
||||
}
|
||||
|
||||
// 设置隐藏定时器(200ms后隐藏,但不重置计数)
|
||||
if (this.like_count >= 3) {
|
||||
this.like_count_timer = setTimeout(() => {
|
||||
this.hide_like_count();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// 设置重置计数的定时器(在隐藏后1秒重置)
|
||||
this.reset_timer = setTimeout(() => {
|
||||
this.like_count = 0;
|
||||
this.show_like_count = false;
|
||||
}, 1200);
|
||||
},
|
||||
|
||||
// 隐藏数量提示
|
||||
hide_like_count() {
|
||||
// #ifdef APP-NVUE
|
||||
const ref = this.$refs['likeCount'];
|
||||
if (ref) {
|
||||
const el = this.is_array(ref) ? ref[0] : ref;
|
||||
if (el) {
|
||||
animation.transition(el, {
|
||||
styles: {
|
||||
opacity: 0,
|
||||
transform: 'scale(0.5) translateY(-10px)'
|
||||
},
|
||||
duration: 500,
|
||||
timingFunction: 'ease-out'
|
||||
}, () => {
|
||||
this.show_like_count = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
this.show_like_count = false;
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 点赞项动画
|
||||
animate_like_item(id) {
|
||||
// #ifdef APP-NVUE
|
||||
const ref = this.$refs['likeItem' + id];
|
||||
if (ref) {
|
||||
const el = this.is_array(ref) ? ref[0] : ref;
|
||||
if (el) {
|
||||
// 先设置初始状态
|
||||
this.like_list = this.like_list.map(item => {
|
||||
if (item.id === id) {
|
||||
item.opacity = 0;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
// 执行显示动画
|
||||
animation.transition(el, {
|
||||
styles: {
|
||||
opacity: 1,
|
||||
transform: 'scale(1.5) rotate(10deg)'
|
||||
},
|
||||
duration: 1000,
|
||||
timingFunction: 'ease-out'
|
||||
}, () => {
|
||||
// 动画完成后执行淡出效果
|
||||
animation.transition(el, {
|
||||
styles: {
|
||||
opacity: 0,
|
||||
transform: 'scale(1) rotate(20deg)'
|
||||
},
|
||||
duration: 1000,
|
||||
timingFunction: 'ease-out'
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
// 在非nvue环境中使用CSS动画
|
||||
this.like_list = this.like_list.map(item => {
|
||||
if (item.id === id) {
|
||||
item.opacity = 1;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
|
||||
// 数量提示动画
|
||||
animate_like_count() {
|
||||
// #ifdef APP-NVUE
|
||||
const ref = this.$refs['likeCount'];
|
||||
if (ref) {
|
||||
const el = this.is_array(ref) ? ref[0] : ref;
|
||||
if (el) {
|
||||
// 先设置初始状态
|
||||
this.like_count_opacity = 1;
|
||||
this.like_count_scale = 1;
|
||||
|
||||
// 执行隐藏动画
|
||||
animation.transition(el, {
|
||||
styles: {
|
||||
opacity: 0,
|
||||
transform: 'scale(0.5) translateY(-10px)'
|
||||
},
|
||||
duration: 500,
|
||||
timingFunction: 'ease-out'
|
||||
});
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
// 更加健壮的数组检查方法
|
||||
is_array(data) {
|
||||
return Array.isArray(data) || data instanceof Array || (data && typeof data === 'object' && data.length !== undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.simple-like-container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
/* #ifndef-nvue */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
/* #endif */
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.like-item {
|
||||
position: absolute;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.like-image {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.like-count {
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
z-index: 10000;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.like-item {
|
||||
opacity: 0;
|
||||
animation: zoomInOut 2s ease-out forwards;
|
||||
}
|
||||
|
||||
.like-count {
|
||||
opacity: 1;
|
||||
animation: shrinkUp 1s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes zoomInOut {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0) rotate(0deg);
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1.5) rotate(10deg);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1) rotate(20deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shrinkUp {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px) scale(0.5);
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
||||
|
|
@ -107,8 +107,8 @@ export default {
|
|||
|
||||
this.lastLikeTime = currentTime;
|
||||
|
||||
if (this.$refs.likeEffect) {
|
||||
this.$refs.likeEffect.add_like(event);
|
||||
if (this.$refs.fullScreenLikeEffect) {
|
||||
this.$refs.fullScreenLikeEffect.add_like(event);
|
||||
}
|
||||
|
||||
if (this.$refs.liveContent) {
|
||||
|
|
@ -153,8 +153,8 @@ export default {
|
|||
this.$refs.likeEffect.add_like(event);
|
||||
}
|
||||
|
||||
if (this.$refs.liveContent) {
|
||||
this.$refs.liveContent.like_click(event);
|
||||
if (this.$refs.fullScreenLikeEffect) {
|
||||
this.$refs.fullScreenLikeEffect.like_click(event);
|
||||
}
|
||||
}
|
||||
this.lastTapTime = currentTime;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
<template v-if="!is_live_ended">
|
||||
<live-content ref="liveContent" :live-config="live_config" :live-show-imgs="like_show_imgs" @live-back="live_back" @handleDoubleClick="handle_double_click" @handleTouchEnd="handle_touch_end"></live-content>
|
||||
<!-- 简化版点赞效果组件 -->
|
||||
<like-effect ref="likeEffect" :custom-images="like_show_imgs" :style="'width:' + windowWidth + 'px;height:' + windowHeight + 'px;'"></like-effect>
|
||||
<full-screen-like-effect ref="fullScreenLikeEffect" :custom-images="like_show_imgs" :style="'width:' + windowWidth + 'px;height:' + windowHeight + 'px;'"></full-screen-like-effect>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="live-ended flex-row align-c jc-c" :style="'width:' + windowWidth + 'px;height:' + windowHeight + 'px;'" @dblclick="handle_double_click" @touchend="handle_touch_end" :data-ignore="true">
|
||||
|
|
@ -24,7 +24,7 @@
|
|||
import liveVideo from './components/video/video.vue';
|
||||
import liveContent from './components/live-content/live-content.vue';
|
||||
// 引入点赞效果组件
|
||||
import LikeEffect from './components/like-effect/like-effect.vue';
|
||||
import fullScreenLikeEffect from './components/full-screen-like-effect/full-screen-like-effect.vue';
|
||||
// 引入混入公共逻辑,避免nvue和vue使用同一套逻辑出现问题
|
||||
import mixins from './mixins/mixins.js';
|
||||
const app = getApp();
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
components: {
|
||||
liveVideo,
|
||||
liveContent,
|
||||
LikeEffect
|
||||
fullScreenLikeEffect
|
||||
},
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<view class="w h" @dblclick="handle_double_click" @touchend="handle_touch_end" :data-ignore="false">
|
||||
<live-video ref="liveVideo" :src="live_config.pull_flv_url || 'http://live-pull-all.shopxo.vip/68f764013572f9240ca7ce6c/shopxo122.m3u8'" @ended="ended" @mutedAutoPlaySuccess="muted_auto_play_success"></live-video>
|
||||
<!-- 简化版点赞效果组件 -->
|
||||
<like-effect ref="likeEffect" :custom-images="like_show_imgs"></like-effect>
|
||||
<full-screen-like-effect ref="fullScreenLikeEffect" :custom-images="like_show_imgs"></full-screen-like-effect>
|
||||
</view>
|
||||
<view v-if="!is_loading" class="live-content pointer-events-none">
|
||||
<template v-if="!is_live_ended">
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
import liveVideo from './components/video/video.vue';
|
||||
import liveContent from './components/live-content/live-content.vue';
|
||||
// 引入点赞效果组件
|
||||
import LikeEffect from './components/like-effect/like-effect.vue';
|
||||
import fullScreenLikeEffect from './components/full-screen-like-effect/full-screen-like-effect.vue';
|
||||
// 引入混入公共逻辑,避免nvue和vue使用同一套逻辑出现问题
|
||||
import mixins from './mixins/mixins.js';
|
||||
const app = getApp();
|
||||
|
|
@ -40,7 +40,7 @@
|
|||
components: {
|
||||
liveVideo,
|
||||
liveContent,
|
||||
LikeEffect
|
||||
fullScreenLikeEffect
|
||||
},
|
||||
mixins: [mixins],
|
||||
data() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue