| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- <template>
- <view class="like-container">
- <image class="image" @click="handleLike" src="/static/images/like.png" ref="likeBtn"></image>
- <view v-for="(icon, index) in icons" :key="icon.id" class="animated-icon" :style="{
- top: icon.top + 'rpx',
- left: icon.left + 'rpx', // 补充left属性的单位
- fontSize: icon.size + 'rpx',
- color: icon.color,
- opacity: icon.opacity,
- transform: 'translateY(' + icon.y + 'rpx) rotate(' + icon.rotate + 'deg)',
- transition: 'all ' + icon.duration + 'ms ' + icon.delay + 'ms ease-out'
- }">
- {{ icon.symbol }}
- </view>
- </view>
- </template>
- <script>
- export default {
- name: 'DouyinLike',
- props: {
- // 初始点赞数
- initialCount: {
- type: Number,
- default: 0
- },
- // 单次点击生成的图标数量
- iconAmount: {
- type: Number,
- default: 6
- },
- // 图标颜色集合
- iconColors: {
- type: Array,
- default: () => [
- '#ff6082', '#ffb92b', '#ffaefb',
- '#87f37f', '#6eb9ff', '#6d86d6',
- '#ab71de', '#ff4b84'
- ]
- },
- // 图标大小范围
- sizeRange: {
- type: Array,
- default: () => [40, 78]
- },
- // 动画持续时间范围(ms)
- durationRange: {
- type: Array,
- default: () => [700, 1300]
- },
- // 上升距离范围(px)
- riseRange: {
- type: Array,
- default: () => [200, 420]
- },
- // 左右飘动范围(px)
- floatRange: {
- type: Array,
- default: () => [-50, 50]
- }
- },
- data() {
- return {
- // 当前点赞数
- count: this.initialCount,
- // 是否已点赞
- isLiked: false,
- // 动画图标数组
- icons: [],
- // 按钮位置信息
- btnRect: null,
- // 可使用的图标集合
- iconSymbols: ['❤', '★', '🎁', '🔥', '👍', '✨', '💖', '🌟'],
- // 用于生成唯一ID
- iconId: 0
- };
- },
- mounted() {
- // 获取按钮位置信息
- this.getBtnRect();
- },
- methods: {
- /**
- * 获取按钮位置信息
- */
- getBtnRect() {
- const query = uni.createSelectorQuery().in(this);
- query.select('.image').boundingClientRect(data => {
- this.btnRect = data;
- }).exec();
- },
- /**
- * 处理点击事件
- */
- handleLike(e) {
- // 更新点赞状态
- this.isLiked = !this.isLiked;
- this.count += this.isLiked ? 1 : -1;
- this.$emit('change', {
- isLiked: this.isLiked,
- count: this.count
- });
- // 生成图标动画
- this.createIcons(e);
- },
- /**
- * 创建多个图标动画
- */
- createIcons(e) {
- if (!this.btnRect) {
- this.getBtnRect();
- return;
- }
- // 获取点击位置(相对于按钮)
- const rect = this.btnRect;
- const x = e.detail.x - rect.left;
- const y = e.detail.y - rect.top;
- // 生成多个图标,带轻微延迟形成连续效果
- for (let i = 0; i < this.iconAmount; i++) {
- this.createIcon(x, y, i * 40);
- }
- },
- /**
- * 创建单个图标动画
- * @param {Number} x 初始X坐标
- * @param {Number} y 初始Y坐标
- * @param {Number} delay 延迟时间(ms)
- */
- createIcon(x, y, delay) {
- // 随机属性
- const size = this.getRandom(...this.sizeRange);
- const color = this.iconColors[Math.floor(Math.random() * this.iconColors.length)];
- const duration = this.getRandom(...this.durationRange);
- const riseDistance = -this.getRandom(...this.riseRange);
- const floatOffset = this.getRandom(...this.floatRange);
- const rotate = this.getRandom(-30, 30);
- // 随机选择一个图标
- const symbol = this.iconSymbols[Math.floor(Math.random() * this.iconSymbols.length)];
- // 生成唯一ID
- const iconId = this.iconId++;
- // 创建图标对象
- const icon = {
- id: iconId,
- left: x,
- top: y,
- size,
- color,
- opacity: 1,
- y: 0,
- rotate,
- duration,
- delay,
- symbol
- };
- // 添加到数组
- this.icons.push(icon);
- // 触发动画
- setTimeout(() => {
- // 使用Vue的$set方法确保响应式更新
- this.$set(this.icons, this.icons.findIndex(item => item.id === iconId), {
- ...this.icons.find(item => item.id === iconId),
- y: riseDistance,
- left: x + floatOffset,
- opacity: 0
- });
- }, delay);
- // 动画结束后移除
- setTimeout(() => {
- const index = this.icons.findIndex(item => item.id === iconId);
- if (index !== -1) {
- this.icons.splice(index, 1);
- }
- }, duration + delay);
- },
- /**
- * 生成范围内的随机数
- */
- getRandom(min, max) {
- return min + Math.random() * (max - min);
- }
- }
- };
- </script>
- <style scoped lang="scss">
- .like-container {
- position: relative;
- }
- .image {
- width: 72rpx;
- height: 72rpx;
- }
- .like-icon {
- font-size: 28rpx;
- color: #999;
- transition: all 0.3s ease;
- }
- /* 动画容器 */
- .like-container {
- /* position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- pointer-events: none;
- overflow: visible; */
- position: relative;
- .image {
- width: 72rpx;
- height: 72rpx;
- }
- /* 动画图标样式 */
- .animated-icon {
- position: absolute;
- will-change: transform, opacity, left;
- text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2);
- z-index: 10;
- animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
- }
- /* 点赞按钮动画 */
- @keyframes pulse {
- 0% {
- transform: scale(1);
- }
- 50% {
- transform: scale(1.4);
- }
- 100% {
- transform: scale(1);
- }
- }
- }
- </style>
|