vue3 波纹效果

  1 <template>
  2   <div class="container">
  3     <div class="animation" ref="album">
  4       <img 
  5         src="http://p1.music.126.net/DSTg1dR7yKsyGq4IK3NL8A==/109951163046050093.jpg?param=300x300" 
  6         alt="Album cover"
  7       >
  8       <div v-for="(ripple, index) in ripples" 
  9            :key="index" 
 10            class="ripple-circle"
 11            :ref="el => rippleRefs[index] = el">
 12       </div>
 13     </div>
 14     <div id="play" @click="play">播放</div>
 15     <div id="pause" @click="pause">停止</div>
 16   </div>
 17 </template>
 18 
 19 <script setup>
 20 import { ref, onBeforeUnmount } from 'vue';
 21 
 22 const album = ref(null);
 23 const rippleRefs = ref([]);
 24 const ripples = [1, 2]; // 两个波纹元素
 25 const isPlaying = ref(false);
 26 let rotateId = null;
 27 let rippleIds = [];
 28 let currentRotation = 0;
 29 
 30 // 旋转动画
 31 function rotateAnimation() {
 32   const rotate = () => {
 33     currentRotation = (currentRotation + 1) % 360;
 34     album.value.style.transform = `rotate(${currentRotation}deg)`;
 35     rotateId = requestAnimationFrame(rotate);
 36   };
 37   rotate();
 38 }
 39 
 40 // 波纹动画
 41 function rippleAnimation(element, delay) {
 42   let scale = 1;
 43   let opacity = 0;
 44   let startTime = null;
 45   
 46   const ripple = (timestamp) => {
 47     if (!startTime) startTime = timestamp;
 48     const elapsed = timestamp - startTime;
 49     const progress = (elapsed % 2000) / 2000; // 2秒循环
 50     
 51     if (progress < 0.25) {
 52       scale = 1 + progress * 4 * 0.25;
 53       opacity = progress * 4 * 0.1;
 54     } else if (progress < 0.5) {
 55       scale = 1.25 + (progress - 0.25) * 4 * 0.25;
 56       opacity = 0.1 + (progress - 0.25) * 4 * 0.2;
 57     } else if (progress < 0.75) {
 58       scale = 1.5 + (progress - 0.5) * 4 * 0.25;
 59       opacity = 0.3 + (progress - 0.5) * 4 * 0.2;
 60     } else {
 61       scale = 1.75 + (progress - 0.75) * 4 * 0.25;
 62       opacity = 0.5 - (progress - 0.75) * 4 * 0.5;
 63     }
 64     
 65     element.style.transform = `scale(${scale})`;
 66     element.style.opacity = opacity;
 67     
 68     const id = requestAnimationFrame(ripple);
 69     rippleIds.push(id);
 70   };
 71   
 72   setTimeout(() => {
 73     const id = requestAnimationFrame(ripple);
 74     rippleIds.push(id);
 75   }, delay);
 76 }
 77 
 78 // 播放功能
 79 function play() {
 80   if (isPlaying.value) return;
 81   isPlaying.value = true;
 82   
 83   // 移除过渡效果
 84   album.value.style.transition = 'none';
 85   rippleRefs.value.forEach(el => {
 86     el.style.transition = 'none';
 87   });
 88   
 89   // 启动动画
 90   rotateAnimation();
 91   rippleAnimation(rippleRefs.value[0], 0);
 92   rippleAnimation(rippleRefs.value[1], 1000);
 93 }
 94 
 95 // 停止功能
 96 function pause() {
 97   if (!isPlaying.value) return;
 98   isPlaying.value = false;
 99   
100   // 停止所有动画
101   cancelAnimationFrame(rotateId);
102   rippleIds.forEach(id => cancelAnimationFrame(id));
103   rippleIds = [];
104   
105   // 添加平滑过渡效果
106   album.value.style.transition = 'transform 0.5s ease-out';
107   rippleRefs.value.forEach(el => {
108     el.style.transition = 'transform 0.5s ease-out, opacity 0.5s ease-out';
109     el.style.transform = 'scale(1)';
110     el.style.opacity = '0';
111   });
112   
113   // 平滑停止到当前角度
114   album.value.style.transform = `rotate(${currentRotation}deg)`;
115   
116   // 过渡完成后移除过渡效果
117   setTimeout(() => {
118     album.value.style.transition = 'none';
119     rippleRefs.value.forEach(el => {
120       el.style.transition = 'none';
121     });
122   }, 500);
123 }
124 
125 // 组件卸载时清除动画
126 onBeforeUnmount(() => {
127   pause();
128 });
129 </script>
130 
131 <style scoped>
132 .container {
133   position: relative;
134   height: 100vh;
135   background-color: #51c77d;
136   display: flex;
137   flex-direction: column;
138   align-items: center;
139   justify-content: center;
140 }
141 
142 .animation {
143   position: relative;
144   width: 100px;
145   height: 100px;
146   border-radius: 50%;
147   background-color: #fff;
148   border: 3px solid rgba(0,0,0,0.1);
149 }
150 
151 .animation img {
152   display: block;
153   width: 100%;
154   height: 100%;
155   border-radius: 50%;
156 }
157 
158 .ripple-circle {
159   position: absolute;
160   top: 0;
161   left: 0;
162   width: 100%;
163   height: 100%;
164   border: 1px solid #fff;
165   border-radius: 50%;
166   opacity: 0;
167 }
168 
169 #play, #pause {
170   width: 100px;
171   height: 40px;
172   line-height: 40px;
173   text-align: center;
174   border-radius: 4px;
175   color: #fff;
176   border: 1px solid #fff;
177   margin-top: 15px;
178   cursor: pointer;
179   user-select: none;
180   transition: background-color 0.2s;
181 }
182 
183 #play:hover, #pause:hover {
184   background-color: rgba(255, 255, 255, 0.2);
185 }
186 </style>

 

posted @ 2025-11-22 14:48  红色的风  阅读(3)  评论(0)    收藏  举报