like.vue 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. <template>
  2. <view class="like-container">
  3. <!-- <image class="image" @click="handleLike" src="https://bjzmky-1323137866.cos.ap-chongqing.myqcloud.com/fs/20251231/dc4eccf12e3b45ec9a2a9b523f42300f.png" ref="likeBtn"></image> -->
  4. <image class="image" @click="handleLike" src="@/static/images/zan1.png" ref="likeBtn"></image>
  5. <view v-for="(icon, index) in icons" :key="icon.id" class="animated-icon" :style="{
  6. top: icon.top + 'rpx',
  7. left: icon.left + 'rpx', // 补充left属性的单位
  8. fontSize: icon.size + 'rpx',
  9. color: icon.color,
  10. opacity: icon.opacity,
  11. transform: 'translateY(' + icon.y + 'rpx) rotate(' + icon.rotate + 'deg)',
  12. transition: 'all ' + icon.duration + 'ms ' + icon.delay + 'ms ease-out'
  13. }">
  14. {{ icon.symbol }}
  15. </view>
  16. </view>
  17. </template>
  18. <script>
  19. export default {
  20. name: 'DouyinLike',
  21. props: {
  22. // 初始点赞数
  23. initialCount: {
  24. type: Number,
  25. default: 0
  26. },
  27. // 单次点击生成的图标数量
  28. iconAmount: {
  29. type: Number,
  30. default: 6
  31. },
  32. // 图标颜色集合
  33. iconColors: {
  34. type: Array,
  35. default: () => [
  36. '#ff798b', '#ffc54f', '#ffaefb',
  37. '#87f37f', '#6eb9ff', '#6d86d6',
  38. '#bb7df5', '#eeffc5'
  39. ]
  40. },
  41. // 图标大小范围
  42. sizeRange: {
  43. type: Array,
  44. default: () => [40, 78]
  45. },
  46. // 动画持续时间范围(ms)
  47. durationRange: {
  48. type: Array,
  49. default: () => [700, 1300]
  50. },
  51. // 上升距离范围(px)
  52. riseRange: {
  53. type: Array,
  54. default: () => [200, 420]
  55. },
  56. // 左右飘动范围(px)
  57. floatRange: {
  58. type: Array,
  59. default: () => [-50, 50]
  60. }
  61. },
  62. data() {
  63. return {
  64. // 当前点赞数
  65. count: this.initialCount,
  66. // 是否已点赞
  67. isLiked: false,
  68. // 动画图标数组
  69. icons: [],
  70. // 按钮位置信息
  71. btnRect: null,
  72. // 可使用的图标集合
  73. iconSymbols: ['❤', '★', '', '', '', '✨', '', ''],
  74. // 用于生成唯一ID
  75. iconId: 0
  76. };
  77. },
  78. mounted() {
  79. // 获取按钮位置信息
  80. this.getBtnRect();
  81. },
  82. methods: {
  83. /**
  84. * 获取按钮位置信息
  85. */
  86. getBtnRect() {
  87. const query = uni.createSelectorQuery().in(this);
  88. query.select('.image').boundingClientRect(data => {
  89. this.btnRect = data;
  90. }).exec();
  91. },
  92. /**
  93. * 处理点击事件
  94. */
  95. handleLike(e) {
  96. // 更新点赞状态
  97. this.isLiked = !this.isLiked;
  98. this.count += this.isLiked ? 1 : -1;
  99. this.$emit('change', {
  100. isLiked: this.isLiked,
  101. count: this.count
  102. });
  103. // 生成图标动画
  104. this.createIcons(e);
  105. },
  106. /**
  107. * 创建多个图标动画
  108. */
  109. createIcons(e) {
  110. if (!this.btnRect) {
  111. this.getBtnRect();
  112. return;
  113. }
  114. // 获取点击位置(相对于按钮)
  115. const rect = this.btnRect;
  116. const x = e.detail.x - rect.left;
  117. const y = e.detail.y - rect.top;
  118. // 生成多个图标,带轻微延迟形成连续效果
  119. for (let i = 0; i < this.iconAmount; i++) {
  120. this.createIcon(x, y, i * 40);
  121. }
  122. },
  123. /**
  124. * 创建单个图标动画
  125. * @param {Number} x 初始X坐标
  126. * @param {Number} y 初始Y坐标
  127. * @param {Number} delay 延迟时间(ms)
  128. */
  129. createIcon(x, y, delay) {
  130. // 随机属性
  131. const size = this.getRandom(...this.sizeRange);
  132. const color = this.iconColors[Math.floor(Math.random() * this.iconColors.length)];
  133. const duration = this.getRandom(...this.durationRange);
  134. const riseDistance = -this.getRandom(...this.riseRange);
  135. const floatOffset = this.getRandom(...this.floatRange);
  136. const rotate = this.getRandom(-30, 30);
  137. // 随机选择一个图标
  138. const symbol = this.iconSymbols[Math.floor(Math.random() * this.iconSymbols.length)];
  139. // 生成唯一ID
  140. const iconId = this.iconId++;
  141. // 创建图标对象
  142. const icon = {
  143. id: iconId,
  144. left: x,
  145. top: y,
  146. size,
  147. color,
  148. opacity: 1,
  149. y: 0,
  150. rotate,
  151. duration,
  152. delay,
  153. symbol
  154. };
  155. // 添加到数组
  156. this.icons.push(icon);
  157. // 触发动画
  158. setTimeout(() => {
  159. // 使用Vue的$set方法确保响应式更新
  160. this.$set(this.icons, this.icons.findIndex(item => item.id === iconId), {
  161. ...this.icons.find(item => item.id === iconId),
  162. y: riseDistance,
  163. left: x + floatOffset,
  164. opacity: 0
  165. });
  166. }, delay);
  167. // 动画结束后移除
  168. setTimeout(() => {
  169. const index = this.icons.findIndex(item => item.id === iconId);
  170. if (index !== -1) {
  171. this.icons.splice(index, 1);
  172. }
  173. }, duration + delay);
  174. },
  175. /**
  176. * 生成范围内的随机数
  177. */
  178. getRandom(min, max) {
  179. return min + Math.random() * (max - min);
  180. }
  181. }
  182. };
  183. </script>
  184. <style scoped lang="scss">
  185. .like-icon {
  186. font-size: 28rpx;
  187. color: #999;
  188. transition: all 0.3s ease;
  189. }
  190. /* 动画容器 */
  191. .like-container {
  192. position: relative;
  193. .image {
  194. width: 88rpx;
  195. height: 88rpx;
  196. }
  197. /* 动画图标样式 */
  198. .animated-icon {
  199. position: absolute;
  200. will-change: transform, opacity, left;
  201. text-shadow: 0 1rpx 2rpx rgba(0, 0, 0, 0.2);
  202. z-index: 10;
  203. animation-timing-function: cubic-bezier(0.2, 0.8, 0.2, 1);
  204. }
  205. /* 点赞按钮动画 */
  206. @keyframes pulse {
  207. 0% {
  208. transform: scale(1);
  209. }
  210. 50% {
  211. transform: scale(1.4);
  212. }
  213. 100% {
  214. transform: scale(1);
  215. }
  216. }
  217. }
  218. </style>