虚位以待,此位置招租
虚位以待,此位置招租
虚位以待,此位置招租
虚位以待,此位置招租
虚位以待,此位置招租
虚位以待,此位置招租
此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租
输入验证码,即可复制
扫描二维码输入:jiuge,即可获取验证码
只需要3秒时间
返回列表 发布新帖

[代码特效] 多个下载按钮动画效果(附源代码)

4 0
发表于 昨天 20:27 | 查看全部 阅读模式
截图202502282025109150.png

这是一组具有动画效果的下载操作按钮。这个交互效果不仅提供了不错的用户反馈,还通过平滑的动画增强了用户体验。实现中巧妙地利用了SVG和CSS动画,使得按钮既美观又具有高度的可定制性。

主要效果和实现原理如下:

视觉效果:

多种样式的下载按钮(普通、深色、白色、单独图标等)
点击按钮后显示下载进度动画
下载完成后显示勾选图标

主要实现原理:

使用事件监听器处理按钮点击
动态生成和更新SVG路径
使用Proxy对象监控和更新SVG路径的属性
使用CSS变量定义不同主题的颜色
大量使用CSS动画(@keyframes)实现状态转换效果
使用flexbox进行布局
CSS样式
JavaScript功能

动画实现:

文本滑动:使用CSS transform实现文本上下滑动
进度条:通过改变SVG路径实现
勾选图标:使用SVG stroke-dasharray和stroke-dashoffset属性创建绘制动画

交互性:

点击按钮触发下载动画
动画完成后显示勾选图标

可定制性:

使用CSS变量允许轻松更改颜色主题
JavaScript中的duration变量控制动画持续时间
使用方式
复制源代码到空白的html格式文件,在浏览中打开运行即可。

源代码

可上下滑动查看完整源代码:
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <style>
  5. .button.dark-single {
  6.   --background: none;
  7.   --rectangle: #242836;
  8.   --success: #4BC793;
  9. }
  10. .button.white-single {
  11.   --background: none;
  12.   --rectangle: #F5F9FF;
  13.   --arrow: #275efe;
  14.   --success: #275efe;
  15.   --shadow: rgba(10, 22, 50, .1);
  16. }
  17. .button.dark {
  18.   --background: #242836;
  19.   --rectangle: #1C212E;
  20.   --arrow: #F5F9FF;
  21.   --text: #F5F9FF;
  22.   --success: #2F3545;
  23. }

  24. .button {
  25.   --background: #275efe;
  26.   --rectangle: #184fee;
  27.   --success: #4672f1;
  28.   --text: #fff;
  29.   --arrow: #fff;
  30.   --checkmark: #fff;
  31.   --shadow: rgba(10, 22, 50, .24);
  32.   display: flex;
  33.   overflow: hidden;
  34.   text-decoration: none;
  35.   -webkit-mask-image: -webkit-radial-gradient(white, black);
  36.   background: var(--background);
  37.   border-radius: 8px;
  38.   box-shadow: 0 2px 8px -1px var(--shadow);
  39.   transition: transform 0.2s ease, box-shadow 0.2s ease;
  40. }
  41. .button:active {
  42.   transform: scale(0.95);
  43.   box-shadow: 0 1px 4px -1px var(--shadow);
  44. }
  45. .button ul {
  46.   margin: 0;
  47.   padding: 16px 40px;
  48.   list-style: none;
  49.   text-align: center;
  50.   position: relative;
  51.   -webkit-backface-visibility: hidden;
  52.           backface-visibility: hidden;
  53.   font-size: 16px;
  54.   font-weight: 500;
  55.   line-height: 28px;
  56.   color: var(--text);
  57. }
  58. .button ul li:not(:first-child) {
  59.   top: 16px;
  60.   left: 0;
  61.   right: 0;
  62.   position: absolute;
  63. }
  64. .button ul li:nth-child(2) {
  65.   top: 76px;
  66. }
  67. .button ul li:nth-child(3) {
  68.   top: 136px;
  69. }
  70. .button > div {
  71.   position: relative;
  72.   width: 60px;
  73.   height: 60px;
  74.   background: var(--rectangle);
  75. }
  76. .button > div:before, .button > div:after {
  77.   content: "";
  78.   display: block;
  79.   position: absolute;
  80. }
  81. .button > div:before {
  82.   border-radius: 1px;
  83.   width: 2px;
  84.   top: 50%;
  85.   left: 50%;
  86.   height: 17px;
  87.   margin: -9px 0 0 -1px;
  88.   background: var(--arrow);
  89. }
  90. .button > div:after {
  91.   width: 60px;
  92.   height: 60px;
  93.   transform-origin: 50% 0;
  94.   border-radius: 0 0 80% 80%;
  95.   background: var(--success);
  96.   top: 0;
  97.   left: 0;
  98.   transform: scaleY(0);
  99. }
  100. .button > div svg {
  101.   display: block;
  102.   position: absolute;
  103.   width: 20px;
  104.   height: 20px;
  105.   left: 50%;
  106.   top: 50%;
  107.   margin: -9px 0 0 -10px;
  108.   fill: none;
  109.   z-index: 1;
  110.   stroke-width: 2px;
  111.   stroke: var(--arrow);
  112.   stroke-linecap: round;
  113.   stroke-linejoin: round;
  114. }
  115. .button.loading ul {
  116.   -webkit-animation: text calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
  117.           animation: text calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
  118. }
  119. .button.loading > div:before {
  120.   -webkit-animation: line calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
  121.           animation: line calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
  122. }
  123. .button.loading > div:after {
  124.   -webkit-animation: background calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
  125.           animation: background calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
  126. }
  127. .button.loading > div svg {
  128.   -webkit-animation: svg calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
  129.           animation: svg calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
  130. }

  131. @-webkit-keyframes text {
  132.   10%, 85% {
  133.     transform: translateY(-100%);
  134.   }
  135.   95%, 100% {
  136.     transform: translateY(-200%);
  137.   }
  138. }

  139. @keyframes text {
  140.   10%, 85% {
  141.     transform: translateY(-100%);
  142.   }
  143.   95%, 100% {
  144.     transform: translateY(-200%);
  145.   }
  146. }
  147. @-webkit-keyframes line {
  148.   5%, 10% {
  149.     transform: translateY(-30px);
  150.   }
  151.   40% {
  152.     transform: translateY(-20px);
  153.   }
  154.   65% {
  155.     transform: translateY(0);
  156.   }
  157.   75%, 100% {
  158.     transform: translateY(30px);
  159.   }
  160. }
  161. @keyframes line {
  162.   5%, 10% {
  163.     transform: translateY(-30px);
  164.   }
  165.   40% {
  166.     transform: translateY(-20px);
  167.   }
  168.   65% {
  169.     transform: translateY(0);
  170.   }
  171.   75%, 100% {
  172.     transform: translateY(30px);
  173.   }
  174. }
  175. @-webkit-keyframes svg {
  176.   0%, 20% {
  177.     stroke-dasharray: 0;
  178.     stroke-dashoffset: 0;
  179.   }
  180.   21%, 89% {
  181.     stroke-dasharray: 26px;
  182.     stroke-dashoffset: 26px;
  183.     stroke-width: 3px;
  184.     margin: -10px 0 0 -10px;
  185.     stroke: var(--checkmark);
  186.   }
  187.   100% {
  188.     stroke-dasharray: 26px;
  189.     stroke-dashoffset: 0;
  190.     margin: -10px 0 0 -10px;
  191.     stroke: var(--checkmark);
  192.   }
  193.   12% {
  194.     opacity: 1;
  195.   }
  196.   20%, 89% {
  197.     opacity: 0;
  198.   }
  199.   90%, 100% {
  200.     opacity: 1;
  201.   }
  202. }
  203. @keyframes svg {
  204.   0%, 20% {
  205.     stroke-dasharray: 0;
  206.     stroke-dashoffset: 0;
  207.   }
  208.   21%, 89% {
  209.     stroke-dasharray: 26px;
  210.     stroke-dashoffset: 26px;
  211.     stroke-width: 3px;
  212.     margin: -10px 0 0 -10px;
  213.     stroke: var(--checkmark);
  214.   }
  215.   100% {
  216.     stroke-dasharray: 26px;
  217.     stroke-dashoffset: 0;
  218.     margin: -10px 0 0 -10px;
  219.     stroke: var(--checkmark);
  220.   }
  221.   12% {
  222.     opacity: 1;
  223.   }
  224.   20%, 89% {
  225.     opacity: 0;
  226.   }
  227.   90%, 100% {
  228.     opacity: 1;
  229.   }
  230. }
  231. @-webkit-keyframes background {
  232.   10% {
  233.     transform: scaleY(0);
  234.   }
  235.   40% {
  236.     transform: scaleY(0.15);
  237.   }
  238.   65% {
  239.     transform: scaleY(0.5);
  240.     border-radius: 0 0 50% 50%;
  241.   }
  242.   75% {
  243.     border-radius: 0 0 50% 50%;
  244.   }
  245.   90%, 100% {
  246.     border-radius: 0;
  247.   }
  248.   75%, 100% {
  249.     transform: scaleY(1);
  250.   }
  251. }
  252. @keyframes background {
  253.   10% {
  254.     transform: scaleY(0);
  255.   }
  256.   40% {
  257.     transform: scaleY(0.15);
  258.   }
  259.   65% {
  260.     transform: scaleY(0.5);
  261.     border-radius: 0 0 50% 50%;
  262.   }
  263.   75% {
  264.     border-radius: 0 0 50% 50%;
  265.   }
  266.   90%, 100% {
  267.     border-radius: 0;
  268.   }
  269.   75%, 100% {
  270.     transform: scaleY(1);
  271.   }
  272. }
  273. html {
  274.   box-sizing: border-box;
  275.   -webkit-font-smoothing: antialiased;
  276. }

  277. * {
  278.   box-sizing: inherit;
  279. }
  280. *:before, *:after {
  281.   box-sizing: inherit;
  282. }

  283. body {
  284.   min-height: 100vh;
  285.   display: flex;
  286.   font-family: "Roboto", Arial;
  287.   justify-content: center;
  288.   align-items: center;
  289.   background: #E4ECFA;
  290. }
  291. body .container {
  292.   display: flex;
  293.   flex-wrap: wrap;
  294.   justify-content: center;
  295. }
  296. body .container > div {
  297.   flex-basis: 100%;
  298.   width: 0;
  299. }
  300. body .container .button {
  301.   margin: 16px;
  302. }
  303. @media (max-width: 400px) {
  304.   body .container .button {
  305.     margin: 12px;
  306.   }
  307. }
  308. body .dribbble {
  309.   position: fixed;
  310.   display: block;
  311.   right: 20px;
  312.   bottom: 20px;
  313. }
  314. body .dribbble img {
  315.   display: block;
  316.   height: 28px;
  317. }
  318. </style>
  319. </head>

  320. <body>
  321. <div class="container">

  322.     <a href="" class="button">
  323.         <ul>
  324.             <li>下载</li>
  325.             <li>下载中</li>
  326.             <li>打开文件</li>
  327.         </ul>
  328.         <div>
  329.             <svg viewBox="0 0 24 24"></svg>
  330.         </div>
  331.     </a>

  332.     <a href="" class="button dark-single">
  333.         <div>
  334.             <svg viewBox="0 0 24 24"></svg>
  335.         </div>
  336.     </a>

  337.     <div></div>

  338.     <a href="" class="button white-single">
  339.         <div>
  340.             <svg viewBox="0 0 24 24"></svg>
  341.         </div>
  342.     </a>

  343.     <a href="" class="button dark">
  344.         <ul>
  345.             <li>下载</li>
  346.             <li>下载中</li>
  347.             <li>打开文件</li>
  348.         </ul>
  349.         <div>
  350.             <svg viewBox="0 0 24 24"></svg>
  351.         </div>
  352.     </a>

  353. </div>
  354. </body>
  355. <script>
  356. document.querySelectorAll('.button').forEach(button => {

  357.     let duration = 3000,
  358.         svg = button.querySelector('svg'),
  359.         svgPath = new Proxy({
  360.             y: null,
  361.             smoothing: null
  362.         }, {
  363.             set(target, key, value) {
  364.                 target[key] = value;
  365.                 if(target.y !== null && target.smoothing !== null) {
  366.                     svg.innerHTML = getPath(target.y, target.smoothing, null);
  367.                 }
  368.                 return true;
  369.             },
  370.             get(target, key) {
  371.                 return target[key];
  372.             }
  373.         });

  374.     button.style.setProperty('--duration', duration);

  375.     svgPath.y = 20;
  376.     svgPath.smoothing = 0;

  377.     button.addEventListener('click', e => {
  378.         
  379.         e.preventDefault();

  380.         if(!button.classList.contains('loading')) {

  381.             button.classList.add('loading');
  382.             setTimeout(() => {
  383.                 svg.innerHTML = getPath(0, 0, [
  384.                     [3, 14],
  385.                     [8, 19],
  386.                     [21, 6]
  387.                 ]);
  388.             }, duration / 2);

  389.         }

  390.     });

  391. });

  392. function getPoint(point, i, a, smoothing) {
  393.     let cp = (current, previous, next, reverse) => {
  394.             let p = previous || current,
  395.                 n = next || current,
  396.                 o = {
  397.                     length: Math.sqrt(Math.pow(n[0] - p[0], 2) + Math.pow(n[1] - p[1], 2)),
  398.                     angle: Math.atan2(n[1] - p[1], n[0] - p[0])
  399.                 },
  400.                 angle = o.angle + (reverse ? Math.PI : 0),
  401.                 length = o.length * smoothing;
  402.             return [current[0] + Math.cos(angle) * length, current[1] + Math.sin(angle) * length];
  403.         },
  404.         cps = cp(a[i - 1], a[i - 2], point, false),
  405.         cpe = cp(point, a[i - 1], a[i + 1], true);
  406.     return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`;
  407. }

  408. function getPath(update, smoothing, pointsNew) {
  409.     let points = pointsNew ? pointsNew : [
  410.             [4, 12],
  411.             [12, update],
  412.             [20, 12]
  413.         ],
  414.         d = points.reduce((acc, point, i, a) => i === 0 ? `M ${point[0]},${point[1]}` : `${acc} ${getPoint(point, i, a, smoothing)}`, '');
  415.     return `<path d="${d}" />`;
  416. }
  417. </script>
  418. </html>
复制代码

免责声明

本社区仅提供信息交流平台,帖子内容由用户自行发布,不代表社区立场。用户对发布内容负全责,包括版权、隐私、诽谤等,社区不承担因使用社区内容导致的损失。禁止发布侵权、隐私信息,发现侵权请联系我们。社区不负责第三方内容,用户因访问第三方内容产生的损失,社区不担责。用户须遵守规则和法律,不得发布违法、不当内容,违规内容将被删除。发表帖子即同意本声明,不同意请勿发帖。


特别提醒:网络空间并非法外之地,请广大用户自觉遵守法律法规,共同营造健康、文明、有序的网络环境。

本社区运营团队

联系我们: 如有疑问或发现违规行为,请联系管理员:kefu@foxccs.com

远方路灯明灭,银河倾斜,一斛星斗洒满天街。

回复

快捷回复: 经历想经历的,成为想成为的 - 九歌社区
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

💡 Tips:

投诉/建议联系

foxccs@qq.com

网站内容来源于网络,版权争议与本站无关。
请在下载后的24小时内从您的设备中彻底删除上述内容。

  • QQ交流群
  • 添加微信客服
Copyright © 2004-2025 九歌社区 版权所有 All Rights Reserved.
关灯 在本版发帖
扫一扫添加微信客服
官方QQ交流群
手机扫一扫访问
返回顶部
快速回复 返回顶部 返回列表