layer_fade.mjs

/**
### mapp.layer.fade()

The `fade()` animation function implements linear interpolation to achieve a smooth transition between opacities on a the cluster layer format. 

The fade will occur during the zoom in and out as well as changing the view port.

### 🎯 Target Opacity Calculation: 
Depending on whether fading in or out, the target opacity is calculated. 
If fading out, the opacity is reduced by 20% or brought to a minimum of 0. 
If fading in, the opacity is increased by 20% or limited to a maximum of 1.

### ⏱️ Animation Duration and Timing: 
The animation duration is set to 100 milliseconds (adjustable), and the start time of the animation is recorded.

### 📊 Opacity Update Function: 
The `updateOpacity` function is used to calculate the current time and elapsed time since the animation started.

### 🌟 Interpolation and Opacity Update:
If the elapsed time is less than the animation duration, interpolation is used to smoothly transition the opacity between `currentOpacity` and `targetOpacity`. 
The `requestAnimationFrame` is used to schedule the next frame of the animation. 
When the animation duration has elapsed, the layer's opacity is set to the `targetOpacity`, concluding the animation.

### 🚀 Starting the Animation: 
The animation is triggered by calling the `updateOpacity` function.

### ⏳ Setting New Timeout:
A new fade timeout is set for the next step of the fade animation after the specified duration has passed, ensuring the animation continues until the desired opacity is reached.

@module /layer/fade
@param {boolean} out - Indicates whether the layer should be faded out (`true`) or faded in (`false`).
@example
"layer": {
  "fade": true
}
*/

export default function fade(layer, out) {

  // Check if the layer has a fade property.
  if (!layer?.fade) return;

  // Clear current fade process.
  clearTimeout(layer.fade);

  // Get current opacity.
  const currentOpacity = layer.L.getOpacity();

  let targetOpacity;

  if (out) {
    // Layer is completely transparent.
    if (currentOpacity <= 0) return;

    // Reduce opacity by 20%.
    targetOpacity = Math.max(currentOpacity - 0.2, 0);
  } else {
    // Layer is completely opaque.
    if (currentOpacity >= 1) return;

    // Increase opacity by 20%.
    targetOpacity = Math.min(currentOpacity + 0.2, 1);
  }

  const duration = 100; // Animation duration in milliseconds

  const startTime = Date.now();
  const updateOpacity = () => {
    // Current time used to calculate the elapsed time. 
    const currentTime = Date.now();

    const elapsedTime = currentTime - startTime;

    // If elapsed time is less than the animation duration, then we will use interpolation.
    if (elapsedTime < duration) {
      const progress = elapsedTime / duration;
      const interpolatedOpacity = currentOpacity + (targetOpacity - currentOpacity) * progress;
      layer.L.setOpacity(interpolatedOpacity);

      // Continue updating opacity.
      requestAnimationFrame(updateOpacity);
    } else {
      // Animation complete, set the final opacity.
      layer.L.setOpacity(targetOpacity);
    }
  };

  // Start the animation.
  updateOpacity();

  // Set the new fade timeout.
  layer.fade = setTimeout(() => fade(layer, out), duration);
}