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

[复制链接]
七夏(UID:1) 发表于 2024-10-7 14:40:43 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×

图片

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

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

  1. 视觉效果:

    • 多种样式的下载按钮(普通、深色、白色、单独图标等)
    • 点击按钮后显示下载进度动画
    • 下载完成后显示勾选图标
  2. 主要实现原理:

  • 使用事件监听器处理按钮点击
  • 动态生成和更新SVG路径
  • 使用Proxy对象监控和更新SVG路径的属性
  • 使用CSS变量定义不同主题的颜色
  • 大量使用CSS动画(@keyframes)实现状态转换效果
  • 使用flexbox进行布局
  • CSS样式:
  • JavaScript功能:
  1. 动画实现:
  • 文本滑动:使用CSS transform实现文本上下滑动
  • 进度条:通过改变SVG路径实现
  • 勾选图标:使用SVG stroke-dasharray和stroke-dashoffset属性创建绘制动画
  1. 交互性:
  • 点击按钮触发下载动画
  • 动画完成后显示勾选图标
  1. 可定制性:
  • 使用CSS变量允许轻松更改颜色主题
  • JavaScript中的duration变量控制动画持续时间

使用方式

复制源代码到空白的html格式文件,在浏览中打开运行即可。

源代码

可上下滑动查看完整源代码:

<!DOCTYPE html>
<html lang="en">
<head>
<style>
.button.dark-single {
  --background: none;
  --rectangle: #242836;
  --success: #4BC793;
}
.button.white-single {
  --background: none;
  --rectangle: #F5F9FF;
  --arrow: #275efe;
  --success: #275efe;
  --shadow: rgba(10, 22, 50, .1);
}
.button.dark {
  --background: #242836;
  --rectangle: #1C212E;
  --arrow: #F5F9FF;
  --text: #F5F9FF;
  --success: #2F3545;
}

.button {
  --background: #275efe;
  --rectangle: #184fee;
  --success: #4672f1;
  --text: #fff;
  --arrow: #fff;
  --checkmark: #fff;
  --shadow: rgba(10, 22, 50, .24);
  display: flex;
  overflow: hidden;
  text-decoration: none;
  -webkit-mask-image: -webkit-radial-gradient(white, black);
  background: var(--background);
  border-radius: 8px;
  box-shadow: 0 2px 8px -1px var(--shadow);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.button:active {
  transform: scale(0.95);
  box-shadow: 0 1px 4px -1px var(--shadow);
}
.button ul {
  margin: 0;
  padding: 16px 40px;
  list-style: none;
  text-align: center;
  position: relative;
  -webkit-backface-visibility: hidden;
          backface-visibility: hidden;
  font-size: 16px;
  font-weight: 500;
  line-height: 28px;
  color: var(--text);
}
.button ul li:not(:first-child) {
  top: 16px;
  left: 0;
  right: 0;
  position: absolute;
}
.button ul li:nth-child(2) {
  top: 76px;
}
.button ul li:nth-child(3) {
  top: 136px;
}
.button > div {
  position: relative;
  width: 60px;
  height: 60px;
  background: var(--rectangle);
}
.button > div:before, .button > div:after {
  content: "";
  display: block;
  position: absolute;
}
.button > div:before {
  border-radius: 1px;
  width: 2px;
  top: 50%;
  left: 50%;
  height: 17px;
  margin: -9px 0 0 -1px;
  background: var(--arrow);
}
.button > div:after {
  width: 60px;
  height: 60px;
  transform-origin: 50% 0;
  border-radius: 0 0 80% 80%;
  background: var(--success);
  top: 0;
  left: 0;
  transform: scaleY(0);
}
.button > div svg {
  display: block;
  position: absolute;
  width: 20px;
  height: 20px;
  left: 50%;
  top: 50%;
  margin: -9px 0 0 -10px;
  fill: none;
  z-index: 1;
  stroke-width: 2px;
  stroke: var(--arrow);
  stroke-linecap: round;
  stroke-linejoin: round;
}
.button.loading ul {
  -webkit-animation: text calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
          animation: text calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
}
.button.loading > div:before {
  -webkit-animation: line calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
          animation: line calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
}
.button.loading > div:after {
  -webkit-animation: background calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
          animation: background calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
}
.button.loading > div svg {
  -webkit-animation: svg calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
          animation: svg calc(var(--duration) * 1ms) linear forwards calc(var(--duration) * .065ms);
}

@-webkit-keyframes text {
  10%, 85% {
    transform: translateY(-100%);
  }
  95%, 100% {
    transform: translateY(-200%);
  }
}

@keyframes text {
  10%, 85% {
    transform: translateY(-100%);
  }
  95%, 100% {
    transform: translateY(-200%);
  }
}
@-webkit-keyframes line {
  5%, 10% {
    transform: translateY(-30px);
  }
  40% {
    transform: translateY(-20px);
  }
  65% {
    transform: translateY(0);
  }
  75%, 100% {
    transform: translateY(30px);
  }
}
@keyframes line {
  5%, 10% {
    transform: translateY(-30px);
  }
  40% {
    transform: translateY(-20px);
  }
  65% {
    transform: translateY(0);
  }
  75%, 100% {
    transform: translateY(30px);
  }
}
@-webkit-keyframes svg {
  0%, 20% {
    stroke-dasharray: 0;
    stroke-dashoffset: 0;
  }
  21%, 89% {
    stroke-dasharray: 26px;
    stroke-dashoffset: 26px;
    stroke-width: 3px;
    margin: -10px 0 0 -10px;
    stroke: var(--checkmark);
  }
  100% {
    stroke-dasharray: 26px;
    stroke-dashoffset: 0;
    margin: -10px 0 0 -10px;
    stroke: var(--checkmark);
  }
  12% {
    opacity: 1;
  }
  20%, 89% {
    opacity: 0;
  }
  90%, 100% {
    opacity: 1;
  }
}
@keyframes svg {
  0%, 20% {
    stroke-dasharray: 0;
    stroke-dashoffset: 0;
  }
  21%, 89% {
    stroke-dasharray: 26px;
    stroke-dashoffset: 26px;
    stroke-width: 3px;
    margin: -10px 0 0 -10px;
    stroke: var(--checkmark);
  }
  100% {
    stroke-dasharray: 26px;
    stroke-dashoffset: 0;
    margin: -10px 0 0 -10px;
    stroke: var(--checkmark);
  }
  12% {
    opacity: 1;
  }
  20%, 89% {
    opacity: 0;
  }
  90%, 100% {
    opacity: 1;
  }
}
@-webkit-keyframes background {
  10% {
    transform: scaleY(0);
  }
  40% {
    transform: scaleY(0.15);
  }
  65% {
    transform: scaleY(0.5);
    border-radius: 0 0 50% 50%;
  }
  75% {
    border-radius: 0 0 50% 50%;
  }
  90%, 100% {
    border-radius: 0;
  }
  75%, 100% {
    transform: scaleY(1);
  }
}
@keyframes background {
  10% {
    transform: scaleY(0);
  }
  40% {
    transform: scaleY(0.15);
  }
  65% {
    transform: scaleY(0.5);
    border-radius: 0 0 50% 50%;
  }
  75% {
    border-radius: 0 0 50% 50%;
  }
  90%, 100% {
    border-radius: 0;
  }
  75%, 100% {
    transform: scaleY(1);
  }
}
html {
  box-sizing: border-box;
  -webkit-font-smoothing: antialiased;
}

* {
  box-sizing: inherit;
}
*:before, *:after {
  box-sizing: inherit;
}

body {
  min-height: 100vh;
  display: flex;
  font-family: "Roboto", Arial;
  justify-content: center;
  align-items: center;
  background: #E4ECFA;
}
body .container {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
}
body .container > div {
  flex-basis: 100%;
  width: 0;
}
body .container .button {
  margin: 16px;
}
@media (max-width: 400px) {
  body .container .button {
    margin: 12px;
  }
}
body .dribbble {
  position: fixed;
  display: block;
  right: 20px;
  bottom: 20px;
}
body .dribbble img {
  display: block;
  height: 28px;
}
</style>
</head>

<body>
<div class="container">

    <a href="" class="button">
        <ul>
            <li>下载</li>
            <li>下载中</li>
            <li>打开文件</li>
        </ul>
        <div>
            <svg viewBox="0 0 24 24"></svg>
        </div>
    </a>

    <a href="" class="button dark-single">
        <div>
            <svg viewBox="0 0 24 24"></svg>
        </div>
    </a>

    <div></div>

    <a href="" class="button white-single">
        <div>
            <svg viewBox="0 0 24 24"></svg>
        </div>
    </a>

    <a href="" class="button dark">
        <ul>
            <li>下载</li>
            <li>下载中</li>
            <li>打开文件</li>
        </ul>
        <div>
            <svg viewBox="0 0 24 24"></svg>
        </div>
    </a>

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

    let duration = 3000,
        svg = button.querySelector('svg'),
        svgPath = new Proxy({
            y: null,
            smoothing: null
        }, {
            set(target, key, value) {
                target[key] = value;
                if(target.y !== null && target.smoothing !== null) {
                    svg.innerHTML = getPath(target.y, target.smoothing, null);
                }
                return true;
            },
            get(target, key) {
                return target[key];
            }
        });

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

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

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

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

            button.classList.add('loading');
            setTimeout(() => {
                svg.innerHTML = getPath(0, 0, [
                    [3, 14],
                    [8, 19],
                    [21, 6]
                ]);
            }, duration / 2);

        }

    });

});

function getPoint(point, i, a, smoothing) {
    let cp = (current, previous, next, reverse) => {
            let p = previous || current,
                n = next || current,
                o = {
                    length: Math.sqrt(Math.pow(n[0] - p[0], 2) + Math.pow(n[1] - p[1], 2)),
                    angle: Math.atan2(n[1] - p[1], n[0] - p[0])
                },
                angle = o.angle + (reverse ? Math.PI : 0),
                length = o.length * smoothing;
            return [current[0] + Math.cos(angle) * length, current[1] + Math.sin(angle) * length];
        },
        cps = cp(a[i - 1], a[i - 2], point, false),
        cpe = cp(point, a[i - 1], a[i + 1], true);
    return `C ${cps[0]},${cps[1]} ${cpe[0]},${cpe[1]} ${point[0]},${point[1]}`;
}

function getPath(update, smoothing, pointsNew) {
    let points = pointsNew ? pointsNew : [
            [4, 12],
            [12, update],
            [20, 12]
        ],
        d = points.reduce((acc, point, i, a) => i === 0 ? `M ${point[0]},${point[1]}` : `${acc} ${getPoint(point, i, a, smoothing)}`, '');
    return `<path d="${d}" />`;
}
</script>
</html>
小时候,看腻了农村的牛和马,长大后,来到了城里,才知道原来到处都是牛马!
全部回复0 显示全部楼层
暂无回复,精彩从你开始!

快速回帖

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于楼主

管理员
  • 主题

    710
  • 回答

    248
  • 积分

    1903
虚位以待,此位置招租

商务推广

    网盘拉新-短剧推广 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租 此位置招租