So erstellen Sie den Ripple-Effekt von Material Design-Buttons nach

Avatar of Bret Cameron
Bret Cameron am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Als ich Material Design zum ersten Mal entdeckte, war ich besonders inspiriert von seiner Button-Komponente. Sie nutzt einen Ripple-Effekt, um dem Benutzer auf einfache und elegante Weise Feedback zu geben.

Wie funktioniert dieser Effekt? Material Design-Buttons zeigen nicht nur eine nette Ripple-Animation, sondern die Animation ändert auch ihre Position je nachdem, wo auf den Button geklickt wird.

Wir können das gleiche Ergebnis erzielen. Wir beginnen mit einer prägnanten Lösung mit ES6+ JavaScript, bevor wir uns einige alternative Ansätze ansehen.

HTML

Unser Ziel ist es, unnötigen HTML-Markup zu vermeiden. Wir werden uns also mit dem absoluten Minimum begnügen

<button>Find out more</button>

Den Button stylen

Wir müssen einige Elemente unseres Ripples dynamisch per JavaScript stylen. Alles andere kann jedoch in CSS erfolgen. Für unsere Buttons sind nur zwei Eigenschaften erforderlich.

button {
  position: relative;
  overflow: hidden;
}

Die Verwendung von position: relative ermöglicht uns die Verwendung von position: absolute für unser Ripple-Element, was wir zur Steuerung seiner Position benötigen. Währenddessen verhindert overflow: hidden, dass der Ripple die Kanten des Buttons überschreitet. Alles andere ist optional. Aber im Moment sieht unser Button etwas altmodisch aus. Hier ist ein modernerer Ausgangspunkt

/* Roboto is Material's default font */
@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap');

button {
  position: relative;
  overflow: hidden;
  transition: background 400ms;
  color: #fff;
  background-color: #6200ee;
  padding: 1rem 2rem;
  font-family: 'Roboto', sans-serif;
  font-size: 1.5rem;
  outline: 0;
  border: 0;
  border-radius: 0.25rem;
  box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.3);
  cursor: pointer;
}

Styling der Ripples

Später werden wir JavaScript verwenden, um Ripples als Spans mit einer Klasse .ripple in unser HTML einzufügen. Aber bevor wir uns mit JavaScript befassen, definieren wir einen Stil für diese Ripples in CSS, damit wir sie bereithalten

span.ripple {
  position: absolute; /* The absolute position we mentioned earlier */
  border-radius: 50%;
  transform: scale(0);
  animation: ripple 600ms linear;
  background-color: rgba(255, 255, 255, 0.7);
}

Um unsere Ripples kreisförmig zu machen, haben wir border-radius auf 50 % gesetzt. Und um sicherzustellen, dass jeder Ripple aus dem Nichts entsteht, haben wir die Standard-Skalierung auf 0 gesetzt. Im Moment können wir nichts sehen, da wir noch keinen Wert für die Eigenschaften top, left, width oder height haben; diese Eigenschaften werden wir bald mit JavaScript einfügen.

Was unseren CSS-Code betrifft, so müssen wir als letztes noch einen Endzustand für die Animation hinzufügen

@keyframes ripple {
  to {
    transform: scale(4);
    opacity: 0;
  }
}

Beachten Sie, dass wir keinen Startzustand mit dem Schlüsselwort from in den Keyframes definieren? Wir können from weglassen und CSS wird die fehlenden Werte basierend auf denen konstruieren, die für das animierte Element gelten. Dies geschieht, wenn die relevanten Werte explizit angegeben sind – wie bei transform: scale(0) – oder wenn sie Standardwerte sind, wie opacity: 1.

Nun zum JavaScript

Schließlich benötigen wir JavaScript, um die Position und Größe unserer Ripples dynamisch festzulegen. Die Größe sollte auf der Größe des Buttons basieren, während die Position sowohl auf der Position des Buttons als auch des Cursors basieren sollte.

Wir beginnen mit einer leeren Funktion, die ein Klickereignis als Argument nimmt

function createRipple(event) {
  //
}

Wir greifen auf unseren Button zu, indem wir das currentTarget des Ereignisses finden.

const button = event.currentTarget;

Als nächstes instanziieren wir unser Span-Element und berechnen seinen Durchmesser und Radius basierend auf der Breite und Höhe des Buttons.

const circle = document.createElement("span");
const diameter = Math.max(button.clientWidth, button.clientHeight);
const radius = diameter / 2;

Wir können nun die verbleibenden Eigenschaften definieren, die wir für unsere Ripples benötigen: left, top, width und height.

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

Bevor wir das Span-Element zum DOM hinzufügen, ist es gute Praxis zu prüfen, ob bereits vorhandene Ripples von früheren Klicks vorhanden sind und diese zu entfernen, bevor der nächste ausgeführt wird.

const ripple = button.getElementsByClassName("ripple")[0];

if (ripple) {
  ripple.remove();
}

Als letzten Schritt fügen wir das Span als Kind-Element zum Button-Element hinzu, sodass es innerhalb des Buttons eingefügt wird.

button.appendChild(circle);

Nachdem unsere Funktion vollständig ist, müssen wir sie nur noch aufrufen. Dies könnte auf verschiedene Arten geschehen. Wenn wir den Ripple auf jeden Button auf unserer Seite anwenden möchten, können wir etwas wie folgt verwenden

const buttons = document.getElementsByTagName("button");
for (const button of buttons) {
  button.addEventListener("click", createRipple);
}

Jetzt haben wir einen funktionierenden Ripple-Effekt!

Weiterführend

Was ist, wenn wir weitergehen und diesen Effekt mit anderen Änderungen an der Position oder Größe unseres Buttons kombinieren wollen? Die Möglichkeit zur Anpassung ist schließlich einer der Hauptvorteile, wenn wir uns entscheiden, den Effekt selbst nachzubilden. Um zu testen, wie einfach es ist, unsere Funktion zu erweitern, habe ich beschlossen, einen "Magnet"-Effekt hinzuzufügen, der bewirkt, dass sich unser Button zum Cursor bewegt, wenn sich der Cursor innerhalb eines bestimmten Bereichs befindet.

Wir müssen uns auf einige der gleichen Variablen verlassen, die in der Ripple-Funktion definiert sind. Anstatt Code unnötigerweise zu wiederholen, sollten wir sie an einem Ort speichern, an dem sie für beide Methoden zugänglich sind. Wir sollten jedoch die gemeinsamen Variablen auf jeden einzelnen Button beschränkt halten. Eine Möglichkeit, dies zu erreichen, ist die Verwendung von Klassen, wie im folgenden Beispiel

Da der Magneteffekt den Cursor jedes Mal verfolgen muss, wenn er sich bewegt, müssen wir die Cursorposition nicht mehr berechnen, um einen Ripple zu erzeugen. Stattdessen können wir uns auf cursorX und cursorY verlassen.

Zwei wichtige neue Variablen sind magneticPullX und magneticPullY. Sie steuern, wie stark unsere Magnetmethode den Button dem Cursor hinterherzieht. Wenn wir also das Zentrum unseres Ripples definieren, müssen wir sowohl die Position des neuen Buttons (x und y) als auch den magnetischen Zug berücksichtigen.

const offsetLeft = this.left + this.x * this.magneticPullX;
const offsetTop = this.top + this.y * this.magneticPullY;

Um diese kombinierten Effekte auf alle unsere Buttons anzuwenden, müssen wir für jeden eine neue Instanz der Klasse erstellen

const buttons = document.getElementsByTagName("button");
for (const button of buttons) {
  new Button(button);
}

Andere Techniken

Natürlich ist dies nur eine Möglichkeit, einen Ripple-Effekt zu erzielen. Auf CodePen gibt es viele Beispiele, die verschiedene Implementierungen zeigen. Unten sind einige meiner Favoriten.

Nur CSS

Wenn ein Benutzer JavaScript deaktiviert hat, hat unser Ripple-Effekt keine Fallbacks. Aber es ist möglich, dem ursprünglichen Effekt mit nur CSS nahe zu kommen, indem die Pseudoklasse :active verwendet wird, um auf Klicks zu reagieren. Die Haupteinschränkung ist, dass der Ripple nur von einer Stelle ausgehen kann – normalerweise von der Mitte des Buttons –, anstatt auf die Position unserer Klicks zu reagieren. Dieses Beispiel von Ben Szabo ist besonders prägnant

JavaScript vor ES6

Leandro Parices Demo ist unserer Implementierung ähnlich, aber sie ist mit früheren JavaScript-Versionen kompatibel: 

jQuery

Dieses Beispiel verwendet jQuery, um den Ripple-Effekt zu erzielen. Wenn Sie jQuery bereits als Abhängigkeit haben, kann dies helfen, einige Codezeilen zu sparen. 

React

Zum Schluss noch ein letztes Beispiel von mir. Obwohl es möglich ist, React-Funktionen wie State und Refs zu verwenden, um den Ripple-Effekt zu erzeugen, sind diese nicht unbedingt erforderlich. Die Position und Größe des Ripples müssen für jeden Klick berechnet werden, daher gibt es keinen Vorteil, diese Informationen im State zu speichern. Außerdem können wir über das Klickereignis auf unser Button-Element zugreifen, sodass wir auch keine Refs benötigen.

Dieses React-Beispiel verwendet eine createRipple-Funktion, die identisch mit der ersten Implementierung dieses Artikels ist. Der Hauptunterschied besteht darin, dass unsere Funktion – als Methode der Button-Komponente – auf diese Komponente beschränkt ist. Außerdem ist der onClick-Event-Listener jetzt Teil unseres JSX