DEV Community

Evgenia Milcheva
Evgenia Milcheva

Posted on • Edited on

Connect sections by 'drawing' dashed SVG paths on scroll

The company I work for Argo Ventures Digital Ltd. will make proud 4 years this April and we are baking something special -- total revamp of the existing website with modern look, animations, industry know-how-s demonstrated and etc. We'd like to showcase what we've learned over the years as website professionals not only as portfolio but as look and feel. I've been given immerse freedom to try some neat stuff that may come to my mind in terms of UX and animations and I feel very impatient to show you one of the things I've been tinkering lately.

Below you'll find my "draw on scroll with SVG" approach that is now on our 'Our Services' page so ... make yourself comfortable and happy reading 📖 ☕

What we'll be coding (shush 🤫, don't tell anyone if you see a typo, this is still prototype preview with dummy content)
https://www.loom.com/share/9e5de765661d453fa2d4f954c2b07246

The approach:

For those of you that are wondering ... first of all drawing on scroll is not new - there are many pretty examples online that leave users impressed like this one for example - http://www.nytimes.com/newsgraphics/2013/10/13/russia/index.html or this one - https://www.lemonade.com/giveback-2019
Chris(@chriscoyier ) did a great job on CSS tricks explaining in-depth SVG line animations [https://css-tricks.com/svg-line-animation-works/].

⚡ There are two challenging/advanced points that we are battling with our approach:

  1. Most of the examples online demonstrate a single SVG path that 'draws' respecting the user scroll. This is relatively easy especially if the path size is 100% the one of the container.
  2. Everybody knows that increasing SVG path length can be done on scroll (event if you don't stackoverflow knows) but a dashed one is a whole new chapter!

What we'll be building togeter today are several dashed lines that are supposed to connect specific page sections and animate independently on scroll i.e. immitating a smooth connected transition for readers from one section to another.

Getting to know the size of the dashed lines

For the purpose of this demo let's say that the SVG paths are already inlined in the HTML right inside the sections that we'll be connecting. There are many ways to dynamically inject/add the SVG-s (even if they are external files) to the DOM but this is out of the scope of this article.

As I previously mentioned the aim here is for the dashed lines to connect sections. However our sections are not with same height (the content is still under construction!). Even more, we'd like our lines to abe adaptive to any content that sections might have!
Our lines will appear as starting and ending in the two adjacent sections images, but this is the visual part. For the technical part let's say that the start point will be the center of the first image and end point is the center of the second section image.
A keen eye (if watched the video above) will see that the center of those images is the center of their sections. Let's calculate our lines widths:

    // the sections containing the lines
    let $animatableSections = $(".section-process-step");
    let sectionHeights = [];
    let linesWidth = [];

    $animatableSections.each(function() {
     //offsetHeight represents the viewable height of an element in pixels, including padding, border and scrollbar
      sectionHeights.push(this.offsetHeight);
    });

    for (let index = 0; index < sectionHeights.length; index++) {
      let currElementHeight = sectionHeights[index];
      let nextElementHeight = sectionHeights[index + 1];
      //we want the path to start at the center that's why we devide the height by 2
      linesWidth.push(currElementHeight / 2 + nextElementHeight / 2);
    }

Positioning the lines

I should admit here that I lost a lot of time thinking that this can be done fairly easy with having the SVG-s added as background images with specific background-size where the width is the linesWidth we already calc-ed and the height is 100%. This worked 100% but ... not with the animation of the lines because as a background img, it isn't part of the DOM and you can't manipulate it. Something very interesting here is an unofficial proposal(https://tabatkins.github.io/specs/svg-params/) to W3C for "parameters" to SVG, which are a method of setting CSS custom properties in an external SVG document via the fragment on the referencing URL.

💡 Another thing I learned but kind of surprised me is that even though browsers agreed by spec to allow setting custom units to data attributes this simply does nothing in none of the modern browsers:
background-size: attr(data-width px) 100% ;
Hopefully this will be available in future though!

So I ended with the old classic way of having the SVG container div positioned absolute and rotated with 40deg so that our line looks as desired:

    //we skip the latter section as it won't have path to draw there
    $.each($(".section-process-step:not(:last-child) .line"), function(
      index,
      value
    ) {
      $(this).css("width", linesWidth[index]);

      if (index % 2 !== 0) {
        $(this).addClass("line--even");
      }
    });

The CSS is trivial ... I'm positioning the lines absolute with the section-process-step container positioned relative. By adding line--even on even lines (as the name implies) I'm just rotating them with -40deg so that naturally show as if connecting the next image.

The nature of the lines

The sweetest part will come with the next section but first let me introduce you the nature of the SVG-s used as there is the trick to the dashed line that we will be animating. As you probably saw we are not just connecting the sections, we have a light dashed line already connecting them and what we want is to fill it in with blueish color respecting the scroll direction.
So... I ended up with three absolutely equal path geometries on top of each other where we will animate only one of them. The first two lines are doing the work practically and the latter is for cosmetic purposes - the dashed effect.
In the snippet below.p-line-fill-2 is the default light one and the one that practically gets animated, that's why it differs with 'js-fill-line' class, .p-line-fill-1 is the blueish one. The third has its stroke set exactly as our body fill and is dashed.
Please mind that the light path is actually doing the work, not the blueish one! The effect of filling in blueish path is just a matter of light one decreasing its length respecting the scroll direction thus leaving the blueish one to cover the gap behind.

<svg class="dashed" viewBox="0 0 355 103" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <path class="p-line p-line--fill p-line--fill-1" d="M-6.75432109,1.54825684 C113.245679,110.326533 233.245679,130.359459 353.245679,61.647035"></path>
    <path class="p-line p-line--fill p-line--fill-2 js-line--fill" d="M-6.75432109,1.54825684 C113.245679,110.326533 233.245679,130.359459 353.245679,61.647035" style="stroke-dasharray: 394.564;"></path>
     <path class="p-line p-line--dashed" d="M-6.75432109,1.54825684 C113.245679,110.326533 233.245679,130.359459 353.245679,61.647035"></path>
</svg>

They are styled as follows:

.dashed {
  height: 100%;

   //the one to be animated
  .p-line--fill-1 {
    stroke: $info;
    stroke-width: 3;
  }

  //the always visible one
  .p-line--fill-2 {
    stroke: $light;
    stroke-width: 4;
  }

   //third one
  .p-line--dashed {
    stroke: $body-bg;
    stroke-dasharray: 6;
    stroke-width: 4;
  }

  .p-line {
    fill: none;
  }
}

Let's 'draw' on scroll

We have already calculated the width for the line containers (see the lineWidths array above). But how much is the length of the path to be animated?

The SVGPathElement.getTotalLength() method returns the user agent's computed value for the total length of the path in user units.

let path = $(this).find(".js-line--fill")[0];
length = path.getTotalLength();

Now we need to know if our current section-process-step that contains the line to be animated is visible and to calculate the percentage of distance this section is from the top of the viewport:

var distanceFromTop = $(this).offset().top - $(window).scrollTop();
var percentDone = 1 - distanceFromTop / $(window).height();
var draw = length * percentDone;

We are almost done!
Having the total length of the path and the draw variable (that will change on scroll) we can do the last step -- set the strokeDashOffset as follows:

   // Reverse the drawing (when scrolling upwards)
        if ($(this).find(".line").hasClass("line--even")) {
          path.style.strokeDashoffset = -(length - draw);
        } else {
          path.style.strokeDashoffset = length - draw;
        }

For the effect to happen, please mind that we need to have the strokeDashArray equal to the length of the path! This is set initially (out of the scroll function)

path.style.strokeDasharray = length;

Still confused? Then tweak the interactive demo in this(https://jakearchibald.com/2013/animated-line-drawing-svg/) blogpost to understand the strokeDashoffset and strokeDasharray relation.

Now everytime you scroll, the new height percentage is calculated and that amount of the line is drawn accordingly!

One last thing to have in mind - please mind that executing a function for every pixel we scroll is very expensive in time and can use a lot of memory.There are many approaches online (even here on Dev.to) on how to avoid that so feel free to adopt the one that works for you (hints:debouncing, requestAnimationFrame and etc.)

P.S. The full source code will be kept private, don't ask for codepen-s as it is a copyright of Argo Ventures Digital Ltd ©️

Please drop a 🦄 / 🧡 or just follow if you enjoyed and feel free to share your opinion! Thank you 😊

Top comments (0)