Luuk van Driel
Luuk van Driel

Reputation: 39

DrawSVG button: How to implement download

I have a flashy download button for my domain using DrawSVG, CustomEasy and TweenMax. Yet, I can not seem to figure out how to get the download triggered.

The button opens up an animation when clicked. Eventually, the OPEN text appears at the end of the animation, where it awaits the click of the user. How can I best implement a download onclick (on the OPEN)? I have tried many on.click events and other implementations, but I can not get it to work. I am forever grateful for your help.

https://codepen.io/anon/pen/qpXRdm

let tl, downloading = 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;
  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(() => {
    tl.seek(0).pause();
    text.textContent = 'MindAffect Technology';
    TweenLite.set(text, {x: 0});
    downloading = false;
  }, 2000);
};

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

(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);
      }
    }
  }
})();
* {
  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;
    }
  }
}
<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>

Kind regards

Upvotes: 0

Views: 158

Answers (3)

Pablo E. Lujambio
Pablo E. Lujambio

Reputation: 177

I modified your example for it to be able to work with multiple buttons in the same page.

  1. First we need to set up the SVG document like this:

    <svg viewBox='0 0 100 50' width='620' height='310' fill='none' class="svg-button" download-url="https://static.pexels.com/photos/784927/pexels-photo-784927.jpeg">
        ...
    </svg>
    

    Notice we are adding the .svg-button class, and the file download url in the download-url attribute.

  2. We will be moving all of your variable declarations inside the animation() function, as well as your changeText() function. For this point check the Codepen for reference. View CodePen example here

  3. We then need to remove btn.addEventListener("click", animation()) from your JS. We will delegate the click event to the document instead:

    document.addEventListener("click", function(event){
      if (event.target.classList.contains("svg-button") {
        console.log("target", event.target);
        animation(event.target)
      }
    }); 
    
  4. Next, we will create a click event listener for opening the URL when your timeline ends, using an onComplete callback like this:

    tl = new TimelineLite({ 
      onComplete: function(){
        target.addEventListener("click", function(){
          Object.assign(document.createElement('a'), { 
            target: '_blank', 
            href: target.getAttribute("download-url") }).click();
        })
      }
    });
    
  5. We set pointer-events: none; for all the elements inside your .svg-button like this, so that the event doesn't propagate to the inner elements:

    .svg-button {
      margin-bottom: 80px;
      cursor: pointer;
      -webkit-tap-highlight-color: transparent;  
      * {
        pointer-events: none; 
      } 
    }
    

View modified example here

Upvotes: 1

Aaron Kvarnlov-Leverty
Aaron Kvarnlov-Leverty

Reputation: 141

Currently the 'onComplete' callback for the button is set to 'restart'.

tl = new TimelineLite({onComplete: restart});

You could instead pass a function that adds a click handler instead of restarting the animation.

tl = new TimelineLite({onComplete: allowOpen});

and define the function to add a click handler:

function allowOpen(){
  btn.removeEventListener('click',animation');
  btn.addEventListener('click',open);
}

function open () {
  alert('opened!');
}

Upvotes: 0

ZPiDER
ZPiDER

Reputation: 4412

hack it:

function changeText() {
  text.textContent = 'OPEN';
  btn.removeEventListener('click', animation);
  btn.addEventListener('click', function() { alert('hey!'); });
  TweenLite.set(text, {x: -5});
};

but then ask yourself: do you really want the user to have to make another click?

Upvotes: 0

Related Questions