Home > OS >  Equivalent of CSS animation direction and infinite iteration count in rAF
Equivalent of CSS animation direction and infinite iteration count in rAF

Time:06-18

I am new to requestAnimationFrame and trying to understand how can I develop something that is done with CSS Keyframe Animation.

To elaborate, I can do the following with CSS

let curve = document.querySelector('#curve')
let dot = document.querySelector('#dot');
let d = curve.getAttribute('d');
dot.style.setProperty('--mp', '"'   d   '"')
#dot {
    offset-path: path(var(--mp));
    animation: move 3000ms infinite alternate ease-in-out;
}

@keyframes move {
    100% {
        offset-distance: 100%;
    }
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
  <g>
    <circle id="dot" r="10" cy="0.5" cx="0.5" style="fill: #dd1819; "></circle>
  </g>
  <path id="curve" d="M 29.928125,119.32884 C 59.868756,78.85387 99.789596,30.283895 138.66049,30.104515 c 80.46172,-0.37133 90.87185,137.794285 140.7729,129.699295 40.35037,-6.54568 49.90105,-80.94994 86.53937,-78.86548 33.24073,1.89117 22.2547,70.77048 73.14399,70.77048 39.92084,0 58.65264,-56.66496 99.80211,-56.66496 49.90105,0 69.86147,80.94995 99.8021,48.56997" style="fill: none; stroke:black; stroke-width: 1;" />
</svg>

animation-direction: alternate; makes sure it plays back and forth and animation-iteration-count: infinite; ensures it runs in an infinite loop.

I am trying to understand if the same can be achieved in rAF. This is what I tried so far

let u = 0;
let time = 3000;
let start = performance.now();
let curve = document.querySelector('#curve')
let totalLength = curve.getTotalLength();
let dot = document.querySelector('#dot');
const temp = [];
const move1 = () => {
    let p = curve.getPointAtLength(u * totalLength);
    dot.setAttribute("transform", `translate(${p.x}, ${p.y})`);
    if (u < 1) {
       u = (performance.now() - start) / time
       temp.push(p);
       requestAnimationFrame(move1)
    }

};
move1();
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
  <g>
    <circle id="dot" r="10" cy="0.5" cx="0.5" style="fill: #dd1819; "></circle>
  </g>
  <path id="curve" d="M 29.928125,119.32884 C 59.868756,78.85387 99.789596,30.283895 138.66049,30.104515 c 80.46172,-0.37133 90.87185,137.794285 140.7729,129.699295 40.35037,-6.54568 49.90105,-80.94994 86.53937,-78.86548 33.24073,1.89117 22.2547,70.77048 73.14399,70.77048 39.92084,0 58.65264,-56.66496 99.80211,-56.66496 49.90105,0 69.86147,80.94995 99.8021,48.56997" style="fill: none; stroke:black; stroke-width: 1;" />
</svg>

The above rAF animation is probably equivalent to

#dot {
    offset-path: path(var(--mp));
    animation: move 3000ms 1 forwards linear;
}

Is there any way to achieve what can be done in CSS

CodePudding user response:

A couple of things about working with requestAnimationFrame(…):

  1. It's best practice to use the timestamp argument given to the callback by requestAnimationFrame. There is no need to call performance.now() inside the function. But if you do, make sure to do it before the animation, otherwise the animation will lack behind any potential frame rate drops on slower PCs and especially phones.

    The callback method is passed a single argument, a DOMHighResTimeStamp similar to the one returned by performance.now(), which indicates the current time (based on the number of milliseconds since time origin).

  2. Take the given timestamp and get from it what you need. (This is usually done using outside variables acting as a configuration.) What iteration of the animation am I in? How far progressed has this iteration?

  3. This approach is valid but not the best: Best practice would be to just save the last time, last requestID (returned by requestAnimationFrame(…)) and last state (iterationWithFraction) of the animation outside. Then compare the last time with the time now in every step and add the normalized progress to the state. Then update the animation accordingly.
    Always comparing back to the very first timestamp start can lead to very confusing errors. (Just imagine time is set to 2000 mid animation.)

  4. If you want to end an animation, check out cancelAnimationFrame(requestID).

let time = 3000;
let start = performance.now();
let curve = document.querySelector('#curve')
let totalLength = curve.getTotalLength();
let dot = document.querySelector('#dot');
let d = curve.getAttribute('d');

const step = (timestamp) => {
    // get current time normalized with "time" (2s passed = 0.6666, 3s passed = 1, 4s passed = 1.33333, ...)
    const iterationWithFraction = (timestamp - start) / time;
    // get current iteration as an integer 
    const iteration = Math.floor(iterationWithFraction);
    // get current moment in iteration
    let fraction = iterationWithFraction % 1;
    // if iteration odd: invert fraction to get the desired alternation
    if (iteration % 2 == 1) fraction = 1 - fraction;
    // if you want an easing effect, you can do something like this now
    fraction = fraction<.5 ? 2*fraction**2 : -1 (4-2*fraction)*fraction;
    // do your animation
    const point = curve.getPointAtLength(fraction * totalLength);
    dot.setAttribute("transform", `translate(${point.x}, ${point.y})`);
    // get next AF as the last thing of the function
    requestAnimationFrame(step);
};
step(performance.now());
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 720">
  <g>
    <circle id="dot" r="10" cy="0.5" cx="0.5" style="fill: #dd1819; "></circle>
  </g>
  <path id="curve" d="M 29.928125,119.32884 C 59.868756,78.85387 99.789596,30.283895 138.66049,30.104515 c 80.46172,-0.37133 90.87185,137.794285 140.7729,129.699295 40.35037,-6.54568 49.90105,-80.94994 86.53937,-78.86548 33.24073,1.89117 22.2547,70.77048 73.14399,70.77048 39.92084,0 58.65264,-56.66496 99.80211,-56.66496 49.90105,0 69.86147,80.94995 99.8021,48.56997" style="fill: none; stroke:black; stroke-width: 1;" />
</svg>

CodePudding user response:

No need for CSS or JavaScript (and I do love JavaScript & Web Components)

To add easing, see:

<svg viewBox="0 0 640 720" xmlns="http://www.w3.org/2000/svg">
    <path id="PATH" stroke="black" fill="none" d="m30 119c30-40 70-89 109-89 81 0 91 138 141 130 40-7 50-81 87-79 33 2 22 71 73 71 40 0 59-57 100-57 50 0 70 81 100 49" />
    <circle r="10" fill="red">
        <animateMotion dur="5s" keyPoints="0;1;0" keyTimes="0;.7;1" 
                       calcMode="linear" repeatCount="indefinite">
            <mpath href="#PATH" />
        </animateMotion>
    </circle>
</svg>

  • Related