javascriptcssmapbox-gl-jsrequestanimationframe

Does requestAnimationFrame interfere CSS transitions?


Is it possible, that requestAnimationFrame prevents other CSS transitions from working?

I have a requestAnimationFrame function, which shall move the position of a div container. The update is done on every frame request. The div container contains a CSS animation:

.container {
  ...
  animation: pulse 2s infinite;
  ...
}

  @keyframes pulse {
    0%   { transform: scale(1); opacity: 1; }
    70%  { transform: scale(3); opacity: 0; }
    100% { transform: scale(1); opacity: 0; }
  }

The CSS animation works fine as long as I do NOT call requestAnimationFrame or I stop making requests.

Additionally, calling requestAnimationFrame also prevents other transitions from working, which are outside of the scope of the animated container, for instance a navigation drawer, which I use as side menu.

As a work-a-round for the navigation drawer I had to wrap the request as following:

this.animationId = requestAnimationFrame((timestamp) => {
  setTimeout(() => {
    this.animateContainer(timestamp)
  }, 50)
})

The inner setTimeout is used to delay the next animation to let the drawer slide in.

However, this does not help for the container animation. Whenever a new frame gets drawn, the CSS animations begins and is reset with every new frame, so the animation flickers.

EDIT:

Why do I use requestAnimationFrame and CSS animations?

The requestAnimationFrame is used to interpolate the coordinates of a marker on a map. The actual position for X and Y is being calculated by the map depending on the canvas. The CSS animation is then used to animate that marker.


Solution

  • The cause of the problem in your code is not related to requestAnimationFrame but is the function that is being called by rAF.

    In animateMarker you add the same marker every time requestAnimationFrame is called. This should be possible. However, because you want to show a CSS Keyframes animation on the marker, it will not work as you'd expect. This is because every time marker.addTo(map) is called, the marker is quickly removed and re-added. This will reset your keyframes animation.

    Add the marker to map outside of the animateMarker function and only change the position of the marker. This will prevent the marker from being reset and get the result you want.

    var map = new mapboxgl.Map({
      container: 'map',
      style: 'mapbox://styles/mapbox/streets-v11',
      center: [0, 0],
      zoom: 2
    });
    
    var el = document.createElement('div');
    var dot = document.createElement('div');
    dot.className = 'mapboxgl-user-location-dot';
    el.appendChild(dot);
    
    // Set radius globally.
    var radius = 20;
    
    // Add marker before animate loop.
    var marker = new mapboxgl.Marker(el)
      .setLngLat(      
        Math.cos(0 / 1000) * 20,
        Math.sin(0 / 1000) * 20
      ]);
      .addTo(map);
    
    // Change only the position in the animation loop without re-adding the marker.
    function animateMarker(timestamp) {
    
      // Update the data to a new position based on the animation timestamp. The
      // divisor in the expression `timestamp / 1000` controls the animation speed.
      marker.setLngLat([
        Math.cos(timestamp / 1000) * radius,
        Math.sin(timestamp / 1000) * radius
      ]);  
    
      // Request the next frame of the animation.
      requestAnimationFrame(animateMarker);
    }
    
    // Start the animation.
    requestAnimationFrame(animateMarker);