• 18-19 College Green, Dublin 2
  • 01 685 9088
  • info@cunninghamwebsolutions.com
  • cunninghamwebsolutions
    Cunningham Web Solutions
    • Home
    • About Us
    • Our Services
      • Web Design
      • Digital Marketing
      • SEO Services
      • E-commerce Websites
      • Website Redevelopment
      • Social Media Services
    • Digital Marketing
      • Adwords
      • Social Media Services
      • Email Marketing
      • Display Advertising
      • Remarketing
    • Portfolio
    • FAQ’s
    • Blog
    • Contact Us
    MENU CLOSE back  

    HTML5 SVG Fill Animation With CSS3 And Vanilla JavaScript

    You are here:
    1. Home
    2. Web Design
    3. HTML5 SVG Fill Animation With CSS3 And Vanilla JavaScript
    Thumbnail for 22220
    Note Display Project Demo

    HTML5 SVG Fill Animation With CSS3 And Vanilla JavaScript

    HTML5 SVG Fill Animation With CSS3 And Vanilla JavaScript

    Marina Ferreira

    2019-01-04T13:00:19+01:00
    2019-01-04T12:33:22+00:00

    SVG stands for Scalable Vector Graphics and it is a standard XML-based markup language for vector graphics. It allows you to draw paths, curves, and shapes by determining a set of points in the 2D plane. Moreover, you can add twitch properties on those paths (such as stroke, color, thickness, fill, and more) in order to produce animations.

    Since April 2017, CSS Level 3 Fill and Stroke Module allow SVG colors and fill patterns to be set from an external stylesheet, instead of setting attributes on each element. In this tutorial, we will use a simple plain hex color, but both fill and stroke properties also accept patterns, gradients and images as values.

    Note: When visiting the Awwwards website, the animated note display can only be viewed with browser width set to 1024px or more.

    A demo of the final result (Large preview)

    • 🕹 Demo: Note Display Project
    • đź“‚ Repo: Note Display Repo

    Front-end is messy and complicated these days. That’s why we publish articles, printed books and webinars with useful techniques to improve your work. Even better: Smashing Membership with a growing selection of front-end & UX goodies. So you get your work done, better and faster.

    Explore Smashing Membership ↬

    File Structure

    Let’s start by creating the files in the terminal:

    🌹  mkdir note-display
    🌹  cd note-display
    🌹  touch index.html styles.css scripts.js
    

    HTML

    Here is the initial template that links both css and js files:

    <html lang="en">
    <head>
      <meta charset="UTF-8">
    
      <title>Note Display</title>
    
      <link rel="stylesheet" href="./styles.css">
    </head>
    <body>
      <script src="./scripts.js"></script>
    </body>
    </html>
    

    Each note element consists of a list item: li that holds the circle, the note value, and its label.

    List item element and direct children

    List item element and its direct children: .circle, .percent and .label. (Large preview)

    The .circle_svg is an SVG element, that wraps two elements. The first is the path to be filled while the second is the fill that will be animated.

    SVG elements

    SVG elements. SVG wrapper and circle tags. (Large preview)

    The note is separated into integer and decimals so different font sizes can be applied to them. The label is a simple . So, putting all of this together looks like this:

    <li class="note-display">
      <div class="circle">
        <svg width="84" height="84" class="circle__svg">
          <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
          <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
        </svg>
    
        <div class="percent">
          <span class="percent__int">0.</span>
          <span class="percent__dec">00</span>
        </div>
      </div>
    
      <span class="label">Transparent</span>
    </li>
    

    The cx and cy attributes define the circle’s x-axis and y-axis center point. The r attribute defines its radius.

    You have probably noticed the underscore/dash pattern in classes names. That’s BEM, which stands for block, element and modifier. It is a methodology that makes your element naming more structured, organized and semantic.

    Recommended reading: An Explanation Of BEM And Why You Need It

    To finish the template structures, let’s wrap the four list items in an unordered list element:

    Unordered list wrapper

    Unordered list wrapper holds four li children (Large preview)

    <ul class="display-container">
      <li class="note-display">
        <div class="circle">
          <svg width="84" height="84" class="circle__svg">
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
          </svg>
    
          <div class="percent">
            <span class="percent__int">0.</span>
            <span class="percent__dec">00</span>
          </div>
        </div>
    
        <span class="label">Transparent</span>
      </li>
    
      <li class="note-display">
        <div class="circle">
          <svg width="84" height="84" class="circle__svg">
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
          </svg>
    
          <div class="percent">
            <span class="percent__int">0.</span>
            <span class="percent__dec">00</span>
          </div>
        </div>
    
        <span class="label">Reasonable</span>
      </li>
    
      <li class="note-display">
        <div class="circle">
          <svg width="84" height="84" class="circle__svg">
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
          </svg>
    
          <div class="percent">
            <span class="percent__int">0.</span>
            <span class="percent__dec">00</span>
          </div>
        </div>
    
        <span class="label">Usable</span>
      </li>
    
      <li class="note-display">
        <div class="circle">
          <svg width="84" height="84" class="circle__svg">
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
          </svg>
    
          <div class="percent">
            <span class="percent__int">0.</span>
            <span class="percent__dec">00</span>
          </div>
        </div>
    
        <span class="label">Exemplary</span>
      </li>
    </ul>
    

    You must be asking yourself what the labels Transparent, Reasonable, Usable and Exemplary mean. The more acquainted you get with programming, you will realize that writing code is not only about making the application functional, but also assuring that it will be long-term maintainable and scalable. That is only achieved if your code is easy to change.

    “The acronym TRUE should help decide if the code you write will be able to accommodate change in the future or not.”

    So, next time, ask yourself:

    • Transparent: Are code changes consequences clear?
    • Reasonable: Is cost benefit worth it?
    • Usable: Will I be able to reuse it in unexpected scenarios?
    • Exemplary: Does it present high quality as an example for future code?

    Note: “Practical Object-Oriented Design in Ruby” by Sandi Metz explains TRUE along with other principles and how to achieve those through design patterns. If you haven’t taken some time to study design patterns yet, consider adding this book to your bedtime reading.

    CSS

    Let’s import the fonts and apply a reset to all items:

    
    @import url('https://fonts.googleapis.com/css?family=Nixie+One|Raleway:200');
    
    * {
      padding: 0;
      margin: 0;
      box-sizing: border-box;
    }
    

    The box-sizing: border-box property includes padding and border values into an element’s total width and height, so it’s easier to calculate its dimensions.

    Note: For a visual explanation on box-sizing, please read “Make Your Life Easier With CSS Box Sizing.”

    body {
      height: 100vh;
      color: #fff;
      display: flex;
      background: #3E423A;
      font-family: 'Nixie One', cursive;
    }
    
    .display-container {
      margin: auto;
      display: flex;
    }
    

    By combining the rules display: flex in the body and margin-auto in the .display-container, it’s possible to center the child element both vertically and horizontally. The .display-container element will also be a flex-container; that way, its children will be placed in the same row along the main axis.

    The .note-display list item will also be a flex-container. Since there are many children for centering, let’s do it through the justify-content and align-items properties. All flex-items will be centered along the cross and main axis. If you’re not sure what those are, check out the alignment section at “CSS Flexbox Fundamentals Visual Guide.”

    .note-display {
      display: flex;
      flex-direction: column;
      align-items: center;
      margin: 0 25px;
    }
    

    Let’s apply a stroke to the circles by setting the rules stroke-width, stroke-opacity and stroke-linecap that altogether style the stroke live ends. Next, let’s add a color to each circle:

    .circle__progress {
      fill: none;
      stroke-width: 3;
      stroke-opacity: 0.3;
      stroke-linecap: round;
    }
    
    .note-display:nth-child(1) .circle__progress { stroke: #AAFF00; }
    .note-display:nth-child(2) .circle__progress { stroke: #FF00AA; }
    .note-display:nth-child(3) .circle__progress { stroke: #AA00FF; }
    .note-display:nth-child(4) .circle__progress { stroke: #00AAFF; }
    

    In order to position the percent element absolutely, it’s necessary to know absolutely to what. The .circle element should be the reference, so let’s add position: relative to it.

    Note: For a deeper, visual explanation on absolute positioning, please read “How To Understand CSS Position Absolute Once And For All.”

    Another way of centering elements is to combine top: 50%, left: 50% and transform: translate(-50%, -50%); which position the element’s center at its parent’s center.

    .circle {
      position: relative;
    }
    
    .percent {
      width: 100%;
      top: 50%;
      left: 50%;
      position: absolute;
      font-weight: bold;
      text-align: center;
      line-height: 28px;
      transform: translate(-50%, -50%);
    }
    
    .percent__int { font-size: 28px; }
    .percent__dec { font-size: 12px; }
    
    .label {
      font-family: 'Raleway', serif;
      font-size: 14px;
      text-transform: uppercase;
      margin-top: 15px;
    }
    

    By now, the template should be looking like this:

    Finished initial template

    Finished template elements and styles (Large preview)

    Fill Transition

    The circle animation can be created with the help of two circle SVG properties: stroke-dasharray and stroke-dashoffset.

    “stroke-dasharray defines the dash-gap pattern in a stroke.”

    It can take up to four values:

    • When it’s set to an only integer (stroke-dasharray: 10), dashes and gaps have the same size;
    • For two values (stroke-dasharray: 10 5), the first is applied to dashes, second to gaps;
    • The third and forth forms (stroke-dasharray: 10 5 2 and stroke-dasharray: 10 5 2 3) will generate dashes and gaps in various sizes.

    Stroke dasharray property valuesstroke-dasharray property values (Large preview)

    The image to the left shows the property stroke-dasharray being set from 0 to 238px, which is the circle circumference length.

    The second image represents the stroke-dashoffset property that offsets the beginning of the dash array. It is also set from 0 to the circle circumference length.

    Stroke dasharray and dashoffset propertiesstroke-dasharray and stroke-dashoffset properties (Large preview)

    To produce the filling effect, we will set the stroke-dasharray to the circumference length, so that all of its length gets filled with a big dash and no gap. We’ll also offset it by the same value, so it gets “hidden”. Then the stroke-dashoffset will be updated to the corresponding note value, filling the stroke accordingly to the transition duration.

    The properties updating will be done in the scripts through CSS Variables. Let’s declare the variables and set the properties:

    .circle__progress--fill {
      --initialStroke: 0;
      --transitionDuration: 0;
      stroke-opacity: 1;
      stroke-dasharray: var(--initialStroke);
      stroke-dashoffset: var(--initialStroke);
      transition: stroke-dashoffset var(--transitionDuration) ease;
    }
    

    In order to set the initial value and update the variables, let’s start by selecting all .note-display elements with document.querySelectorAll. The transitionDuration will be set to 900 milliseconds.

    Then, we iterate through the displays array, select its .circle__progress.circle__progress--fill and extract the r attribute set in the HTML to calculate the circumference length. With that, we can set the initial --dasharray and --dashoffset values.

    The animation will occur when the --dashoffset variable gets updated by a 100ms setTimeout:

    const displays = document.querySelectorAll('.note-display');
    const transitionDuration = 900;
    
    displays.forEach(display => {
      let progress = display.querySelector('.circle__progress--fill');
      let radius = progress.r.baseVal.value;
      let circumference = 2 * Math.PI * radius;
    
      progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);
      progress.style.setProperty('--initialStroke', circumference);
    
      setTimeout(() => progress.style.strokeDashoffset = 50, 100);
    });
    

    To get the transition starting from the top, the .circle__svg element has to be rotated:

    .circle__svg {
      transform: rotate(-90deg);
    }
    

    Stroke properties transitionStroke properties transition (Large preview)

    Now, let’s calculate the dashoffset value — relative to the note. The note value will be inserted to each li item through the data-* attribute. The * can be switched for any name that suits your needs and it can then, be retrieved in JavaScript through the element’s dataset: element.dataset.*.

    Note: You can read more about the data-* attribute on MDN Web Docs.

    Our attribute will be called “data-note”:

    <ul class="display-container">
    + <li class="note-display" data-note="7.50">
        <div class="circle">
          <svg width="84" height="84" class="circle__svg">
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
          </svg>
    
          <div class="percent">
            <span class="percent__int">0.</span>
            <span class="percent__dec">00</span>
          </div>
        </div>
    
        <span class="label">Transparent</span>
      </li>
    
    + <li class="note-display" data-note="9.27">
        <div class="circle">
          <svg width="84" height="84" class="circle__svg">
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
          </svg>
    
          <div class="percent">
            <span class="percent__int">0.</span>
            <span class="percent__dec">00</span>
          </div>
        </div>
    
        <span class="label">Reasonable</span>
      </li>
    
    + <li class="note-display" data-note="6.93">
        <div class="circle">
          <svg width="84" height="84" class="circle__svg">
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
          </svg>
    
          <div class="percent">
            <span class="percent__int">0.</span>
            <span class="percent__dec">00</span>
          </div>
        </div>
    
        <span class="label">Usable</span>
      </li>
    
    + <li class="note-display" data-note="8.72">
        <div class="circle">
          <svg width="84" height="84" class="circle__svg">
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle>
            <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle>
          </svg>
    
          <div class="percent">
            <span class="percent__int">0.</span>
            <span class="percent__dec">00</span>
          </div>
        </div>
    
        <span class="label">Exemplary</span>
      </li>
    </ul>
    

    The parseFloat method will convert the string returned by display.dataset.note into a floating point number. The offset represents the percentage missing to reach the maximum score. So, for a 7.50 note, we would have (10 - 7.50) / 10 = 0.25, which means the circumference length should be offset by 25% of its value:

    let note = parseFloat(display.dataset.note);
    let offset = circumference * (10 - note) / 10;
    

    Updating the scripts.js:

    const displays = document.querySelectorAll('.note-display');
    const transitionDuration = 900;
    
    displays.forEach(display => {
      let progress = display.querySelector('.circle__progress--fill');
      let radius = progress.r.baseVal.value;
      let circumference = 2 * Math.PI * radius;
    + let note = parseFloat(display.dataset.note);
    + let offset = circumference * (10 - note) / 10;
    
      progress.style.setProperty('--initialStroke', circumference);
      progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);
    
    + setTimeout(() => progress.style.strokeDashoffset = offset, 100);
    });
    

    Stroke properties transition up to note valueStroke properties transition up to note value (Large preview)

    Before we move on, let’s extract the stoke transition to its own method:

    const displays = document.querySelectorAll('.note-display');
    const transitionDuration = 900;
    
    displays.forEach(display => {
    - let progress = display.querySelector('.circle__progress--fill');
    - let radius = progress.r.baseVal.value;
    - let circumference = 2 * Math.PI * radius;
      let note = parseFloat(display.dataset.note);
    - let offset = circumference * (10 - note) / 10;
    
    - progress.style.setProperty('--initialStroke', circumference);
    - progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);
    
    - setTimeout(() => progress.style.strokeDashoffset = offset, 100);
    
    + strokeTransition(display, note);
    });
    
    + function strokeTransition(display, note) {
    +   let progress = display.querySelector('.circle__progress--fill');
    +   let radius = progress.r.baseVal.value;
    +   let circumference = 2 * Math.PI * radius;
    +   let offset = circumference * (10 - note) / 10;
    
    +   progress.style.setProperty('--initialStroke', circumference);
    +   progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`);
    
    +   setTimeout(() => progress.style.strokeDashoffset = offset, 100);
    + }
    

    Note Value Increase

    There is still the note transition from 0.00 to the note value to be built. The first thing to do is to separate the integer and decimal values. We will use the string method split() (it takes an argument that determines where the string will be broken and returns an array containing both broken strings). Those will be converted to numbers and passed as arguments to the increaseNumber() function, along with the display element and a flag indicating if its an integer or a decimal.

    const displays = document.querySelectorAll('.note-display');
    const transitionDuration = 900;
    
    displays.forEach(display => {
      let note = parseFloat(display.dataset.note);
    + let [int, dec] = display.dataset.note.split('.');
    + [int, dec] = [Number(int), Number(dec)];
    
      strokeTransition(display, note);
    
    + increaseNumber(display, int, 'int');
    + increaseNumber(display, dec, 'dec');
    });
    

    In the increaseNumber() function, we select either the .percent__int or .percent__dec element, depending on the className, and also in case the output should contain a decimal point or not. We’ve set our transitionDuration to 900ms. Now, to animate a number from 0 to 7, for example, the duration has to be divided by the note 900 / 7 = 128.57ms. The result represents how long each increase iteration will take. This means our setInterval will fire every 128.57ms.

    With those variables set, let’s define the setInterval. The counter variable will be appended to the element as text and increased on each iteration:

    function increaseNumber(display, number, className) {
      let element = display.querySelector(`.percent__${className}`),
          decPoint = className === 'int' ? '.' : '',
          interval = transitionDuration / number,
          counter = 0;
    
      let increaseInterval = setInterval(() => {
        element.textContent = counter + decPoint;
        counter++;
      }, interval);
    }
    

    Infinite counter increaseInfinite counter increase (Large preview)

    Cool! It does increase the values, but it kind of does it forever. We need to clear the setInterval when the notes achieve the value we want. That is done with clearInterval function:

    function increaseNumber(display, number, className) {
      let element = display.querySelector(`.percent__${className}`),
          decPoint = className === 'int' ? '.' : '',
          interval = transitionDuration / number,
          counter = 0;
    
      let increaseInterval = setInterval(() => {
    +   if (counter === number) { window.clearInterval(increaseInterval); }
    
        element.textContent = counter + decPoint;
        counter++;
      }, interval);
    }
    

    Finished note display projectFinished project (Large preview)

    Now the number is updated up to the note value and cleared with clearInterval() function.

    That’s pretty much it for this tutorial. I hope you enjoyed it!

    If you feel like building something a bit more interactive, check out my Memory Game Tutorial created with Vanilla JavaScript. It covers basic HTML5, CSS3 and JavaScript concepts such as positioning, perspective, transitions, Flexbox, event handling, timeouts and ternaries.

    Happy coding! 🌹

    Smashing Editorial
    (dm, ra, il)

    From our sponsors: HTML5 SVG Fill Animation With CSS3 And Vanilla JavaScript

    Posted on 4th January 2019Web Design
    FacebookshareTwittertweetGoogle+share

    Related posts

    Archived
    22nd March 2023
    Archived
    18th March 2023
    Archived
    20th January 2023
    Thumbnail for 25788
    Handling Continuous Integration And Delivery With GitHub Actions
    19th October 2020
    Thumbnail for 25778
    A Monthly Update With New Guides And Community Resources
    19th October 2020
    Thumbnail for 25781
    Supercharge Testing React Applications With Wallaby.js
    19th October 2020
    Latest News
    • Archived
      22nd March 2023
    • Archived
      18th March 2023
    • Archived
      20th January 2023
    • 20201019 ML Brief
      19th October 2020
    • Thumbnail for 25788
      Handling Continuous Integration And Delivery With GitHub Actions
      19th October 2020
    • Thumbnail for 25786
      The Future of CX with Larry Ellison
      19th October 2020
    News Categories
    • Digital Marketing
    • Web Design

    Our services

    Website Design
    Website Design

    A website is an important part of any business. Professional website development is an essential element of a successful online business.

    We provide website design services for every type of website imaginable. We supply brochure websites, E-commerce websites, bespoke website design, custom website development and a range of website applications. We love developing websites, come and talk to us about your project and we will tailor make a solution to match your requirements.

    You can contact us by phone, email or send us a request through our online form and we can give you a call back.

    More Information

    Digital Marketing
    Digital Marketing

    Our digital marketeers have years of experience in developing and excuting digital marketing strategies. We can help you promote your business online with the most effective methods to achieve the greatest return for your marketing budget. We offer a full service with includes the following:

    1. Social Media Marketing

    2. Email & Newsletter Advertising

    3. PPC - Pay Per Click

    4. A range of other methods are available

    More Information

    SEO
    SEO Services

    SEO is an essential part of owning an online property. The higher up the search engines that your website appears, the more visitors you will have and therefore the greater the potential for more business and increased profits.

    We offer a range of SEO services and packages. Our packages are very popular due to the expanse of on-page and off-page SEO services that they cover. Contact us to discuss your website and the SEO services that would best suit to increase your websites ranking.

    More Information

    E-commerce
    E-commerce Websites

    E-commerce is a rapidly growing area with sales online increasing year on year. A professional E-commerce store online is essential to increase sales and is a reflection of your business to potential customers. We provide professional E-commerce websites custom built to meet our clients requirements.

    Starting to sell online can be a daunting task and we are here to make that journey as smooth as possible. When you work with Cunningham Web Solutions on your E-commerce website, you will benefit from the experience of our team and every detail from the website design to stock management is carefully planned and designed with you in mind.

    More Information

    Social Media Services
    Social Media Services

    Social Media is becoming an increasingly effective method of marketing online. The opportunities that social media marketing can offer are endless and when managed correctly can bring great benefits to every business.

    Social Media Marketing is a low cost form of advertising that continues to bring a very good ROI for our clients. In conjuction with excellent website development and SEO, social media marketing should be an essential part of every digital marketing strategy.

    We offer Social Media Management packages and we also offer Social Media Training to individuals and to companies. Contact us to find out more.

    More Information

    Cunningham Web Solutions
    © Copyright 2025 | Cunningham Web Solutions
    • Home
    • Our Services
    • FAQ's
    • Account Services
    • Privacy Policy
    • Contact Us