Graeme Bryson
Graeme Bryson

Reputation: 193

Revealing SVG on section scroll

I'm using the function in this JSFiddle to fetch the percentage of an element seen within the viewport on scroll (capped at 100 once user scrolls beyond it):

// Track percentage of section seen within viewport
// Assign target section to var
const element = document.getElementById('section--example');

// Calculate percentage of section in viewport
const percentageSeen = () => {
    
    // Get the relevant measurements and positions
    const viewportHeight = window.innerHeight;
    const scrollTop = window.scrollY;
    const elementOffsetTop = element.offsetTop;
    const elementHeight = element.offsetHeight;
    
    // Calculate percentage of the element that's been seen
    const distance = (scrollTop + viewportHeight) - elementOffsetTop;
    const percentage = Math.round(distance / ((viewportHeight + elementHeight) / 100));
    
    // Restrict range between 0 — 100
    return Math.min(100, Math.max(0, percentage));

}

Then, I'm attempting to use that percentageSeen value in the following function to animate an SVG path into view on scroll:

// Get a reference to the <path>
var path = document.querySelector('#path--example');

// Get length of SVG path
var pathLength = path.getTotalLength();

// Create dashes to match the length of the SVG path
path.style.strokeDasharray = pathLength + ' ' + pathLength;

// Initially offset dashes to hide SVG entirely
path.style.strokeDashoffset = pathLength;

path.getBoundingClientRect();

// Animate SVG on section scroll
window.addEventListener('scroll', function(e) {
 
    // Fetch percentage seen 
    var scrollPercentage = percentageSeen;

    // Length to offset the dashes
    var drawLength = pathLength * scrollPercentage;

    // Draw in reverse
    path.style.strokeDashoffset = pathLength - drawLength;
  
});

I've created a JSFiddle here with my progress — I can log the visible percentage to the console without issue, but that value doesn't seem to be accepted when used in the above scroll function. I can also reveal the SVG based on page height (commented out in the JSFiddle), but not the section height. Can anyone help point me in the right direction that'll allow the use of the percentageSeen var in the scroll function?

Upvotes: 0

Views: 970

Answers (1)

vqf
vqf

Reputation: 2628

  • The main problem is that percentageSeen is a percentage. When you multiply pathLength by percentageSeen you get a value that is 100 times larger than the value you want.
  • Also, you were calling the function without parentheses, which meant that scrollPercentage did not store the result, but the function itself.
  • I think you want the element to appear as the user scrolls. In that case, it is simpler to set strokeDasharray to 0 pathLength and then drawLength (pathLength-drawLenght).
  • You are capping percentageSeen at 100, which means that the last capping procedure is not necessary (also, you set the cap at 0.99 and you should have set it to 99).

I have forked the fiddle with a working example. As you can see, percentageSeen is never lower than 38 or larger than 61. You may want to fix the calculation.

// Animation — Stitch [Scroll]
// Reference: https://css-tricks.com/scroll-drawing/
// --------------------------------------------------

// Track percentage of animation container (section) in viewport
// Assign target section to var
const element = document.getElementById('section--example');

// Log percentage to console
const logPercentageSeen = () => {
    //console.log(percentageSeen());
}

// Re-run 'logPercentageSeen' on scroll
window.addEventListener('scroll', logPercentageSeen);

// Calculate percentage of section in viewport
const percentageSeen = () => {
    // Get the relevant measurements and positions
    const viewportHeight = window.innerHeight;
    const scrollTop = window.scrollY;
  const elementOffsetTop = element.offsetTop;
  const elementHeight = element.offsetHeight;
    // Calculate percentage of the element that's been seen
  const distance = (scrollTop + viewportHeight) - elementOffsetTop;
  const percentage = Math.round(distance / ((viewportHeight + elementHeight) / 100));
    // Restrict the range to between 0 and 100
  return Math.min(100, Math.max(0, percentage));
}

// Log the initial value before any scrolling has happened
logPercentageSeen();




// Get a reference to the <path>
var path = document.querySelector('#path--example');

// Get length of path
var pathLength = path.getTotalLength();

// Make very long dashes (the length of the path itself)
path.style.strokeDasharray = '0 ' + pathLength;

// Offset the dashes so the it appears hidden entirely
path.style.strokeDashoffset = 0;

// Jake Archibald says so
// https://jakearchibald.com/2013/animated-line-drawing-svg/
path.getBoundingClientRect();

// When the page scrolls...
window.addEventListener('scroll', function(e) {
 
    // What % down is it? 
    var scrollPercentage = percentageSeen();
    
    // This runs, but is tied to the page height, NOT the section height
    // var scrollPercentage = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);

    // Length to offset the dashes
    var drawLength = pathLength * scrollPercentage / 100;
    

    // Draw in reverse
    var rest = pathLength - drawLength;
    path.style.strokeDasharray = drawLength + ' ' + rest;

    // When complete, remove the dash array, otherwise shape isn't quite sharp
    // Accounts for fuzzy math
    
  
});
<section id="section--example">
  
  <h1>Section heading</h1>
  <p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</p>
  
  <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="300" height="300"
     viewBox="0 0 900 512" style="enable-background:new 0 0 900 512;" xml:space="preserve">
    <style type="text/css">
      .st0{fill:none;stroke:#000000;stroke-width:1.24;stroke-miterlimit:10;}
    </style>
    <path id="path--example" class="st0" d="M30.5,58.3c0,0,60.4-124,372,4.3c109.4,45,230.4,82.8,350.2,76.8c33.5-1.7,76.9-4.2,101.8-28.6
      c25.6-25.1,17.3-60.8-11-79.5c-63.6-42.1-221,5.4-354.8,74.4c-40.3,21.1-81.4,40.7-122.9,59.4c-57.5,25.9-116,53.7-179.2,62.3
      C153.5,232,67.6,238,61.5,189.2c-5.9-47,80-58.5,111.4-54.4c41,5.4,112.1,14,282.5,100.3c58.8,29.8,123.4,52.2,189.8,55.2
      c29.2,1.3,91.5,2.8,104.1-31.5c8.1-22.1-9.3-42.5-30-46.9c-23.5-5-49.5,1.5-72.1,8c-45,12.9-88.3,32.2-130.6,52.3
      c-26.8,12.8-53.4,26.2-79.5,40.5c0,0-87.3,49.6-140.2,49.6c-17.4,0-39.6-1.3-53.5-13.5c-13.1-11.6-13-30.9,2-41.3
      c36.8-25.8,86,3.2,119.9,20.7c25.1,13,49.8,26.9,73.9,41.7c0,0,45.3,28,96,28c13.6,0,30.7-2.8,32-19.8c1.1-15.6-16.7-25.9-30-28
      c-22.5-3.5-43.6,8.8-58,25.2c-41,46.4-18.3,114.3,25.9,133.7"/>
  </svg>

  <p>It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English.</p>

</section>

Upvotes: 1

Related Questions