Material Design 3 Expressive Loading Indicator to React/Vue/Svelte/Vanilla JS

Category: Javascript , Loading | May 28, 2026
AuthorAler1x
Last UpdateMay 28, 2026
LicenseMIT
Views0 views
Material Design 3 Expressive Loading Indicator to React/Vue/Svelte/Vanilla JS

m3-loading-indicator is a canvas-based loading animation library that creates the Material Design 3 expressive loading indicator in your React, Svelte, Vue 3, or vanilla JavaScript project.

It ports the full animation system from Android’s material-components-android library, including exact spring force parameters, SVG path geometry from official Material Design asset files, and the dual rotation math from LoadingIndicatorAnimatorDelegate.java.

Features:

  • Renders seven morphing shapes in sequence with spring-driven transitions.
  • Matches the Android SpringForce stiffness and damping ratio parameters exactly.
  • Uses pixel-accurate SVG path geometry from official Material Design asset files.
  • Draws on an HTML canvas with automatic HiDPI scaling.
  • Supports a contained variant with a circular background behind the indicator.

Vanilla JavaScript Usage:

This example creates a 56px canvas indicator and keeps animation work inside requestAnimationFrame.

<p><canvas id="billing-loader"></canvas></p>
<script type="module">
  import {
    M3Animator,
    getMorphedShape,
    drawIndicator,
    setupCanvas,
  } from "https://cdn.jsdelivr.net/npm/@alerix/m3-loading-indicator/dist/core/index.js";
  const canvas = document.querySelector("#billing-loader");
  const size = 56;
  // Set up a HiDPI-aware 2D canvas context.
  const ctx = setupCanvas(canvas, size);
  // Create the animation controller.
  const animator = new M3Animator();
  function renderBillingLoader(timestamp) {
    animator.update(timestamp);
    // Convert the current morph value into a drawable shape.
    const shape = getMorphedShape(animator.morph);
    drawIndicator(ctx, size, shape, animator.rotation, {
      color: "#6750A4",
      sizeRatio: 0.8,
      contained: true,
      containerColor: "#F3E8FF",
    });
    requestAnimationFrame(renderBillingLoader);
  }
  requestAnimationFrame(renderBillingLoader);
</script>

React Usage:

React projects can render the default loading indicator with one component import. The default output uses a 48px canvas and inherits the current text color from its parent.

import { M3LoadingIndicator } from "@alerix/m3-loading-indicator/react";
export default function CheckoutStatus() {
  return (
    <div className="checkout-status">
      {/* 48px indicator that uses currentColor */}
      <M3LoadingIndicator />
    </div>
  );
}

Vue Usage:

<script setup>
  import { M3LoadingIndicator } from "@alerix/m3-loading-indicator/vue";
</script>
<template>
  <section class="sales-card">
    <header class="sales-card__header">
      <span>Revenue snapshot</span>
      <!-- Bind numeric props with :prop syntax -->
      <M3LoadingIndicator
        :size="32"
        color="#2563EB"
        :speed="1.2"
      />
    </header>
  </section>
</template>

Svelte Usage:

Svelte 5 route transitions can show a loader while a page-level request resolves. The snippet imports the Svelte component and keeps the indicator markup close to the loading message.

<script lang="ts">
  import M3LoadingIndicator from "@alerix/m3-loading-indicator/svelte";
</script>
<div class="route-loading" aria-live="polite">
  <!-- The loader inherits layout from the surrounding UI -->
  <M3LoadingIndicator size={40} color="#9333EA" />
  <span>Loading account details</span>
</div>

Configuration Options (Props):

  • size (number): Sets the CSS pixel size of the indicator. The default value is 48.
  • color (string): Sets the fill color of the morphing shape. The default value is "currentColor".
  • sizeRatio (number): Sets the shape-to-container ratio. The default value is 0.79, which maps the 38dp shape into a 48dp container.
  • speed (number): Sets the animation speed multiplier. The default value is 1.
  • paused (boolean): Stops the animation while the component remains visible. The default value is false.
  • contained (boolean): Draws a circular background behind the indicator. The default value is false.
  • containerColor (string): Sets the circular container background color. The default value is "rgba(0,0,0,0.08)".

API methods:

import {
  M3Animator,
  Spring,
  getMorphedShape,
  drawIndicator,
  setupCanvas,
  getShapes,
  lerpShapes,
  SHAPE_COUNT,
  DURATION_PER_SHAPE_MS,
  CONSTANT_ROTATION_DEG,
  EXTRA_ROTATION_DEG,
} from "@alerix/m3-loading-indicator";
// Create a core animation controller for custom canvas renderers.
const checkoutAnimator = new M3Animator();
// Advance the morph and rotation state from a requestAnimationFrame timestamp.
checkoutAnimator.update(performance.now());
// Read the current rotation and morph values.
const checkoutState = checkoutAnimator.getState();
// Reset the animation timeline and spring state.
checkoutAnimator.reset();
// Create a HiDPI-aware 2D canvas context.
const canvas = document.querySelector("#checkout-loader");
const ctx = setupCanvas(canvas, 56);
// Convert the current morph number into a drawable point array.
const currentShape = getMorphedShape(checkoutState.morph);
// Draw the indicator on a canvas context.
drawIndicator(ctx, 56, currentShape, checkoutState.rotation, {
  color: "#6750A4",
  sizeRatio: 0.79,
  contained: true,
  containerColor: "#E8DEF8",
});
// Read all normalized Material shape point arrays.
const materialShapes = getShapes();
// Interpolate between two shape point arrays.
const halfwayShape = lerpShapes(materialShapes[0], materialShapes[1], 0.5);
// Create the lower-level spring primitive for custom motion experiments.
const spring = new Spring(200, 0.6);
// Set the spring target value.
spring.target = 1;
// Advance the spring by one 60fps frame.
spring.step(1 / 60);
// Reset the spring position, velocity, and target.
spring.reset();
// Read exported animation constants for custom renderers.
console.log(SHAPE_COUNT);
console.log(DURATION_PER_SHAPE_MS);
console.log(CONSTANT_ROTATION_DEG);
console.log(EXTRA_ROTATION_DEG);

Alternatives:

You Might Be Interested In:


Leave a Reply