skip to Main Content

I wanted to create a CSS animation on a page scroll. The arrow will move to a snake path on the page scroll and if possible change the icon as well on every new section.

I have a few examples:

codepen.io/yesvin/pen/XymwvX

codepen.io/gkando/pen/LYEvjOv

But unable to create a path according to design and change icon on every section 1-2 arrow, 2-3 circle, 3-4 another icon, etc.

I am attaching the path as well for reference.

enter image description here

enter image description here

my code so far:

window.addEventListener('scroll', function() {
  let l = Path_440.getTotalLength();
  let dasharray = l;
  let dashoffset = l;
  e = document.documentElement;
  theFill.setAttributeNS(null, "stroke-dasharray", l);
  theFill.setAttributeNS(null, "stroke-dashoffset", l);
  dashoffset = l - window.scrollY * l / (e.scrollHeight - e.clientHeight);
  //console.log('window.scrollY', window.scrollY, 'scrollTop', e.scrollTop, 'scrollHeight', e.scrollHeight, 'clientHeight', e.clientHeight, 'dash-offset', dashoffset);
  theFill.setAttributeNS(null, "stroke-dashoffset", dashoffset);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<svg width="246" height="2990" viewBox="0 0 246 2990" fill="none" xmlns="http://www.w3.org/2000/svg">
                <defs>
                <path id="Path_440" d="M210.001 1.5C210.001 1.5 41.0015 324.5 6.50082 617.5C-27.004 902.042 182.501 1032.5 240.001 1313C275.095 1484.2 29.8527 1661 41.0008 1914.5C50.4751 2129.94 230.431 2237.5 235.001 2431.5C240.42 2661.59 41.0008 2988 41.0008 2988" stroke="#F39029" stroke-width="4" stroke-dasharray="20 10"/>
                </defs>
                <use xlink:href="#Path_440" stroke="#000" stroke-width="4" stroke-dasharray="1"/>
                <use id="theFill" xlink:href="#Path_440" stroke="#000" stroke-width="1"/>
            </svg>

2

Answers


  1. This isn’t exactly CSS Animation but produces the similar sort of effect. If you are open to something like this, this could be further polished.

    I am just using the HTML Canvas to plot a Sin curve. Now, you do have to know some high school level trigonometry…

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title>Path</title>
        <style>
          body,
          html,
          canvas {
            height: 99.6%;
            width: 99.6%;
          }
          canvas {
            background-color: black;
          }
        </style>
      </head>
      <body>
        <canvas id="c"></canvas>
      </body>
      <script src="canvas.js"></script>
    </html>
    

    And the canvas.js has,

    let canvas = document.getElementById('c');
    canvas.width = document.body.clientWidth;
    canvas.height = document.body.clientHeight;
    let ctx = canvas.getContext('2d');
    ctx.fillStyle = '#FF0000';
    let delta = 100;
    let n = 100;
    addEventListener('wheel', (e) => {
      let c = 150;
      for (let i = n; i <= n; i++) {
        let x = (i * c * 2 * Math.PI) / (300*delta);
        let y = -c * Math.sin(x / c) + 600;
        ctx.fillRect(y, x, 3, 3);
      }
      n += delta;
    });
    

    And this is how it looks,

    enter image description here

    Login or Signup to reply.
  2. Since you have a way of calculating scrollPercent, which is window.scrollY / (docElt.scrollHeight - docElt.clientHeight), you can use that to roughly calculate the distance along the path so far with scrollPercent * Path_440.getTotalLength(). Then with that distance, use getPointAtLength(distance) to get a DOMPoint on the path, which can be used to update the position and rotation of the arrow icon as shown below. You can also add logic in the event handler to update the icon based on scrollPercent.

    const pathLength = Path_440.getTotalLength();
    const docElt = document.documentElement;
    
    function clamp(min, val, max) {
      return Math.min(Math.max(min, val), max);
    }
    
    updatePath();
    window.addEventListener("scroll", () => updatePath());
    
    function updatePath() {
      const scrollPercent =
        window.scrollY / (docElt.scrollHeight - docElt.clientHeight);
      const drawLength = clamp(0, pathLength * scrollPercent, pathLength);
    
      // use if you want the dashes to 'fill in' on scroll
      // const rest = pathLength - drawLength;
      // theFill.style.strokeDasharray = `${drawLength} ${rest}`;
    
      // update icon position and rotation
      const [l1, l2] =
      drawLength < pathLength ?
        [drawLength, drawLength + 0.1] :
        [drawLength - 0.1, drawLength];
      const p1 = Path_440.getPointAtLength(l1);
      const p2 = Path_440.getPointAtLength(l2);
      const angle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
      arrow.style.left = `${p1.x}px`;
      arrow.style.top = `${p1.y}px`;
      arrow.style.rotate = `${angle}rad`;
    
      // update icon image (here I just change the fill)
      if (scrollPercent < 0.2) {
        arrow.style.fill = "black";
      } else if (scrollPercent < 0.4) {
        arrow.style.fill = "red";
      } else if (scrollPercent < 0.6) {
        arrow.style.fill = "blue";
      } else if (scrollPercent < 0.8) {
        arrow.style.fill = "magenta";
      } else if (scrollPercent < 1) {
        arrow.style.fill = "goldenrod";
      }
      if (scrollPercent >= 1) {
        // reached end
        arrow.style.fill = "lime";
      }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <svg width="246" height="2990" viewBox="0 0 246 2990" fill="none" xmlns="http://www.w3.org/2000/svg">
      <defs>
        <path id="Path_440" d="M210.001 1.5C210.001 1.5 41.0015 324.5 6.50082 617.5C-27.004 902.042 182.501 1032.5 240.001 1313C275.095 1484.2 29.8527 1661 41.0008 1914.5C50.4751 2129.94 230.431 2237.5 235.001 2431.5C240.42 2661.59 41.0008 2988 41.0008 2988" stroke-width="4" stroke="#F39029" />
      </defs>
      <use xlink:href="#Path_440" stroke-dasharray="20 10" />
      <!-- use if you want the dashes to 'fill in' on scroll -->
      <!--   <use id="theFill" xlink:href="#Path_440" /> -->
    </svg>
    <svg id="arrow" style="position: absolute; left: 0; top: 0;" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill-rule="evenodd" clip-rule="evenodd">
      <path d="M21.883 12l-7.527 6.235.644.765 9-7.521-9-7.479-.645.764 7.529 6.236h-21.884v1h21.883z" />
    </svg>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search