16

I have a requirement where I need to load js files dynamically and show the progress of loading files through a SVG icon. The SVG icon will act as progress bar where it fills with a color from bottom to top, linearly.

Here is the codepen

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="79.36px" height="93.844px" viewBox="0 0 79.36 93.844">

  <path fill="transparent" stroke="black" d="M50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
</svg>

I am planning to make this icon independent such that I will only pass the percentage value dynamically.

I somehow able to get the animation done but unable to keep the border or outline of the svg. Here is the code.

#progressMove {
  transition: .3s y;
}
#progressMove:hover {
  y: 60%;
}
<svg id="kenseoProgress" width="79.36px" height="93.844px" viewBox="0 0 79.36 93.844">
  <defs>
    <mask id="bubbleKenseo">
      <path fill="red" stroke="black" d="M50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
    </mask>
  </defs>
  <g x="0" y="0" width="79.36px" height="93.844px" mask="url(#bubbleKenseo)" height="100">
    <rect id="progressMove" x="0" y="0%" width="100%" height="100%" fill="blue" stroke="black" />
  </g>
</svg>

So, the problems I have are:

  • Unable to maintain the border to the SVG
  • Whatever the color I add is having some kind of opacity which I am unable to remove.
  • Edit: Browser compatibility: IE11+, chrome, safari and firefox

PS: I don't want to use SMIL animations.

Mr_Green
  • 40,727
  • 45
  • 159
  • 271

4 Answers4

9

CHROME/SAFARI SOLUTION

Using the CSS property transform and counter-increment you can achieve the fill and number increment.

jsFiddle

CODE SNIPPET

for (var i = 0; i < 100; i++) {
  setTimeout(function() {
    $(".progress-container p").append("<span>");
  }, i * 20);
}
pattern #progressMove {
  transform: translateY(100%);
  color: purple;
  animation: progressBar 2s steps(100, end) forwards;
}
@keyframes progressBar {
  to {
    transform: translateY(0);
  }
}
.progress-container {
  margin: 0;
  display: inline-block;
  position: relative;
  counter-reset: progress;
}
.progress-container figcaption {
  position: absolute;
  top: 40%;
  left: 50%;
  transform: translate(-40%, -50%);
}
.progress-container p {
  margin: 0;
  font-weight: bold;
}
.progress-container span {
  counter-increment: progress;
}
.progress-container p::after {
  content: counter(progress)"%";
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<figure class="progress-container">
  <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="79.36px" height="93.844px" viewBox="0 0 79.36 93.844">
    <pattern id="progress" x="0" y="0" width="79.36" height="93.844" patternUnits="userSpaceOnUse">
      <rect id="progressMove" x="0" y="0" width="100%" height="100%" stroke="none" fill="currentColor" />
    </pattern>
    <path fill="url(#progress)" stroke="#000" d="M50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
  </svg>
  <figcaption>
    <p>

    </p>
  </figcaption>
</figure>

Note:

Will update if I can give a better solution to cover browser support.


EDIT:

Based on Persijn answer, you will as well have to change the color of the background to that of its parent.

The whole component would be the figure element, sadly the symbol in the spritesheet will only be used to provide the path and background.

Note: jQuery removed in this version.

jsFiddle

for (var i = 0; i < 100; i++) {
  setTimeout(function() {
    var progressCounter = document.querySelector(".progress__counter"),
      number = document.createElement("span");
    progressCounter.appendChild(number);
  }, i * 20);
}
#spritesheet {
  display: none;
}
.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
}
.icon-bubble {
  font-size: 7em;
  color: white;
}
.progress-container {
  margin: 0;
  display: inline-block;
  position: relative;
  counter-reset: progress;
  overflow: hidden;
  line-height: 0;
}
.progress__inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
}
.progress__fill {
  background-color: purple;
  height: 100%;
  transform: translateY(100%);
  animation: progressFill 2s steps(100, end) forwards;
}
@keyframes progressFill {
  to {
    transform: translateY(0);
  }
}
.progress__counter {
  position: absolute;
  top: 40%;
  left: 50%;
  transform: translate(-40%, -50%);
  margin: 0;
  font-weight: bold;
}
.progress__counter span {
  counter-increment: progress;
}
.progress__counter::after {
  content: counter(progress)"%";
}
<figure class="progress-container">
  <svg class="icon icon-bubble">
    <use xlink:href="#icon-bubble"></use>
  </svg>
  <figcaption class="progress__inner">
    <div class="progress__fill"></div>
    <p class="progress__counter"></p>
  </figcaption>
</figure>

<svg id="spritesheet">
  <symbol id="icon-bubble" viewBox="0 0 79.36 93.844">
    <title>Loading Bubble</title>
    <path id="bubble-cover" fill="currentColor" stroke="#000" d="M-10,-10 100,-10 100,100 -10,100 -10,-10  50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
  </symbol>
</svg>

TESTS:

  • Chrome 53
  • IE10
  • Edge
  • FireFox 47
  • IOS 10 Safari

PLAYGROUND

jsFiddle

for (var i = 0; i < 100; i++) {
  setTimeout(function() {
    var progressCounter = document.querySelector(".progress__counter"),
      number = document.createElement("span");
    progressCounter.appendChild(number);
  }, i * 20);
}
#spritesheet {
  display: none;
}
.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
}
.icon-bubble {
  font-size: 7em;
  color: white;
}
.progress-container {
  margin: 0;
  display: inline-block;
  position: relative;
  counter-reset: progress;
  overflow: hidden;
  line-height: 0;
}
.progress__inner {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: -1;
}
.progress__fill {
  background-color: purple;
  height: 100%;
  transform: translateY(100%);
  animation: progressFill 2s steps(100, end) forwards, progressFillColor 100ms linear 2s forwards;
  position: relative;
}
@keyframes progressFill {
  to {
    transform: translateY(0);
  }
}
@keyframes progressFillColor {
  to {
    background-color: green;
  }
}
.progress__counter {
  position: absolute;
  top: 40%;
  transform: translateY(-40%);
  text-align: center;
  width: 100%;
  margin: 0;
  font-weight: bold;
  animation: progressCounter 100ms linear 1s forwards;
}
.progress__counter span {
  counter-increment: progress;
}
.progress__counter::after {
  content: counter(progress)"%";
  animation: progressCounterCompleted 1s linear 2s forwards;
}
@keyframes progressCounter {
  to {
    color: white;
  }
}
/* Chrome Only*/

@keyframes progressCounterCompleted {
  33% {
    content: "File(s)";
  }
  66% {
    content: "Uploaded";
  }
  100% {
    content: "Successfully!";
  }
}
<figure class="progress-container">
  <svg class="icon icon-bubble">
    <use xlink:href="#icon-bubble"></use>
  </svg>
  <figcaption class="progress__inner">
    <div class="progress__fill"></div>
    <p class="progress__counter"></p>
  </figcaption>
</figure>

<svg id="spritesheet">
  <symbol id="icon-bubble" viewBox="0 0 79.36 93.844">
    <title>Loading Bubble</title>
    <path id="bubble-cover" fill="currentColor" stroke="#000" d="M-10,-10 100,-10 100,100 -10,100 -10,-10  50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
  </symbol>
</svg>
Community
  • 1
  • 1
Ricky Ruiz
  • 25,455
  • 6
  • 44
  • 53
  • Yes I need browser support considering IE11+, chrome, safari and firefox. Will update the same in my post. – Mr_Green Sep 28 '16 at 04:45
7

SVG with pattern and y transition:

svg:hover pattern #fillshape {
  y: 0%;
}
pattern #fillshape {
  transition: y 1s;
  y: 100%;
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="79.36px" height="93.844px" viewBox="0 0 79.36 93.844">
    <pattern id="pattern1"
           x="0" y="0" width="79.36" height="93.844"
           patternUnits="userSpaceOnUse" >

      <rect id="fillshape" x="0" y="0" width="100%" height="200%" stroke="none" fill="purple" />

  </pattern>
  <path fill="url(#pattern1)" stroke="black" d="M50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
</svg>

Now this does not work in Firefox or Edge. It does not recognize x and y as CSS properties...

Here is a solution that uses a div behind the svg shape. The downside of this solution is that the svg shape gets a background eg. if you want the shape only you would have to match the background color of the shape with that of the background of the page.

svg {
  position: relative;
} 
.spesial {
  width: 90px;
  height: 0px;
  display: inline-block;
  background-color: purple;
  margin-left: -100px;
  transition: height 1s;
}
svg:hover + .spesial {
  height: 100px;
}
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100" height="100" viewBox="0 0 75 90">
  <path stroke="black" fill="gray" d="M-10,-10 100,-10 100,100 -10,100 -10,-10  50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
</svg>
<div class="spesial">
</div>
Persijn
  • 14,624
  • 3
  • 43
  • 72
  • Thanks, I can go with the below solution as it is cross browser supported. But I was hoping maybe I can just keep the div inside the svg itself. because I am using this svg as spritesheet using `use` tag. – Mr_Green Sep 28 '16 at 05:28
3

First off, you want to use clip-path, or set the mask fill to white for 100% opacity: mask is used as a greyscale alpha channel and the red fill color causes the opacity change.

As for the stroke, you want to add it as a separate element that is not affected by the clipping. (You can probably re-use the path with defs and use, I just copy-pasted it here)

#progressMove {
  transition: .3s y;
}
#progressMove:hover {
  y: 60%;
}
<svg id="kenseoProgress" width="79.36px" height="93.844px" viewBox="0 0 79.36 93.844">
  <defs>
    <clipPath id="bubbleKenseo">
      <path d="M50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
    </clipPath>
  </defs>
  <path stroke="black" stroke-width="1" fill="transparent" d="M50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
  <g x="0" y="0" width="79.36px" height="93.844px" clip-path="url(#bubbleKenseo)" height="100">
    <rect id="progressMove" x="0" y="0%" width="100%" height="100%" fill="blue" stroke="black" />
  </g>
</svg>
yscik
  • 879
  • 4
  • 7
  • This is working Awesome!! I did minimum changes to my existing code. Thanks. Can you please explain why I was seeing opacity to my applied color as I mentioned in question? – Mr_Green Oct 04 '16 at 04:20
  • I don't see how this solution is different from the first in my answer, and this only works in chrome – Persijn Oct 04 '16 at 10:09
  • 1
    @Mr_Green The mask is used as a greyscale alpha channel, where black = 0% opacity and white = 100%. The red color you used is translated to a mid-dark grey so it yields 50-60% opacity. – yscik Oct 04 '16 at 12:41
0

var prObject = document.getElementById("prObject"),
  prDom = document.getElementById("progressMove"),
  prValue = 0;

prObject.onmouseenter = function() {
  prDom.setAttribute('class', 'prHover')
};
prObject.onmouseleave = function() {
  prDom.removeAttribute('class')
};

/*prDom.setAttributeNS(null, 'y', '0');*/

var cTimer = setInterval(function() {
  prValue += 20.6;
  prDom.style.transform = "translateY(" + [100 - Math.min(prValue, 100)] + "%)";

  if (prValue >= 100) {
    clearInterval(cTimer);
  }
}, 450);
#progressMove {
  transition: transform 0.20s linear;
}
#progressMove.prHover {
  transform: translateY(40%) !important;
}
<!DOCTYPE html>
<html>

<head>
  <title></title>
</head>

<body>
  <svg id="kenseoProgress" width="79.36px" height="93.844px" viewBox="0 0 79.36 93.844">
    <defs>
      <path id="mypath" fill="white" d="M50,2C30-4,8,7,2,28c-6,20,5,42,26,48h0l-4,15l33-18c0-0,0-0,1-0l0-0l-0-0c8-4,15-12,17-22C83,30,71,8,50,2z" />
      <mask id="bubbleKenseo">
        <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#mypath"></use>
      </mask>
    </defs>
    <g x="0" y="0" width="79.36px" height="93.844px" mask="url(#bubbleKenseo)" height="100" stroke-width="0">
      <rect id="progressMove" x="0" y="0" width="100%" height="100%" fill="blue" stroke="black" style="transform: translateY(100%);" />
    </g>
    <g id="prObject" x="0" y="0" width="79.36px" height="93.844px" height="100" fill-opacity="0" stroke="black" stroke-width="0.5px">
      <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#mypath"></use>
    </g>
  </svg>
</body>

</html>
Al Foиce ѫ
  • 4,195
  • 12
  • 39
  • 49
tdjprog
  • 706
  • 6
  • 11