arrow-process_no_1.html 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>288度环形进度条</title>
  7. <style>
  8. * {
  9. margin: 0;
  10. padding: 0;
  11. box-sizing: border-box;
  12. }
  13. body {
  14. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  15. background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
  16. min-height: 100vh;
  17. display: flex;
  18. flex-direction: column;
  19. align-items: center;
  20. justify-content: center;
  21. padding: 20px;
  22. color: #fff;
  23. }
  24. .container {
  25. max-width: 900px;
  26. width: 100%;
  27. text-align: center;
  28. }
  29. h1 {
  30. margin-bottom: 10px;
  31. color: #fff;
  32. font-size: 2.5rem;
  33. text-shadow: 0 2px 10px rgba(255, 255, 255, 0.2);
  34. background: linear-gradient(90deg, #FFC900, #13A6FF, #806EFF, #FF8EEC, #FE4F51);
  35. -webkit-background-clip: text;
  36. -webkit-text-fill-color: transparent;
  37. }
  38. .subtitle {
  39. color: rgba(255, 255, 255, 0.8);
  40. margin-bottom: 30px;
  41. font-size: 1.1rem;
  42. }
  43. .info-section {
  44. background-color: rgba(255, 255, 255, 0.08);
  45. border-radius: 15px;
  46. padding: 20px;
  47. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  48. backdrop-filter: blur(10px);
  49. border: 1px solid rgba(255, 255, 255, 0.1);
  50. margin-bottom: 25px;
  51. }
  52. .info-title {
  53. color: #4cd964;
  54. font-size: 1.3rem;
  55. margin-bottom: 15px;
  56. text-align: center;
  57. }
  58. .info-description {
  59. color: rgba(255, 255, 255, 0.9);
  60. text-align: left;
  61. line-height: 1.6;
  62. }
  63. .angle-info {
  64. display: flex;
  65. justify-content: space-around;
  66. margin-top: 20px;
  67. flex-wrap: wrap;
  68. gap: 15px;
  69. }
  70. .angle-item {
  71. text-align: center;
  72. padding: 15px;
  73. background: rgba(255, 255, 255, 0.05);
  74. border-radius: 8px;
  75. min-width: 150px;
  76. }
  77. .angle-value {
  78. font-size: 1.8rem;
  79. font-weight: bold;
  80. color: #FFC900;
  81. margin-bottom: 5px;
  82. }
  83. .angle-label {
  84. font-size: 0.9rem;
  85. color: rgba(255, 255, 255, 0.7);
  86. }
  87. .progress-container {
  88. display: flex;
  89. flex-direction: column;
  90. align-items: center;
  91. gap: 20px;
  92. margin-bottom: 40px;
  93. }
  94. .progress-wrapper {
  95. background-color: rgba(255, 255, 255, 0.08);
  96. border-radius: 15px;
  97. padding: 30px;
  98. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  99. transition: transform 0.3s ease;
  100. backdrop-filter: blur(10px);
  101. border: 1px solid rgba(255, 255, 255, 0.1);
  102. width: 100%;
  103. max-width: 500px;
  104. /* 关键修改:向左旋转90度 */
  105. transform: rotate(-90deg);
  106. }
  107. .progress-wrapper:hover {
  108. transform: rotate(-90deg) translateY(-5px);
  109. }
  110. canvas {
  111. display: block;
  112. margin: 0 auto;
  113. }
  114. .progress-info {
  115. margin-top: 20px;
  116. text-align: center;
  117. /* 需要反向旋转,让文字保持正常方向 */
  118. transform: rotate(90deg);
  119. }
  120. .progress-percent {
  121. font-size: 2.5rem;
  122. font-weight: bold;
  123. text-shadow: 0 0 20px rgba(255, 255, 255, 0.2);
  124. color: #FFC900;
  125. }
  126. .progress-label {
  127. font-size: 1.1rem;
  128. color: rgba(255, 255, 255, 0.7);
  129. margin-top: 5px;
  130. }
  131. .current-color {
  132. margin-top: 10px;
  133. font-size: 1.2rem;
  134. color: rgba(255, 255, 255, 0.9);
  135. }
  136. .color-indicator {
  137. display: inline-block;
  138. width: 20px;
  139. height: 20px;
  140. border-radius: 50%;
  141. margin-right: 8px;
  142. vertical-align: middle;
  143. border: 2px solid rgba(255, 255, 255, 0.3);
  144. box-shadow: 0 0 10px currentColor;
  145. }
  146. .controls {
  147. background-color: rgba(255, 255, 255, 0.08);
  148. border-radius: 15px;
  149. padding: 30px;
  150. box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
  151. margin-bottom: 30px;
  152. backdrop-filter: blur(10px);
  153. border: 1px solid rgba(255, 255, 255, 0.1);
  154. width: 100%;
  155. }
  156. .slider-container {
  157. margin-bottom: 20px;
  158. text-align: left;
  159. }
  160. label {
  161. display: block;
  162. margin-bottom: 8px;
  163. font-weight: 600;
  164. color: rgba(255, 255, 255, 0.9);
  165. }
  166. input[type="range"] {
  167. width: 100%;
  168. height: 8px;
  169. border-radius: 4px;
  170. background: rgba(255, 255, 255, 0.1);
  171. outline: none;
  172. -webkit-appearance: none;
  173. }
  174. input[type="range"]::-webkit-slider-thumb {
  175. -webkit-appearance: none;
  176. width: 22px;
  177. height: 22px;
  178. border-radius: 50%;
  179. background: linear-gradient(45deg, #FFC900, #FE4F51);
  180. cursor: pointer;
  181. box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
  182. border: 2px solid rgba(255, 255, 255, 0.8);
  183. }
  184. .value-display {
  185. display: flex;
  186. justify-content: space-between;
  187. margin-top: 5px;
  188. font-size: 0.9rem;
  189. color: rgba(255, 255, 255, 0.7);
  190. }
  191. .button-container {
  192. display: flex;
  193. gap: 15px;
  194. justify-content: center;
  195. margin-top: 25px;
  196. flex-wrap: wrap;
  197. }
  198. button {
  199. padding: 12px 25px;
  200. background: linear-gradient(90deg, #FFC900, #FE4F51);
  201. color: white;
  202. border: none;
  203. border-radius: 8px;
  204. font-size: 1rem;
  205. cursor: pointer;
  206. transition: all 0.3s ease;
  207. box-shadow: 0 4px 15px rgba(255, 201, 0, 0.3);
  208. font-weight: 600;
  209. min-width: 120px;
  210. }
  211. button:hover {
  212. transform: translateY(-2px);
  213. box-shadow: 0 6px 20px rgba(255, 201, 0, 0.5);
  214. }
  215. button:active {
  216. transform: translateY(0);
  217. }
  218. .test-buttons {
  219. display: flex;
  220. gap: 10px;
  221. justify-content: center;
  222. margin-top: 20px;
  223. flex-wrap: wrap;
  224. }
  225. .test-btn {
  226. padding: 8px 16px;
  227. background: rgba(255, 255, 255, 0.1);
  228. color: white;
  229. border: 1px solid rgba(255, 255, 255, 0.2);
  230. border-radius: 6px;
  231. font-size: 0.9rem;
  232. cursor: pointer;
  233. transition: all 0.2s ease;
  234. }
  235. .test-btn:hover {
  236. background: rgba(255, 255, 255, 0.2);
  237. transform: translateY(-1px);
  238. }
  239. .visual-guide {
  240. display: flex;
  241. gap: 20px;
  242. margin: 20px 0;
  243. padding: 15px;
  244. background: rgba(0, 0, 0, 0.2);
  245. border-radius: 10px;
  246. justify-content: center;
  247. flex-wrap: wrap;
  248. align-items: center;
  249. }
  250. .guide-item {
  251. text-align: center;
  252. padding: 10px;
  253. }
  254. .guide-canvas {
  255. background: rgba(0, 0, 0, 0.2);
  256. border-radius: 10px;
  257. margin: 0 auto 10px;
  258. }
  259. .guide-label {
  260. font-size: 0.9rem;
  261. color: rgba(255, 255, 255, 0.8);
  262. }
  263. footer {
  264. margin-top: 40px;
  265. color: rgba(255, 255, 255, 0.6);
  266. font-size: 0.9rem;
  267. text-align: center;
  268. }
  269. .angle-display {
  270. display: flex;
  271. justify-content: space-between;
  272. align-items: center;
  273. margin-top: 15px;
  274. padding: 10px;
  275. background: rgba(0, 0, 0, 0.2);
  276. border-radius: 8px;
  277. /* 需要反向旋转,让文字保持正常方向 */
  278. transform: rotate(90deg);
  279. }
  280. .angle-display-item {
  281. text-align: center;
  282. flex: 1;
  283. }
  284. .angle-display-value {
  285. font-size: 1.2rem;
  286. font-weight: bold;
  287. color: #FFC900;
  288. }
  289. .angle-display-label {
  290. font-size: 0.8rem;
  291. color: rgba(255, 255, 255, 0.7);
  292. }
  293. @media (max-width: 768px) {
  294. .progress-wrapper {
  295. max-width: 100%;
  296. }
  297. h1 {
  298. font-size: 2rem;
  299. }
  300. .button-container {
  301. flex-direction: column;
  302. }
  303. button {
  304. min-width: 100%;
  305. }
  306. .visual-guide {
  307. flex-direction: column;
  308. gap: 15px;
  309. }
  310. }
  311. </style>
  312. </head>
  313. <body>
  314. <div class="container">
  315. <h1>对称288度环形进度条</h1>
  316. <p class="subtitle">进度条从-144度(7点钟方向)开始,到144度(5点钟方向)结束</p>
  317. <div class="info-section">
  318. <div class="info-title">角度配置说明</div>
  319. <div class="info-description">
  320. <p><strong>总角度范围</strong>:-144° 到 144°(总计288°,占360°的80%)</p>
  321. <p><strong>起始位置</strong>:-144°(相对于12点钟方向,位于7点钟方向)</p>
  322. <p><strong>结束位置</strong>:144°(相对于12点钟方向,位于5点钟方向)</p>
  323. <p><strong>空白区域</strong>:144° 到 216°(右侧72°空白)</p>
  324. <p><strong>角度计算公式</strong>:角度 = -144° + 进度百分比 × 288° / 100</p>
  325. <p><strong>注意</strong>:整个圆环已向左旋转90度,使0°基准位于12点钟方向</p>
  326. </div>
  327. <div class="angle-info">
  328. <div class="angle-item">
  329. <div class="angle-value">-144°</div>
  330. <div class="angle-label">起始角度</div>
  331. <div class="angle-label">7点钟方向</div>
  332. </div>
  333. <div class="angle-item">
  334. <div class="angle-value">-90°</div>
  335. <div class="angle-label">左中角度</div>
  336. <div class="angle-label">9点钟方向</div>
  337. </div>
  338. <div class="angle-item">
  339. <div class="angle-value">0°</div>
  340. <div class="angle-label">中间角度</div>
  341. <div class="angle-label">12点钟方向</div>
  342. </div>
  343. <div class="angle-item">
  344. <div class="angle-value">90°</div>
  345. <div class="angle-label">右中角度</div>
  346. <div class="angle-label">3点钟方向</div>
  347. </div>
  348. <div class="angle-item">
  349. <div class="angle-value">144°</div>
  350. <div class="angle-label">结束角度</div>
  351. <div class="angle-label">5点钟方向</div>
  352. </div>
  353. </div>
  354. </div>
  355. <div class="visual-guide">
  356. <div class="guide-item">
  357. <canvas id="guideCanvas1" width="120" height="120" class="guide-canvas"></canvas>
  358. <div class="guide-label">完整圆</div>
  359. <div class="guide-label">360°</div>
  360. </div>
  361. <div class="guide-item">
  362. <div style="font-size: 2rem; color: #FFC900;">→</div>
  363. <div class="guide-label">转换为</div>
  364. </div>
  365. <div class="guide-item">
  366. <canvas id="guideCanvas2" width="120" height="120" class="guide-canvas"></canvas>
  367. <div class="guide-label">-144°到144°</div>
  368. <div class="guide-label">288°进度条</div>
  369. </div>
  370. </div>
  371. <div class="progress-container">
  372. <div class="progress-wrapper">
  373. <canvas id="progressCanvas" width="300" height="300"></canvas>
  374. <div class="progress-info">
  375. <div class="progress-percent" id="percentText">0%</div>
  376. <div class="progress-label">当前进度</div>
  377. <div class="current-color">
  378. <span id="colorIndicator" class="color-indicator" style="background-color: transparent;"></span>
  379. <span id="currentColorText">无进度</span>
  380. </div>
  381. </div>
  382. <div class="angle-display">
  383. <div class="angle-display-item">
  384. <div class="angle-display-value" id="startAngle">-144°</div>
  385. <div class="angle-display-label">起始角度 (7点)</div>
  386. </div>
  387. <div class="angle-display-item">
  388. <div class="angle-display-value" id="currentAngle">0°</div>
  389. <div class="angle-display-label">当前角度</div>
  390. </div>
  391. <div class="angle-display-item">
  392. <div class="angle-display-value" id="endAngle">144°</div>
  393. <div class="angle-display-label">结束角度 (5点)</div>
  394. </div>
  395. </div>
  396. </div>
  397. </div>
  398. <div class="controls">
  399. <div class="slider-container">
  400. <label for="progressSlider">进度控制:<span id="currentProgress">0</span>%</label>
  401. <input type="range" id="progressSlider" min="0" max="100" value="0" step="1">
  402. <div class="value-display">
  403. <span>0% (-144°)</span>
  404. <span>100% (144°)</span>
  405. </div>
  406. </div>
  407. <div class="test-buttons">
  408. <button class="test-btn" data-value="0">测试0% (-144°)</button>
  409. <button class="test-btn" data-value="25">测试25% (-72°)</button>
  410. <button class="test-btn" data-value="50">测试50% (0°)</button>
  411. <button class="test-btn" data-value="75">测试75% (72°)</button>
  412. <button class="test-btn" data-value="100">测试100% (144°)</button>
  413. </div>
  414. <div class="button-container">
  415. <button id="animateBtn">动画演示</button>
  416. <button id="resetBtn">重置到0%</button>
  417. <button id="toEndBtn">跳转到144°</button>
  418. <button id="randomBtn">随机进度</button>
  419. </div>
  420. </div>
  421. <footer>
  422. <p>对称288度环形进度条 | 圆环已旋转,0°基准位于12点钟方向</p>
  423. </footer>
  424. </div>
  425. <script>
  426. // 获取Canvas元素和上下文
  427. const canvas = document.getElementById('progressCanvas');
  428. const ctx = canvas.getContext('2d');
  429. // 获取指南Canvas
  430. const guideCanvas1 = document.getElementById('guideCanvas1');
  431. const guideCtx1 = guideCanvas1.getContext('2d');
  432. const guideCanvas2 = document.getElementById('guideCanvas2');
  433. const guideCtx2 = guideCanvas2.getContext('2d');
  434. // 颜色节点配置数组(按照百分比位置)
  435. const colorStops = [
  436. {percent: 0, color: '#FFC900'},
  437. {percent: 11, color: '#FFC900'},
  438. {percent: 32, color: '#13A6FF'},
  439. {percent: 50, color: '#806EFF'},
  440. {percent: 66, color: '#FF8EEC'},
  441. {percent: 89, color: '#FE4F51'},
  442. {percent: 100, color: '#FE4F51'}
  443. ];
  444. // 进度条相关变量
  445. let currentProgress = 0;
  446. let targetProgress = 0;
  447. let animationId = null;
  448. let radius = 120; // 圆环半径
  449. let thickness = 15; // 圆环厚度
  450. // 注意:由于整个圆环已经旋转了-90度,所以这里不需要修改角度计算
  451. // 原有的角度计算逻辑保持不变,CSS旋转会处理0度位置的调整
  452. let startAngleDeg = -144; // 起始角度:-144度(相对于原始坐标系)
  453. let endAngleDeg = 144; // 结束角度:144度(相对于原始坐标系)
  454. let totalAngleDeg = endAngleDeg - startAngleDeg; // 总角度:288度
  455. // 转换为弧度
  456. let startAngleRad = startAngleDeg * Math.PI / 180;
  457. let endAngleRad = endAngleDeg * Math.PI / 180;
  458. let totalAngleRad = totalAngleDeg * Math.PI / 180;
  459. // 空白区域角度(上方)
  460. let blankStartDeg = endAngleDeg;
  461. let blankEndDeg = startAngleDeg + 360;
  462. let blankAngleDeg = blankEndDeg - blankStartDeg; // 72度空白
  463. // 获取DOM元素
  464. const progressSlider = document.getElementById('progressSlider');
  465. const currentProgressSpan = document.getElementById('currentProgress');
  466. const percentText = document.getElementById('percentText');
  467. const currentColorText = document.getElementById('currentColorText');
  468. const colorIndicator = document.getElementById('colorIndicator');
  469. const currentAngle = document.getElementById('currentAngle');
  470. const startAngleDisplay = document.getElementById('startAngle');
  471. const endAngleDisplay = document.getElementById('endAngle');
  472. const animateBtn = document.getElementById('animateBtn');
  473. const resetBtn = document.getElementById('resetBtn');
  474. const toEndBtn = document.getElementById('toEndBtn');
  475. const randomBtn = document.getElementById('randomBtn');
  476. const testButtons = document.querySelectorAll('.test-btn');
  477. // 计算画布中心点
  478. const centerX = canvas.width / 2;
  479. const centerY = canvas.height / 2;
  480. // 指南Canvas中心点
  481. const guideCenterX = guideCanvas1.width / 2;
  482. const guideCenterY = guideCanvas1.height / 2;
  483. const guideRadius = 45;
  484. // 绘制完整圆指南
  485. function drawFullCircleGuide() {
  486. guideCtx1.clearRect(0, 0, guideCanvas1.width, guideCanvas1.height);
  487. // 绘制完整圆
  488. guideCtx1.beginPath();
  489. guideCtx1.arc(guideCenterX, guideCenterY, guideRadius, 0, Math.PI * 2);
  490. guideCtx1.lineWidth = 8;
  491. guideCtx1.strokeStyle = 'rgba(255, 255, 255, 0.1)';
  492. guideCtx1.stroke();
  493. // 绘制-144°和144°标记
  494. const angle1 = -144 * Math.PI / 180;
  495. const angle2 = 144 * Math.PI / 180;
  496. // 标记-144°位置(左下)
  497. const marker1X = guideCenterX + Math.cos(angle1) * guideRadius;
  498. const marker1Y = guideCenterY + Math.sin(angle1) * guideRadius;
  499. guideCtx1.beginPath();
  500. guideCtx1.arc(marker1X, marker1Y, 3, 0, Math.PI * 2);
  501. guideCtx1.fillStyle = '#FFC900';
  502. guideCtx1.fill();
  503. // 标记144°位置(右下)
  504. const marker2X = guideCenterX + Math.cos(angle2) * guideRadius;
  505. const marker2Y = guideCenterY + Math.sin(angle2) * guideRadius;
  506. guideCtx1.beginPath();
  507. guideCtx1.arc(marker2X, marker2Y, 3, 0, Math.PI * 2);
  508. guideCtx1.fillStyle = '#FE4F51';
  509. guideCtx1.fill();
  510. guideCtx1.font = '10px Arial';
  511. guideCtx1.textAlign = 'center';
  512. guideCtx1.fillStyle = 'white';
  513. guideCtx1.fillText('360°', guideCenterX, guideCenterY + 25);
  514. }
  515. // 绘制-144°到144°进度条指南
  516. function drawSymmetricCircleGuide() {
  517. guideCtx2.clearRect(0, 0, guideCanvas2.width, guideCanvas2.height);
  518. // 绘制完整背景圆
  519. guideCtx2.beginPath();
  520. guideCtx2.arc(guideCenterX, guideCenterY, guideRadius, 0, Math.PI * 2);
  521. guideCtx2.lineWidth = 8;
  522. guideCtx2.strokeStyle = 'rgba(255, 255, 255, 0.1)';
  523. guideCtx2.stroke();
  524. // 绘制-144°到144°进度条
  525. const guideStartAngle = startAngleDeg * Math.PI / 180; // -144度
  526. const guideEndAngle = endAngleDeg * Math.PI / 180; // 144度
  527. // 绘制进度条
  528. guideCtx2.beginPath();
  529. guideCtx2.arc(guideCenterX, guideCenterY, guideRadius, guideStartAngle, guideEndAngle);
  530. guideCtx2.lineWidth = 8;
  531. guideCtx2.lineCap = 'round';
  532. guideCtx2.strokeStyle = '#FFC900';
  533. guideCtx2.stroke();
  534. // 标记-144°位置(起点,左下)
  535. const marker1X = guideCenterX + Math.cos(guideStartAngle) * guideRadius;
  536. const marker1Y = guideCenterY + Math.sin(guideStartAngle) * guideRadius;
  537. guideCtx2.beginPath();
  538. guideCtx2.arc(marker1X, marker1Y, 3, 0, Math.PI * 2);
  539. guideCtx2.fillStyle = '#FFC900';
  540. guideCtx2.fill();
  541. // 标记144°位置(终点,右下)
  542. const marker2X = guideCenterX + Math.cos(guideEndAngle) * guideRadius;
  543. const marker2Y = guideCenterY + Math.sin(guideEndAngle) * guideRadius;
  544. guideCtx2.beginPath();
  545. guideCtx2.arc(marker2X, marker2Y, 3, 0, Math.PI * 2);
  546. guideCtx2.fillStyle = '#FE4F51';
  547. guideCtx2.fill();
  548. // 标记空白区域(144°到-144°,经过上方)
  549. guideCtx2.beginPath();
  550. guideCtx2.arc(guideCenterX, guideCenterY, guideRadius - 15, guideEndAngle, guideStartAngle + Math.PI * 2);
  551. guideCtx2.lineWidth = 2;
  552. guideCtx2.strokeStyle = 'rgba(255, 255, 255, 0.3)';
  553. guideCtx2.setLineDash([3, 3]);
  554. guideCtx2.stroke();
  555. guideCtx2.setLineDash([]);
  556. guideCtx2.font = '10px Arial';
  557. guideCtx2.textAlign = 'center';
  558. guideCtx2.fillStyle = 'white';
  559. guideCtx2.fillText('-144° to 144°', guideCenterX, guideCenterY + 25);
  560. }
  561. // 根据百分比获取颜色
  562. function getColorForPercent(percent) {
  563. // 确保百分比在0-100范围内
  564. percent = Math.max(0, Math.min(100, percent));
  565. // 如果进度为0,返回null表示无颜色
  566. if (percent === 0) {
  567. return null;
  568. }
  569. // 找到百分比所在的两个颜色节点
  570. for (let i = 0; i < colorStops.length - 1; i++) {
  571. const startStop = colorStops[i];
  572. const endStop = colorStops[i + 1];
  573. if (percent >= startStop.percent && percent <= endStop.percent) {
  574. // 计算在两个节点之间的比例
  575. const t = (percent - startStop.percent) / (endStop.percent - startStop.percent);
  576. return interpolateColor(startStop.color, endStop.color, t);
  577. }
  578. }
  579. return colorStops[colorStops.length - 1].color;
  580. }
  581. // 颜色插值函数
  582. function interpolateColor(color1, color2, t) {
  583. // 将十六进制颜色转换为RGB
  584. const r1 = parseInt(color1.slice(1, 3), 16);
  585. const g1 = parseInt(color1.slice(3, 5), 16);
  586. const b1 = parseInt(color1.slice(5, 7), 16);
  587. const r2 = parseInt(color2.slice(1, 3), 16);
  588. const g2 = parseInt(color2.slice(3, 5), 16);
  589. const b2 = parseInt(color2.slice(5, 7), 16);
  590. // 线性插值
  591. const r = Math.round(r1 + (r2 - r1) * t);
  592. const g = Math.round(g1 + (g2 - g1) * t);
  593. const b = Math.round(b1 + (b2 - b1) * t);
  594. // 转回十六进制
  595. return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
  596. }
  597. // 根据进度计算角度
  598. function calculateAngle(progress) {
  599. // 将百分比转换为角度 (-144° 到 144°)
  600. return startAngleDeg + (progress / 100) * totalAngleDeg;
  601. }
  602. // 更新UI显示
  603. function updateUIDisplay(progress, color) {
  604. // 计算当前角度
  605. const angle = calculateAngle(progress);
  606. // 更新角度显示
  607. currentAngle.textContent = `${Math.round(angle)}°`;
  608. if (progress === 0 || color === null) {
  609. // 0%进度时,显示"无进度"
  610. currentColorText.textContent = '无进度';
  611. colorIndicator.style.backgroundColor = 'transparent';
  612. colorIndicator.style.boxShadow = 'none';
  613. percentText.style.color = 'rgba(255, 255, 255, 0.7)';
  614. percentText.style.textShadow = 'none';
  615. } else {
  616. // 大于0%进度时,显示对应颜色
  617. currentColorText.textContent = `当前颜色: ${color}`;
  618. colorIndicator.style.backgroundColor = color;
  619. colorIndicator.style.boxShadow = `0 0 15px ${color}`;
  620. percentText.style.color = color;
  621. percentText.style.textShadow = `0 0 20px ${color}`;
  622. }
  623. // 更新进度显示
  624. percentText.textContent = `${Math.round(progress)}%`;
  625. currentProgressSpan.textContent = Math.round(progress);
  626. progressSlider.value = Math.round(progress);
  627. }
  628. // 绘制背景圆环(包含空白区域指示)
  629. function drawBackgroundRing() {
  630. // 绘制完整背景圆
  631. ctx.beginPath();
  632. ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
  633. ctx.lineWidth = thickness;
  634. ctx.lineCap = 'round';
  635. ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
  636. ctx.stroke();
  637. // 绘制空白区域指示线(144°到-144°,经过上方)
  638. const blankStartRad = endAngleDeg * Math.PI / 180;
  639. const blankEndRad = (startAngleDeg + 360) * Math.PI / 180;
  640. ctx.beginPath();
  641. ctx.arc(centerX, centerY, radius - 5, blankStartRad, blankEndRad);
  642. ctx.lineWidth = 2;
  643. ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
  644. ctx.setLineDash([3, 3]);
  645. ctx.stroke();
  646. ctx.setLineDash([]);
  647. }
  648. // 绘制进度文字
  649. function drawProgressText(progress, color) {
  650. // 绘制百分比
  651. ctx.font = 'bold 42px Arial';
  652. ctx.textAlign = 'center';
  653. ctx.textBaseline = 'middle';
  654. if (progress === 0) {
  655. ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
  656. ctx.shadowColor = 'transparent';
  657. } else {
  658. ctx.fillStyle = color;
  659. ctx.shadowColor = color;
  660. ctx.shadowBlur = 15;
  661. }
  662. ctx.fillText(`${Math.round(progress)}%`, centerX, centerY);
  663. // 重置阴影
  664. ctx.shadowColor = 'transparent';
  665. ctx.shadowBlur = 0;
  666. // 添加当前角度标签
  667. ctx.font = '16px Arial';
  668. ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
  669. const angle = calculateAngle(progress);
  670. if (progress === 0) {
  671. ctx.fillText('起始角度: -144°', centerX, centerY + 40);
  672. } else {
  673. ctx.fillText(`当前角度: ${Math.round(angle)}°`, centerX, centerY + 40);
  674. }
  675. }
  676. // 绘制进度条(-144°到144°对称版本)- 修复小进度值问题
  677. function drawProgress(progress) {
  678. // 清空画布
  679. ctx.clearRect(0, 0, canvas.width, canvas.height);
  680. // 绘制背景圆环
  681. drawBackgroundRing();
  682. // 获取当前颜色(用于显示)
  683. const currentColor = progress === 0 ? null : getColorForPercent(progress);
  684. // 只有当进度大于0时才绘制进度条
  685. if (progress > 0) {
  686. // 计算结束角度(-144°到144°范围内)
  687. const endAngle = startAngleRad + (totalAngleRad * progress / 100);
  688. // 当进度非常小时(小于3%),使用简单绘制方法
  689. if (progress < 3) {
  690. // 直接绘制一个很小的圆弧,不使用分段
  691. ctx.beginPath();
  692. ctx.arc(centerX, centerY, radius, startAngleRad, endAngle);
  693. ctx.lineWidth = thickness;
  694. ctx.lineCap = 'round';
  695. // 使用统一的颜色,避免分段导致的绘制异常
  696. ctx.strokeStyle = currentColor;
  697. ctx.stroke();
  698. } else {
  699. // 原有分段绘制逻辑(用于较大进度)
  700. const segmentCount = Math.max(20, Math.floor(progress * 0.5));
  701. const angleRange = endAngle - startAngleRad;
  702. // 分段绘制圆弧
  703. for (let i = 0; i < segmentCount; i++) {
  704. const segmentStart = startAngleRad + (angleRange * i / segmentCount);
  705. const segmentEnd = startAngleRad + (angleRange * (i + 1) / segmentCount);
  706. // 计算这段中心点的百分比
  707. const segmentCenterPercent = (i + 0.5) / segmentCount * progress;
  708. const segmentColor = getColorForPercent(segmentCenterPercent);
  709. ctx.beginPath();
  710. ctx.arc(centerX, centerY, radius, segmentStart, segmentEnd);
  711. ctx.lineWidth = thickness;
  712. ctx.lineCap = 'round';
  713. ctx.strokeStyle = segmentColor;
  714. ctx.stroke();
  715. }
  716. }
  717. // 在144度位置标记终点
  718. if (progress === 100) {
  719. const markerX = centerX + Math.cos(endAngle) * radius;
  720. const markerY = centerY + Math.sin(endAngle) * radius;
  721. ctx.beginPath();
  722. ctx.arc(markerX, markerY, 5, 0, Math.PI * 2);
  723. ctx.fillStyle = '#FE4F51';
  724. ctx.fill();
  725. ctx.strokeStyle = 'white';
  726. ctx.lineWidth = 2;
  727. ctx.stroke();
  728. // 也在-144度位置标记起点
  729. const startMarkerX = centerX + Math.cos(startAngleRad) * radius;
  730. const startMarkerY = centerY + Math.sin(startAngleRad) * radius;
  731. ctx.beginPath();
  732. ctx.arc(startMarkerX, startMarkerY, 5, 0, Math.PI * 2);
  733. ctx.fillStyle = '#FFC900';
  734. ctx.fill();
  735. ctx.strokeStyle = 'white';
  736. ctx.lineWidth = 2;
  737. ctx.stroke();
  738. }
  739. }
  740. // 绘制进度文字
  741. drawProgressText(progress, currentColor);
  742. // 更新UI显示
  743. updateUIDisplay(progress, currentColor);
  744. }
  745. // 动画函数
  746. function animateProgress() {
  747. if (animationId) {
  748. cancelAnimationFrame(animationId);
  749. }
  750. function animate() {
  751. // 使用缓动函数使动画更平滑
  752. const easing = 0.1;
  753. currentProgress += (targetProgress - currentProgress) * easing;
  754. // 如果接近目标值,直接设为目标值
  755. if (Math.abs(targetProgress - currentProgress) < 0.1) {
  756. currentProgress = targetProgress;
  757. } else {
  758. animationId = requestAnimationFrame(animate);
  759. }
  760. // 更新显示
  761. drawProgress(currentProgress);
  762. }
  763. animationId = requestAnimationFrame(animate);
  764. }
  765. // 更新进度
  766. function updateProgress(newProgress) {
  767. targetProgress = Math.max(0, Math.min(100, newProgress));
  768. animateProgress();
  769. }
  770. // 事件监听器
  771. progressSlider.addEventListener('input', function() {
  772. updateProgress(parseInt(this.value));
  773. });
  774. // 测试按钮事件
  775. testButtons.forEach(button => {
  776. button.addEventListener('click', function() {
  777. const value = parseInt(this.getAttribute('data-value'));
  778. updateProgress(value);
  779. });
  780. });
  781. // 动画演示按钮
  782. animateBtn.addEventListener('click', function() {
  783. // 从0到100的动画演示
  784. let progress = 0;
  785. function demoAnimation() {
  786. progress += 0.5; // 调整速度
  787. if (progress >= 100) {
  788. progress = 100;
  789. }
  790. updateProgress(progress);
  791. // 继续动画
  792. if (progress < 100) {
  793. requestAnimationFrame(demoAnimation);
  794. }
  795. }
  796. demoAnimation();
  797. });
  798. // 重置按钮
  799. resetBtn.addEventListener('click', function() {
  800. updateProgress(0);
  801. });
  802. // 跳转到144°按钮
  803. toEndBtn.addEventListener('click', function() {
  804. updateProgress(100); // 100% 对应 144°
  805. });
  806. // 随机进度按钮
  807. randomBtn.addEventListener('click', function() {
  808. const randomProgress = Math.floor(Math.random() * 101);
  809. updateProgress(randomProgress);
  810. });
  811. // 初始化角度显示
  812. startAngleDisplay.textContent = `${startAngleDeg}°`;
  813. endAngleDisplay.textContent = `${endAngleDeg}°`;
  814. // 设置图像平滑
  815. ctx.imageSmoothingEnabled = true;
  816. ctx.imageSmoothingQuality = 'high';
  817. // 初始化绘制
  818. drawProgress(currentProgress);
  819. // 绘制指南
  820. drawFullCircleGuide();
  821. drawSymmetricCircleGuide();
  822. console.log("对称288度环形进度条说明:");
  823. console.log("1. 进度条范围:-144° 到 144°(总计288°,占360°的80%)");
  824. console.log("2. 起始位置:-144°(相对于12点钟方向,位于7点钟方向)");
  825. console.log("3. 结束位置:144°(相对于12点钟方向,位于5点钟方向)");
  826. console.log("4. 空白区域:144° 到 216°(右侧72°空白)");
  827. console.log("5. 整个圆环已向左旋转90度,使0°基准位于12点钟方向");
  828. console.log("6. 角度计算公式:角度 = -144° + 进度百分比 × 288° / 100");
  829. console.log("7. 修复:小进度值(<3%)使用统一颜色绘制,避免分段导致的绘制异常");
  830. // 初始测试
  831. setTimeout(() => {
  832. updateProgress(50); // 50% 对应 0°
  833. }, 500);
  834. </script>
  835. </body>
  836. </html>