Material Design Ripple-Effekt mit CSS und JavaScript implementieren

Simon Mader

Simon Mader

simonmader17

📅 Veröffentlicht: 25. Mai 2022

👀 Aufrufe: ---

Material Design Ripple-Effekt mit CSS und JavaScript implementieren

Wie man den Material Design Ripple-Effekt mit CSS und JavaScript für jedes beliebige Element implementieren kann.

In diesem Blogpost geht es darum, den Ripple-Effekt den man von Google's Material Design kennt zu implementieren. Ich habe mich von diesem Tutorial auf css-tricks.com von Bret Cameron inspirieren lassen, aber habe dabei selbst ein paar Verbesserungen vorgenommen.

Nachdem du diesen Blogpost gelesen hast, wirst du in der Lage sein den Ripple-Effekt auf so ziemlich jedes Element anzuwenden. So sieht das dann aus:

<div
  class="element"
  onPointerDown={(e) => createRipple(e)}
>
  Klick mich!
</div>
Klick mich!

Die Funktion createRipple(e) fügt dabei ein Element mit der Klasse ripple in das Element ein und entfernt es nach einer bestimmten Aktion wieder. Während das Ripple-Effekt Element vorhanden ist, sieht der HTML-Code folgendermaßen aus:

<div
  class="element"
  onPointerDown={(e) => createRipple(e)}
>
  <span class="ripple" />
  Klick mich!
</div>

Und der CSS-Code sieht so aus:

.element {
  position: relative;
  overflow: hidden;
  cursor: pointer;
}

.ripple {
  position: absolute;
  border-radius: 50%;
  background-color: rgba(255, 255, 255, 0.5);
  opacity: 1;
  transform: scale(0);
  animation: ripple 600ms linear forwards;
}

@keyframes ripple {
  to {
    transform: scale(3);
  }
}

Alles was man machen muss, ist dem Element eine Position von relative zu geben, den Overflow auf hidden zu setzen und, damit es sich mehr wie ein Button verhält, den Cursor auf pointer zu setzen. Dann muss man nur noch die createRipple(e) Funktion aufrufen, wenn auf das Element geklickt wird. Die Funktion sieht folgendermaßen aus:

const createRipple = (event: React.MouseEvent<Element, MouseEvent>) => {
  // Ripple-Element erstellen
  const button = event.currentTarget as HTMLElement;
  const ripple = document.createElement("span");

  const diameter = Math.max(button.clientWidth, button.clientHeight);
  const radius = diameter / 2;

  ripple.style.width = ripple.style.height = `${diameter}px`;
  ripple.style.left = `${
    event.clientX - button.getBoundingClientRect().left - radius
  }px`;
  ripple.style.top = `${
    event.clientY - button.getBoundingClientRect().top - radius
  }px`;
  ripple.classList.add("ripple");

  // Ripple-Element einfügen
  button.insertBefore(ripple, button.firstChild);
}

Zurzeit sieht der Ripple-Effekt so aus:

Klick mich!

Wie man sehen kann, verschwinden die einzelnen Ripple-Elemente nicht sondern stapeln sich übereinander.

Um dieses Problem zu lösen, implementieren wir eine fadeOutRipple() Funktion:

const fadeOutRipple = (
  button: HTMLElement,
  handleFadeOutRipple: () => void,
  ripple: HTMLSpanElement,
  animationStart: number
) => {
  const animationInterrupt = Date.now();
  let remainingTime = 600 - (animationInterrupt - animationStart);
  if (remainingTime < 200) remainingTime = 200;
  ripple.style.transition = `opacity ${remainingTime}ms linear`;
  if (ripple) ripple.classList.add("ripple-fade-out");
  const removeRipple = async () => {
    await new Promise((res) => setTimeout(res, remainingTime));
    if (ripple) ripple.remove();
  };
  removeRipple();
  button.removeEventListener("pointerup", handleFadeOutRipple);
  button.removeEventListener("pointercancel", handleFadeOutRipple);
  button.removeEventListener("pointerleave", handleFadeOutRipple);
};

Die Funktion bekommt das Ripple-Element das sie entfernen soll und die Zeit zu der das Ripple-Element erstellt wurde als Parameter übergeben. Anhand dieses Zeitwertes berechnet die Funktion die verbleibende Animationszeit für die Ausblendungsanimation.

Außerdem bekommt die Funktion das button Element und die handleFadeOutRipple() Funktion übergeben, damit die Event-Listener, die wir im nächsten Schritt dem Button hinzufügen, am Ende des Prozesses wieder entfernt werden können.

Nun müssen wir nur noch am Ende der createRipple(e) Funktion eine handleFadeOutRipple() Funktion erstellen, die die fadeOutRipple() Funktion aufruft, und diese zu den folgenden Event-Listenern hinzufügen:

  • pointerup: Der pointerup-Event-Handler wird ausgelöst, wenn der Zeiger (Maus, Tastaur oder Touch) nicht länger aktiv ist.
  • pointercancel: Der pointercancel-Event-Handler wird ausgelöst, wenn der Browser erwartet, dass keine Zeigereignisse mehr ausgelöst werden, z.B. wenn das Gerät deaktiviert wurde.
  • pointerleave: Der pointerleave-Event-Handler wird ausgelöst, wenn ein Zeiger (Maus, Tastatur oder Touch) die aktive Fläche wieder verlässt.
const createRipple = (event: React.MouseEvent<Element, MouseEvent>) => {

  ...

  // Event-Listener hinzufügen die die Ripple-Elemente wieder entfernen
  const animationStart = Date.now();

  const handleFadeOutRipple = () => {
    fadeOutRipple(button, handleFadeOutRipple, ripple, animationStart);
  };

  button.addEventListener("pointerup", handleFadeOutRipple);
  button.addEventListener("pointercancel", handleFadeOutRipple);
  button.addEventListener("pointerleave", handleFadeOutRipple);
};

So sieht der fertige Ripple-Effekt aus:

Danke fürs Lesen! ✌️