童年回忆-类加速球的水球加载动画
逛博客的时候看到了这么一个水球效果,整体是一个球形容器,球顶不断有水滴滴落,随着水滴滴落,底部水位不断上升,水面上涨的同时伴随着波动的动画。
难道你就是…360加速小球???
总之就只是一个写起来好玩的东西 (有水文章之嫌疑)
(感谢豆包大人对代码变量名和代码注释的大力支持)
HTML 部分
1 2 3 4 5 6 7
| <div> <div class="wave_holder"> <div class="wave wave1"></div> <div class="wave wave2"></div> <div class="percentage">0%</div> </div> </div>
|
分别是整个球形容器 ,其中的波浪 wave1wave2 ,和加载进度
对于水球内水的波动立体效果 其实是用这里的两个波浪元素的差异实现的(看后面的 CSS 部分)
CSS 部分
水球本体部分
1 2 3 4 5 6 7 8 9
| .wave_holder { position: relative; width: 200px; height: 200px; border-radius: 50%; background: rgba(255, 255, 255, 0.1); overflow: hidden; box-shadow: 00010pxrgba (255, 255, 255, 0.05), 0020pxrgba(108, 92, 231, 0.3); }
|
简单构造了一个圆
这里用 box-shadow 实现了一个 外发光和内阴影 效果 能让整个水球显得更加有质感一点()
波浪部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| .wave { position: absolute; width: 200%; height: 200%; top: 50%; left: -50%; transform-origin: 50% 50%; animation: wave 6s linear infinite; }
.wave1 { background: rgba(108, 92, 231, 0.8); border-radius: 45%; animation-duration: 6s; }
.wave2 { background: rgba(0, 206, 255, 0.6); border-radius: 40%; animation-duration: 8s; top: 60%; }
|
这里通过设置两层波浪颜色的不同和 border-radius 的不同来让这两层波浪有了差异从而实现立体感(也许吧..)
现在就只需要添加波浪的滚动效果了 这里直接给波浪添加旋转动画 通过设置不同的动画速度来实现层次感
1 2 3 4 5 6 7 8
| @keyframes wave { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
加载进度部分
1 2 3 4 5 6 7 8 9 10 11 12
| .percentage { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: black; font-family: sans-serif; font-size: 2rem; font-weight: bold; z-index: 10; text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); }
|
通过设置 z-index 来保证进度始终在最上方
通过设置 text-shadow 来让进度看起来发光()
为了让整个进度加载得更加有趣 决定在这里添加一个水滴元素 通过水滴的滴落来增加加载进度
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| .drop { position: absolute; width: 8px; height: 12px; transform-origin: center; opacity: 0; background: linear-gradient( 180deg, rgba(0, 206, 255, 0.9), rgba(108, 92, 231, 0.9) ); border-radius: 50% 50% 50% 50%/60% 60% 40% 40%; box-shadow: 0 0 5px rgba(255, 255, 255, 0.3); }
|
水滴设置的效果跟之前的设置都差不多
JavaScript 部分
一些基础的逻辑部分这里就省略了(详细可以看最后面的全代码) 这里只写一些在这里学到的东西
随机生成水滴
这里需要处理的一个是水滴出现的时机 和 水滴每次随机出现的位置
水滴出现的时机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let progress = 0; const totalTime = 15000; const interval = 50; const increment = 100 / (totalTime / interval);
const dropIntervalMin = 200; const dropIntervalMax = 300; let nextDropTime = 0;
if (Date.now() > nextDropTime && progress < 100) { createDrop(); nextDropTime = Date.now() + dropIntervalMin + Math.random() * (dropIntervalMax - dropIntervalMin); }
|
水滴每次出现的位置
1 2 3 4
| const x = Math.random() * 80 + 10; drop.style.left = `${x}%`; drop.style.top = "-15px";
|
这里用随机数来设定水滴的水平位置
同时把水滴的竖直位置固定在容器的上方 这样来模拟水滴从最上面掉下来的状态
模拟水滴下落
这里其实就是模拟的重力作用下的掉落(跟我寒假大作业的时候写的子弹下降大差不差 hhh)
1 2 3 4 5 6 7 8 9 10 11
| const height = waveLoader.offsetHeight; const gravity = 0.25; const drag = 0.98; let velocity = 0; let position = -15;
velocity += gravity; velocity *= drag; position += velocity; drop.style.top = `${position}px`;
|
到达水面后的处理
这也是一开始自己没有注意到的一点 即当水滴到达了水面后 应该将其移除掉从而体现出融入水面的感觉
这部分也是跟着源码学习到的
水滴的移除通过设置 opacity 来实现
水滴的移除与否通过比较其位置和水平面的关系来决定
1 2 3 4 5 6 7 8 9 10 11 12 13
| const waterLevel = 100 - progress; const waterY = (waterLevel / 100) * height;
if (position >= waterY - 5) { clearInterval(dropInterval); drop.style.transition = "opacity 0.2s"; drop.style.opacity = "0"; setTimeout(() => waveLoader.removeChild(drop), 300); } }, 16);
|
注意这里设置 position >= waterY - 5 是提前触发 这样能更加自然
动画循环
这也是自己完全没有想到的循环这个过程 这部分都是在源码中学习的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| const loadingInterval = setInterval(() => { progress += increment; progress = Math.min(progress, 100); percentageEl.textContent = `${Math.round(progress)}%`;
const height = 100 - progress; wave1.style.top = `${height}%`; wave2.style.top = `${height + 10}%`;
if (Date.now() > nextDropTime && progress < 100) { createDrop(); nextDropTime = Date.now() + dropIntervalMin + Math.random() * (dropIntervalMax - dropIntervalMin); }
if (progress >= 100) { setTimeout(() => { setTimeout(() => { progress = 0; }, 1500); }, 1000); } }, interval);
|
源码部分
这里像源码一样设置了
loader-container:固定定位的全屏容器,用于覆盖整个页面,z-index: 9999确保层级最高
不得不说确实美观了不少
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>水球进度加载</title> <style> body { margin: 0; padding: 0; overflow: hidden; }
.loader-container { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: #1a1e2d; display: flex; justify-content: center; align-items: center; z-index: 9999; }
.wave_holder { position: relative; width: 200px; height: 200px; border-radius: 50%; background: rgba(255, 255, 255, 0.1); overflow: hidden; box-shadow: 0 0 0 10px rgba(255, 255, 255, 0.05), 0 0 20px rgba(108, 92, 231, 0.3); }
.wave { position: absolute; width: 200%; height: 200%; top: 50%; left: -50%; transform-origin: 50% 50%; animation: wave 6s linear infinite; }
.wave1 { background: rgba(108, 92, 231, 0.8); border-radius: 45%; animation-duration: 6s; }
.wave2 { background: rgba(0, 206, 255, 0.6); border-radius: 40%; animation-duration: 8s; top: 60%; }
.percentage { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: white; font-family: sans-serif; font-size: 2rem; font-weight: bold; z-index: 10; text-shadow: 0 0 10px rgba(255, 255, 255, 0.5); }
@keyframes wave { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.drop { position: absolute; width: 8px; height: 12px; transform-origin: center; opacity: 0; background: linear-gradient( 180deg, rgba(0, 206, 255, 0.9), rgba(108, 92, 231, 0.9) ); border-radius: 50% 50% 50% 50% / 60% 60% 40% 40%; box-shadow: 0 0 5px rgba(255, 255, 255, 0.3); }
.ripple { position: absolute; border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 50%; transform-origin: center; opacity: 0; } </style> </head> <body> <div class="loader-container"> <div class="wave_holder"> <div class="wave wave1"></div> <div class="wave wave2"></div> <div class="percentage">0%</div> </div> </div>
<script> const percentageEl = document.querySelector(".percentage"); const wave1 = document.querySelector(".wave1"); const wave2 = document.querySelector(".wave2"); const waveLoader = document.querySelector(".wave_holder");
let progress = 0; const totalTime = 15000; const interval = 50; const increment = 100 / (totalTime / interval);
const dropIntervalMin = 200; const dropIntervalMax = 300; let nextDropTime = 0;
function createDrop() { const drop = document.createElement("div"); drop.classList.add("drop"); waveLoader.appendChild(drop);
const x = Math.random() * 80 + 10; drop.style.left = `${x}%`; drop.style.top = "-15px";
const height = waveLoader.offsetHeight; const gravity = 0.25; const drag = 0.98; let velocity = 0; let position = -15;
drop.style.opacity = "1";
const waterLevel = 100 - progress; const waterY = (waterLevel / 100) * height;
const dropInterval = setInterval(() => { velocity += gravity; velocity *= drag; position += velocity; drop.style.top = `${position}px`;
if (position >= waterY - 5) { clearInterval(dropInterval); drop.style.transition = "opacity 0.2s"; drop.style.opacity = "0"; setTimeout(() => waveLoader.removeChild(drop), 300); } }, 16); }
const loadingInterval = setInterval(() => { progress += increment; progress = Math.min(progress, 100); percentageEl.textContent = `${Math.round(progress)}%`;
const height = 100 - progress; wave1.style.top = `${height}%`; wave2.style.top = `${height + 10}%`;
if (Date.now() > nextDropTime && progress < 100) { createDrop(); nextDropTime = Date.now() + dropIntervalMin + Math.random() * (dropIntervalMax - dropIntervalMin); }
if (progress >= 100) { setTimeout(() => { setTimeout(() => { progress = 0; }, 1500); }, 1000); } }, interval); </script> </body> </html>
|