1

I'm having a really hard time with this so I wanted to see if anyone here would be able to help me think through this.

I'm attempting to attach a scroll listener to an SVG path between a specific pixel range, so it will draw back and forth when you scroll up and down.

I've been researching this a few days and I've found topics on converting the stroke dashoffset progress to a percentage based off of the top of the window or height of the document as a single number, but can't seem to wrap my noggin around how I would apply that same logic to a specified pixel range.

How I would have the stroke-dasharray of the svg path be at its starting value (3000) at the start of the range of pixels (say 2500px to 3000px from the top of the document.) and the end value of stroke dasharray be 6000 (it's end value) at the end of the pixel range? I really hope this isn't a stupidly easy issue I'm just staring at for too long. Here is what I have:

var path = document.querySelector('path');

var length = path.getTotalLength();

var triggerPadding = 600;

var startLocation = document.querySelector('.scroll-container').getBoundingClientRect()

var bodyRect = document.body.getBoundingClientRect();

var elemRect = document.querySelector('.scroll-container').getBoundingClientRect();

var elementYLocation = elemRect.top - bodyRect.top;

var elementBottomYLocation = Math.round((elemRect.bottom - bodyRect.top) - triggerPadding);

var startTrigger = Math.round(elementYLocation - triggerPadding);

path.style.strokeDasharray = length + ' ' + length;

path.style.strokeDashoffset = length;

// 

function animationScroll(currentY, startY, endY) {
    console.log({ currentY, startY, endY })
    if (currentY >= startY) {
        //do animation

    }
}

window.addEventListener('scroll', function () {
    const currentY = window.pageYOffset || window.scrollY;

    animationScroll(currentY, startTrigger, elementBottomYLocation);

}, false);

Any help at all on the equation I would need to do this would be so appreciated. Thank you in advance.

Dave Funk
  • 77
  • 8
  • 1
    Basically, you want a linear transformation from one interval to another - https://math.stackexchange.com/questions/914823/shift-numbers-into-a-different-range – CBroe Jan 08 '21 at 08:09
  • @CBroe, Would you be able to explain what you mean in a little more detail and for the layman? I'm not sure I follow. I'm actually a junior developer trying to complete this task for work, been banging my head against this one for a few days now. As much as I wish I understood a word of what was on that link you referenced I unfortunately do not. – Dave Funk Jan 08 '21 at 08:35
  • 1
    I think I misunderstood what you meant with _“at the start of the range of pixels (say 2500px to 3000px from the top of the document.)”_ - so 2500 to 3000 is supposed to be your whole pixel range here, and not a 500px section at the start where you want the stroke array offset to be constant? Well, then it is really as simple as that - you want to transform a given value that is somewhere in the range from 2500 to 3000, into a corresponding value in the range of 3000 to 6000. The math for that is explained in the question I linked to. – CBroe Jan 08 '21 at 08:50
  • 1
    There is also an example with concrete values here, https://math.stackexchange.com/a/3585118 – CBroe Jan 08 '21 at 08:50
  • I'm really not capable of interpreting that level of math, like I was saying I'm a junior developer. Is this something that is going to not be doable by myself without a library? – Dave Funk Jan 08 '21 at 09:02
  • 1
    But you got the formula right there, so even if you don’t really understand it, all you have to do is insert the values. a=2500, b=3000 is your starting interval, c=3000 and d=6000 is your target interval. t is your current scroll position. Say you have currently scrolled to the exact middle of your start interval, t=2750. Then the stroke offset should be at the middle of its interval, so 4500, right? So, `f(2750) = 3000 + ( (6000 - 3000) / (3000 - 2500) ) * (2750 - 2500)` … et voila, the result of that is `4500`. – CBroe Jan 08 '21 at 09:15
  • I think that It took me seeing it written like this next to the formula to start to piece it together what was happening here and what I was supposed to sub in and where. I really appreciate you taking the time to help explain this. – Dave Funk Jan 08 '21 at 09:33

2 Answers2

1

You need to compute the scrollRatio, then multiply it by the desired range of the svg property you wish to animate.

Below, I've illustrated an example that waits for a certain element to enter the viewport, then begins animating, executing animation keyframes in-sync with scroll, and stops animating once the element leaves the viewport.

const triggerEl = document.getElementById('trigger');

const animateSvg = () => {
  const rect = triggerEl.getBoundingClientRect();
  const scrollPosition = window.scrollY;
  let scrollRatio = (rect.top / window.innerHeight); 
  scrollRatio = Math.max(scrollRatio, 0);
  scrollRatio = Math.min(scrollRatio, 1);
  triggerEl.innerHTML = `Trigger Element ${(scrollRatio * 100).toFixed(2)}%`;
  for (const path of document.querySelectorAll('svg path')) { 
      // Min & Max can be hardcoded to fit your use-case
      const dashOffsetMin = 0;
      const dashOffsetMax = path.getTotalLength();
      const dashOffsetRange = dashOffsetMax 
        - dashOffsetMin;
      // Set stroke-dashoffset
      path.style.strokeDashoffset = dashOffsetMin
        + dashOffsetRange * scrollRatio;
  }
}

animateSvg();
document.addEventListener('scroll', animateSvg)
body {
  position: relative;
  background:linear-gradient(135deg, #5b247a 0%,#1bcedf 100%);
  min-height: 300vh;
}

h1 {
  font-family: arial;
  font-weight: 900;
  color: white;
  width: 100%;
  text-align: center;
}

svg {
  position: fixed;
  top: 0;
  color: white;
}

svg path {
 transition: stroke-dashoffset 250ms linear;
 box-shadow: 0px 0px 15px white;
}

#trigger {
  position: absolute;
  z-index: 2;
  top: 100vh;
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  color: white;
  font-family: arial;
  font-weight: 900;
  height: 20vh;
  background: linear-gradient(to bottom right, rgba(0,0,25,0.2), rgba(0,0,25,0.5));
  border-radius: 5px;
  backdrop-filter: blur(5px);
  box-shadow: 0px 15px 10px -10px rgba(0,0,0,0.4);
}
<div id='trigger'>
Trigger Element
</div>
<h1>Scroll!</h1>
<svg viewBox="0 0 280 100">
  <g fill="none" fill-rule="evenodd" stroke="currentColor" stroke-width="1" class="lines">
    <path class="el" d="M58 80V50.12C57.7 41.6 51.14 35 43 35a15 15 0 0 0 0 30h7.5v15H43a30 30 0 1 1 0-60c16.42 0 29.5 13.23 30 29.89V80H58z" style="stroke-dashoffset: 0px;" stroke-dasharray="316.85528564453125"></path>
    <path class="el" d="M73 80V20H58v60h15z" style="stroke-dashoffset: 0px;" stroke-dasharray="150"></path>
    <path class="el" d="M58 80V49.77C58.5 33.23 71.58 20 88 20a30 30 0 0 1 30 30v30h-15V50a15 15 0 0 0-15-15c-8.14 0-14.7 6.6-15 15.12V80H58zm75 0V20h-15v60h15z" style="stroke-dashoffset: 0px;" stroke-dasharray="441.1739501953125"></path>
    <path class="el" d="M118 80V49.77C118.5 33.23 131.58 20 148 20a30 30 0 0 1 30 30v30h-15V50a15 15 0 0 0-15-15c-8.14 0-14.7 6.6-15 15.12V80h-15zm-7.5-60a7.5 7.5 0 1 1-7.48 8v-1c.25-3.9 3.5-7 7.48-7z" style="stroke-dashoffset: 0px;" stroke-dasharray="338.3053894042969"></path>
    <path class="el" d="M133 65a15 15 0 0 1-15-15v-7.5h-15V50a30 30 0 0 0 30 30V65zm30 15V49.77C163.5 33.23 176.58 20 193 20a30 30 0 0 1 30 30v30h-15V50a15 15 0 0 0-15-15c-8.14 0-14.7 6.6-15 15.12V80h-15z" style="stroke-dashoffset: 0px;" stroke-dasharray="406.8699035644531"></path>
    <path class="el" d="M238 65a15 15 0 0 1 0-30c8.1 0 14.63 6.53 15 15h-15v15h30V49.89C267.5 33.23 254.42 20 238 20a30 30 0 0 0 0 60V65z" style="stroke-dashoffset: 4.49556px;" stroke-dasharray="301.8561706542969"></path>
    <path class="el" d="M260.48 65a7.5 7.5 0 1 1-7.48 8v-1c.26-3.9 3.5-7 7.48-7z" style="stroke-dashoffset: 6.61913px;" stroke-dasharray="47.128875732421875"></path>
  </g>
</svg>
mccallofthewild
  • 521
  • 2
  • 6
0

User @CBroe answers and provides/walks through the required formula to calculate a linear transformation of two number ranges.

f(t) = c + ( (d - c) / (b - a) ) * (t - a);

Dave Funk
  • 77
  • 8