Reputation: 77
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.
Upvotes: 1
Views: 787
Reputation: 520
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>
Upvotes: 1
Reputation: 77
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);
Upvotes: 0