Search code examples
javascripthtmlsvgscroll

How to animate element along svg path on scroll?


I have an issue: I have to change element position&angle on scroll event. Like my webpage is a long road with background - and I need to animate car movements along this path during the scroll.

I good explanation is here: http://prinzhorn.github.io/skrollr-path/ - a perfect solution, that meets my requirements. But Unfortunately it's extremely outdated.

Maybe somebody has an up to date solution-library? Or code-ideas, how to animate element via svg-path with page scrolling?

Also i tried http://scrollmagic.io/examples/expert/bezier_path_animation.html - but it's not something that I need, because my path is difficult. Not just a couple of circles.


Solution

  • Here is some vanilla Javascript that moves a "car" along a path according to how much the page has scrolled.

    It should work in all (most) browsers. The part that you may need to tweak is how we get the page height (document.documentElement.scrollHeight). You may need to use different methods depending on the browsers you want to support.

    function positionCar()
    {
      var  scrollY = window.scrollY || window.pageYOffset;
      var  maxScrollY = document.documentElement.scrollHeight - window.innerHeight;
      var  path = document.getElementById("path1");
      // Calculate distance along the path the car should be for the current scroll amount
      var  pathLen = path.getTotalLength();
      var  dist = pathLen * scrollY / maxScrollY;
      var  pos = path.getPointAtLength(dist);
      // Calculate position a little ahead of the car (or behind if we are at the end), so we can calculate car angle
      if (dist + 1 <= pathLen) {
        var  posAhead = path.getPointAtLength(dist + 1);
        var  angle = Math.atan2(posAhead.y - pos.y, posAhead.x - pos.x);
      } else {
        var  posBehind = path.getPointAtLength(dist - 1);
        var  angle = Math.atan2(pos.y - posBehind.y, pos.x - posBehind.x);
      }
      // Position the car at "pos" totated by "angle"
      var  car = document.getElementById("car");
      car.setAttribute("transform", "translate(" + pos.x + "," + pos.y + ") rotate(" + rad2deg(angle) + ")");
    }
    
    function rad2deg(rad) {
      return 180 * rad / Math.PI;
    }
    
    // Reposition car whenever there is a scroll event
    window.addEventListener("scroll", positionCar);
    
    // Position the car initially
    positionCar();
    body {
      min-height: 3000px;
    }
    
    svg {
      position: fixed;
    }
    <svg width="500" height="500"
         viewBox="0 0 672.474 933.78125">
      <g transform="translate(-54.340447,-64.21875)" id="layer1">
       <path d="m 60.609153,64.432994 c 0,0 -34.345187,72.730986 64.649767,101.015256 98.99494,28.28427 321.2285,-62.62946 321.2285,-62.62946 0,0 131.31984,-52.527932 181.82746,16.16244 50.50763,68.69037 82.04198,196.41856 44.44671,284.86302 -30.25843,71.18422 -74.75128,129.29952 -189.90867,133.34013 -115.15739,4.04061 -72.73099,-153.54318 -72.73099,-153.54318 0,0 42.42641,-129.29953 135.36044,-119.198 92.93404,10.10152 -14.14213,-129.29953 -141.42135,-94.95434 -127.27922,34.34518 -183.84777,80.8122 -206.07112,121.2183 -22.22336,40.40611 -42.06243,226.23742 -26.26397,305.06607 8.77013,43.75982 58.20627,196.1403 171.72594,270.72088 73.8225,48.50019 181.82745,2.02031 181.82745,2.02031 0,0 94.95434,-12.12183 78.7919,-155.56349 -16.16244,-143.44166 -111.68403,-138.77778 -139.9683,-138.77778 -28.28427,0 83.39976,-156.18677 83.39976,-156.18677 0,0 127.27922,-189.90867 107.07617,16.16245 C 634.3758,640.21994 864.69058,888.71747 591.94939,941.2454 319.2082,993.77334 -16.162441,539.20469 153.54319,997.81395"
        id="path1"
        style="fill:none;stroke:#ff0000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"/>
    
    
        <path id="car" d="M-15,-10 L15,0 L -15,10 z" fill="yellow" stroke="red" stroke-width="7.06"/>
      </g>
    </svg>