You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为DrawSVG动画按钮的OPEN文本添加下载触发功能?

解决下载按钮的触发问题

嘿,你的这个SVG动画按钮做得真的很酷!问题出在当前的逻辑里,点击事件只处理了动画播放,没有在动画完成后的"OPEN"状态下添加下载触发的逻辑。我来帮你调整一下代码,让它完美实现点击播放动画,再点击触发下载的功能:

关键修改点

  1. 新增一个状态变量readyToDownload,用来标记按钮是否处于可下载状态
  2. 实现triggerDownload函数,通过创建临时<a>标签触发浏览器的下载行为
  3. 修改点击事件的逻辑,区分"播放动画"和"触发下载"两种场景

修改后的完整代码

HTML

<script type='text/javascript' src="http://www.mindaffect.nl/wp-content/themes/x/js/CustomEase.min.js"></script>
<script type='text/javascript' src="https://cdnjs.cloudflare.com/ajax/libs/snap.svg/0.4.1/snap.svg-min.js"></script>
<script type='text/javascript' src="http://www.mindaffect.nl/wp-content/themes/x/js/TweenMax.min.js"></script>
<script type='text/javascript' src="http://www.mindaffect.nl/wp-content/themes/x/js/DrawSVGPlugin.min.js"></script>

<svg viewBox='0 0 100 50' width='620' height='310' fill='none'>
  <circle cx='20'cy='35' r='8.5' fill='#f78c3a' class='mainCircle'></circle>
  <circle cx='20' cy='35' r='8.05' stroke='#f78c3a' stroke-width='.9' fill='url(#gradient)' class='mainCircleFill'></circle>
  <rect x='17.5' y='32.5' width='5' height='5' stroke='none' fill='#f78c3a'' class='rect'></rect>
  <path d='M20,39 l3.5,-3.5 l0,0 M20,39 l-3.5,-3.5 l0,0 M20,39 l0,-7.5' stroke='#303030' stroke-linecap='round' stroke-width='.8' class='arrow'></path>
  <text x='55' y='36.5' fill='#f78c3a'' text-anchor='middle' font-size='4.5' font-family='Lato' letter-spacing='.2' class='text'>Mindaffect Technology</text>
  <path d='M50,25 h30 a10,10 0 0,1 10,10 a10,10 0 0,1 -10,10 s-30,0 -60,0 a10,10 0 0,1 -10,-10 a10,10 0 0,1 10,-10 h30' stroke='#f78c3a' stroke-width='.7' fill='transparent' class='btn'></path>
  <circle cx='20' cy='35' r='7.9' fill='#303030' fill-opacity='0' stroke='#303030' stroke-width='1.6' stroke-opacity='0' class='subCircle'></circle>
  <circle cx='50' cy='26' r='0' fill='#303030' class='dot'></circle>
  <linearGradient id='gradient' x1='0%' y1='0%' x2='0%' y2='100%'>
    <stop offset='98%' class='gradient' stop-color='#f78c3a'/>
    <stop offset='98%' class='gradient' stop-color='#f78c3a'/>
  </linearGradient>
</svg>

CSS

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  min-height: 100vh;
  overflow: hidden;
  background-color: #313636;
  display: flex;
  align-items: center;
  justify-content: center;
}
svg {
  margin-bottom: 80px;
}
.btn {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.text {
  user-select: none;
  -webkit-font-smoothing: subpixel-antialiased;
  text-rendering: optimizeLegibility;
}
.subCircle {
  pointer-events: none;
}
.strokeW {
  animation: strokeW .6s forwards;
  @keyframes strokeW {
    to { stroke-width: 1.16; }
  }
}

JavaScript

let tl, 
    downloading = false, 
    readyToDownload = false, // 新增状态变量:标记是否可触发下载
    points = [], 
    btn = document.querySelector('.btn'), 
    dot = document.querySelector('.dot'), 
    text = document.querySelector('.text'), 
    mainCirc = document.querySelector('.mainCircle'), 
    subCirc = document.querySelector('.subCircle'), 
    mainCircFill = document.querySelector('.mainCircleFill'), 
    arrow = document.querySelector('.arrow'), 
    rect = document.querySelector('.rect');

TweenLite.set(rect, {transformOrigin: '50% 50%', rotation: 45});
btn.addEventListener('click', animation);

function animation() {
  // 如果正在播放动画,直接返回
  if (downloading) return;
  
  // 如果处于可下载状态,触发下载并重置
  if (readyToDownload) {
    triggerDownload();
    return;
  }
  
  downloading = !downloading;
  let downloadTime = Math.random() * .5 + .7;
  
  tl = new TimelineLite({onComplete: restart});
  tl.restart().play()
    .to(arrow, .35, {y: 2.5, ease: CustomEase.create('custom', 'M0,0,C0.042,0.14,0.374,1,0.5,1,0.64,1,0.964,0.11,1,0')}, 'click')
    .to(text, .3, {svgOrigin: '55% 35%', scale: .77, ease: CustomEase.create('custom', 'M0,0,C0.042,0.14,0.374,1,0.5,1,0.64,1,0.964,0.11,1,0')}, 'click+=.05')
    .set(subCirc, {fillOpacity: 1, strokeOpacity: 1}, 'squeeze-=.3')
    .to(subCirc, .35, {fillOpacity: 0, ease: Power1.easeInOut}, 'squeeze-=.3')
    .to(subCirc, .45, {attr:{r: 13}, strokeOpacity: 0, className: '+=strokeW', ease: Power0.easeNone}, 'squeeze-=.3')
    .to(btn, .7, {attr:{d: 'M50,25 h0 a10,10 0 0,1 10,10 a10,10 0 0,1 -10,10 s0,0 0,0 a10,10 0 0,1 -10,-10 a10,10 0 0,1 10,-10 h0'}, ease: Sine.easeOut}, 'squeeze')
    .to([mainCirc, mainCircFill, rect, arrow], .7, {x: 30, ease: Sine.easeOut}, 'squeeze')
    .to(rect, .7, {fill: '#303030', rotation: 270, ease: Sine.easeOut}, 'squeeze')
    .to(text, .3, {autoAlpha: 0, y: 7, onComplete: changeText}, 'squeeze')
    .to(arrow, .7, {attr:{d: 'M20,39 l3.5,-3.5 l-3.5,-3.5 M20,39 l-3.5,-3.5 l3.5,-3.5 M20,39 l0,0'}, transformOrigin: '50% 50%', rotation: 225, ease: Sine.easeOut}, 'squeeze')
    .to(dot, .4, {attr:{r: 1.5}, ease: Back.easeOut.config(7)})
    .set(subCirc, {drawSVG: 0, strokeOpacity: 1, transformOrigin: '50% 50%', x: 30, rotation: -90, attr:{r: 9.07}})
    .to(subCirc, downloadTime, {drawSVG: '102%', ease: Power2.easeIn}, 'fill+=.02')
    .to(dot, downloadTime, {bezier:{type: 'cubic', values: points}, attr:{r: 2.7} , ease: Power2.easeIn}, 'fill')
    .to('.gradient', downloadTime, {attr:{offset: '0%'}, ease: Power2.easeIn}, 'fill')
    .to(dot, .44, {fill: '#f78c3a', y: -22, ease: Power1.easeOut}, 'stretch-=.01')
    .to(dot, .27, {transformOrigin: '50% 50%', scaleX: .5, ease: SlowMo.ease.config(0.1, 2, true)}, 'stretch+=.04')
    .to(dot, .3, {scaleY: .6, ease: SlowMo.ease.config(0.1, 2, true)}, 'stretch+=.31')
    .to(dot, .44, {scaleX: .4, y: 22, ease: Power2.easeIn}, 'stretch+=.45')
    .to([mainCirc, subCirc, arrow, rect, mainCircFill], .33, {opacity: 0, ease: Power2.easeOut}, 'stretch+=.2')
    .to(btn, .4, {attr:{d: 'M50,25 h20 a10,10 0 0,1 10,10 a10,10 0 0,1 -10,10 s-20,0 -40,0 a10,10 0 0,1 -10,-10 a10,10 0 0,1 10,-10 h20'}, ease: Power1.easeOut}, 'stretch+=.2')
    .set(dot, {opacity: 0}, 'stretch+=.875')
    .to(btn, .01, {stroke: '#f78c3a', ease: Power2.easeIn}, 'stretch+=.87')
    .to(btn, .3, {attr:{d: 'M50,25 h20 a10,10 0 0,1 10,10 a12,12 0 0,1 -10,10.5 s-20,6 -40,0 a12,12 0 0,1 -10,-10.5 a10,10 0 0,1 10,-10 h20'}, ease: CustomEase.create('custom', 'M0,0 C0.046,0.062 0.018,1 0.286,1 0.532,1 0.489,-0.206 0.734,-0.206 0.784,-0.206 0.832,-0.174 1,0')}, 'stretch+=.869')
    .to(text, .45, {autoAlpha: 1, y: 0, ease: Back.easeOut.config(2.5)}, 'stretch+=.855');
};

function restart() {
  setTimeout(() => {
    readyToDownload = true; // 动画完成后标记为可下载状态
    downloading = false;
    // 这里保持文本为"OPEN",等待用户点击触发下载
  }, 2000);
};

function changeText() {
  text.textContent = 'OPEN';
  TweenLite.set(text, {x: -5});
};

// 新增:触发下载的核心函数
function triggerDownload() {
  // 替换成你的实际下载链接和文件名
  const downloadUrl = 'https://your-domain.com/your-file-to-download.zip';
  const fileName = 'mindaffect-technology-file.zip';
  
  // 创建临时a标签触发下载
  const downloadLink = document.createElement('a');
  downloadLink.href = downloadUrl;
  downloadLink.download = fileName;
  document.body.appendChild(downloadLink);
  downloadLink.click();
  document.body.removeChild(downloadLink);
  
  // 下载触发后重置按钮状态,回到初始样式
  readyToDownload = false;
  tl.seek(0).pause();
  text.textContent = 'MindAffect Technology';
  TweenLite.set(text, {x: 0});
};

// 初始化路径点的代码保持不变
(function() {
  let data = Snap.path.toCubic('M0,0 a9,9 0 0,1 0,18 a9,9 0 0,1 0,-18'), dataLen = data.length;
  for (let i = 0; i < dataLen; i++) {
    let seg = data[i];
    if (seg[0] === 'M') {
      let point = {};
      point.x = seg[1];
      point.y = seg[2];
      points.push(point);
    } else {
      for (let i = 1; i < 6; i+=2) {
        let point = {};
        point.x = seg[i];
        point.y = seg[i+1];
        points.push(point);
      }
    }
  }
})();

代码说明

  • 新增的readyToDownload变量用来区分按钮的两个状态:动画播放中 / 可下载状态
  • triggerDownload函数是核心,通过创建临时<a>标签利用浏览器原生的下载机制,你只需要把downloadUrl替换成你实际的文件链接即可
  • 点击逻辑做了分支判断:第一次点击播放动画,动画完成后显示"OPEN",此时再次点击就会触发下载,下载完成后按钮自动回到初始状态

这样调整后,你的按钮就能完美实现动画播放 + 下载触发的完整流程啦!

内容的提问来源于stack exchange,提问作者Luuk van Driel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 07:26:14