So mappen Sie die Mausposition in CSS

Avatar of Amit Sheen
Amit Sheen am

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

Schauen wir uns an, wie man die Mausposition des Benutzers ermittelt und sie in CSS Custom Properties überträgt: --positionX und --positionY.

Wir könnten das in JavaScript tun. Wenn wir das täten, könnten wir Dinge tun wie ein Element ziehbar machen oder einen Hintergrund verschieben. Aber tatsächlich können wir immer noch ähnliche Dinge tun, ohne JavaScript zu verwenden!

Ich habe diese Methode als Teil einer Demo verwendet, die ich für einen 'Ziehen und Loslassen'-Effekt mit reinem CSS erstellt habe. Ich habe die perspective-Tipps aus meinem vorherigen Artikel verwendet. Es ist ein ziemlich genialer Effekt, das alles in CSS zu erreichen, was eine breitere Anwendbarkeit haben könnte als meine Demo, also schauen wir es uns an.

Das Setup

Unsere erste Demo wird --positionX und --positionY benutzerdefinierte Eigenschaften verwenden, um die width und height eines Elements festzulegen.

Bitte beachten Sie, dass wir hier nur SCSS zur Kürze verwenden, aber all dies kann in reinem CSS erfolgen.

Dies ist unser Anfangszustand. Wir haben hier ein 'Wrapper' <div> mit einer .content-Klasse, die sich über die Breite und Höhe des Bodys erstreckt. Dieses <div> beherbergt den Inhalt unseres Projekts und die Elemente, die wir über die Mausposition steuern möchten – in diesem Fall das .square-Element.

Wir haben die beiden benutzerdefinierten Eigenschaften auch zum content hinzugefügt. Wir werden die Mausposition verwenden, um den Wert dieser Eigenschaften festzulegen, und sie dann verwenden, um die width und height des .square-Elements entsprechend festzulegen.

Sobald wir die benutzerdefinierten Eigenschaften für die Mausposition gemappt haben, können wir sie verwenden, um so ziemlich alles zu tun, was wir wollen. Zum Beispiel könnten wir sie verwenden, um die top / left-Eigenschaften eines absolut positionierten Elements festzulegen, eine transform-Eigenschaft zu steuern, die background-position festzulegen, Farben zu manipulieren oder sogar den Inhalt eines Pseudo-Elements festzulegen. Einige dieser Bonus-Demos sehen wir am Ende des Artikels.

Das Raster

Das Ziel ist es, ein unsichtbares Raster auf dem Bildschirm zu erstellen und die :hover-Pseudoklasse zu verwenden, um jede 'Zelle' mit einer Reihe von Werten zu verknüpfen, die wir den benutzerdefinierten Eigenschaften zuweisen werden. Wenn sich der Mauszeiger also auf die rechte Seite des Bildschirms bewegt, ist der Wert von --positionX höher; und wenn er sich nach links bewegt, wird er niedriger. Wir machen dasselbe mit --positionY: Der Wert wird niedriger, wenn sich der Cursor nach oben bewegt, und höher, wenn er sich nach unten bewegt.

Ein paar Worte zur Rastergröße, die wir verwenden: Wir können das Raster tatsächlich beliebig groß machen. Je größer es ist, desto genauer werden unsere benutzerdefinierten Eigenschaftswerte sein. Das bedeutet aber auch, dass wir mehr Zellen haben werden, was zu Leistungsproblemen führen kann. Es ist wichtig, ein angemessenes Gleichgewicht zu wahren und die Rastergröße an die spezifischen Bedürfnisse jedes Projekts anzupassen.

Fürs Erste sagen wir, dass wir ein 10x10-Raster für insgesamt 100 Zellen in unserem Markup wollen. (Und ja, es ist in Ordnung, Pug dafür zu verwenden, auch wenn ich es in diesem Beispiel nicht tun werde.)

<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
<!-- 97 more cells -->

<div class="content">
  <div class="square"></div>
</div>

Sie fragen sich wahrscheinlich, warum die .cell-Elemente vor dem .content kommen. Das liegt an der Kaskade.

Wir möchten die .cell-Klasse verwenden, um das .square zu steuern, und die Art und Weise, wie die Kaskade (derzeit) funktioniert, ist, dass ein Element nur seine Kinder (oder Nachfahren) und seine Geschwister (oder deren Nachfahren) steuern kann – aber nur, solange das Geschwisterelement nach dem steuernden Element kommt.

Das bedeutet zwei Dinge:

  1. Jede .cell muss vor dem Element kommen, das wir steuern wollen (in diesem Fall das .square).
  2. Wir können diese .cells nicht in einen Container packen, denn wenn wir das tun, ist das .content nicht mehr ihr Geschwister.

Positionierung der Zellen

Es gibt ein paar Möglichkeiten, die .cells zu positionieren. Wir können sie position: absolute geben und sie mit den top- und left-Eigenschaften verschieben. Oder wir können sie mit transform in Position translaten. Aber die einfachste Option ist wahrscheinlich die Verwendung von display: grid.

body {
  background-color: #000;
  height: 100vh;
  display: grid;
  grid-template: repeat(10, 1fr) / repeat(10, 1fr);
}

.cell {
  width: 100%;
  height: 100%;
  border: 1px solid gray;
  z-index: 2;
}
  • Der border ist nur temporär, damit wir die cells auf dem Bildschirm sehen können. Wir werden ihn später entfernen.
  • Der z-index ist wichtig, da wir möchten, dass die cells vor dem content liegen.

Hier ist, was wir bisher haben:

Werte hinzufügen

Wir möchten .cell verwenden, um die --positionX- und --positionY-Werte festzulegen.

Wenn wir über eine .cell fahren, die sich in der ersten (linken) Spalte befindet, sollte der Wert von --positionX 0 sein. Wenn wir über eine .cell in der zweiten Spalte fahren, sollte der Wert 1 sein. Er sollte 2 in der dritten Spalte sein und so weiter.

Dasselbe gilt für die Y-Achse. Wenn wir über eine .cell fahren, die sich in der ersten (obersten) Zeile befindet, sollte --positionY 0 sein, und wenn wir über eine cell in der zweiten Zeile fahren, sollte der Wert 1 sein und so weiter.

A black ten by ten grid of squares with white borders and numbers in sequential order from left to right.

Die Zahlen in diesem Bild stellen die Nummern der Zellelemente im Raster dar. Wenn wir nur eine einzige .cell als Beispiel nehmen – sagen wir Zelle 42 –, können wir sie mit :nth-child() auswählen.

.cell:nth-child(42) { }

Aber wir müssen ein paar Dinge beachten:

  1. Wir möchten, dass dieser Selektor nur funktioniert, wenn wir über die Zelle fahren, also hängen wir :hover daran.
  2. Wir möchten das .content anstelle der Zelle selbst auswählen, also verwenden wir den General Sibling Combinator (~), um dies zu tun.

Um also --positionX auf 1 und --positionY auf 3 für .content festzulegen, wenn die 42. cell angefahren wird, müssen wir Folgendes tun:

.cell:nth-child(42):hover ~ .content {
  --positionX: 1;
  --positionY: 3;
}

Aber wer will das 100 Mal tun!? Es gibt ein paar Ansätze, um die Dinge einfacher zu machen:

  1. Verwenden Sie eine Sass @for-Schleife, um alle 100 Zellen zu durchlaufen, und führen Sie einige Berechnungen durch, um die richtigen --positionX- und --positionY-Werte für jede Iteration festzulegen.
  2. Trennen Sie die X- und Y-Achsen, um jede Zeile und jede Spalte einzeln mit der funktionalen Notation von :nth-child auszuwählen.
  3. Kombinieren Sie diese beiden Ansätze und verwenden Sie eine Sass @for-Schleife *und* :nth-child-Funktionsnotation.

Ich habe lange und gründlich darüber nachgedacht, was der beste und einfachste Ansatz wäre, und obwohl alle Vor- und Nachteile haben, habe ich mich für den dritten Ansatz entschieden. Die Menge des zu schreibenden Codes, die Qualität des kompilierten Codes und die mathematische Komplexität flossen alle in mein Denken ein. Nicht einverstanden? Sagen Sie mir, warum in den Kommentaren!

Werte mit einer @for-Schleife setzen

@for $i from 0 to 10 {
 .cell:nth-child(???):hover ~ .content {
    --positionX: #{$i};
  }
  .cell:nth-child(???):hover ~ .content {
    --positionY: #{$i};
  }
}

Dies ist die grundlegende Schleife. Wir gehen 10 Mal durch, da wir 10 Zeilen und 10 Spalten haben. Und wir haben die X- und Y-Achsen getrennt, um --positionX für jede Spalte und --positionY für jede Zeile festzulegen. Alles, was wir jetzt tun müssen, ist, diese ??? mit der richtigen Notation zu ersetzen, um jede Zeile und Spalte auszuwählen.

Fangen wir mit der X-Achse an

Wenn wir noch einmal auf unser Rasterbild zurückblicken (das mit den Zahlen), sehen wir, dass die Zahlen aller Zellen in der zweiten Spalte Vielfache von 10 plus 2 sind. Die Zellen in der dritten Spalte sind Vielfache von 10 plus 3. Und so weiter.

Nun 'übersetzen' wir es in die funktionale Notation von :nth-child. Hier ist, wie das für die zweite Spalte aussieht:

:nth-child(10n + 2)
  • 10n wählt jedes Vielfache von 10 aus.
  • 2 ist die Spaltennummer.

Und für unsere Schleife ersetzen wir die Spaltennummer durch #{$i + 1}, um sequenziell zu iterieren.

.cell:nth-child(10n + #{$i + 1}):hover ~ .content {
  --positionX: #{$i};
}

Nun kümmern wir uns um die Y-Achse

Schauen wir uns noch einmal das Rasterbild an und konzentrieren uns auf die vierte Zeile. Die Zellennummern liegen irgendwo zwischen 41 und 50. Die Zellen in der fünften Zeile liegen zwischen 51 und 60 und so weiter. Um jede Zeile auszuwählen, müssen wir ihren Bereich definieren. Zum Beispiel ist der Bereich für die vierte Zeile:

.cell:nth-child(n + 41):nth-child(-n + 50)
  • (n + 41) ist der Beginn des Bereichs.
  • (-n + 50) ist das Ende des Bereichs.

Nun ersetzen wir die Zahlen durch einige Berechnungen mit dem $i-Wert. Für den Beginn des Bereichs erhalten wir (n + #{10 * $i + 1}) und für das Ende des Bereichs erhalten wir (-n + #{10 * ($i + 1)}).

Daher ist die endgültige @for-Schleife:

@for $i from 0 to 10 {
 .cell:nth-child(10n + #{$i + 1}):hover ~ .content {
    --positionX: #{$i};
  }
  .cell:nth-child(n + #{10 * $i + 1}):nth-child(-n + #{10 * ($i + 1)}):hover ~ .content {
    --positionY: #{$i};
  }
}

Das Mapping ist abgeschlossen! Wenn wir über Elemente fahren, ändern sich --positionX und --positionY entsprechend der Mausposition. Das bedeutet, wir können sie verwenden, um die Elemente innerhalb von .content zu steuern.

Umgang mit den benutzerdefinierten Eigenschaften

Okay, jetzt haben wir die Mausposition auf zwei benutzerdefinierte Eigenschaften abgebildet. Als Nächstes verwenden wir sie, um die width und height des .square-Elements zu steuern.

Fangen wir mit der width an und sagen, wir wollen, dass die minimale width des .square 100px beträgt (d.h. wenn sich der Mauszeiger auf der linken Seite des Bildschirms befindet), und wir wollen, dass sie sich für jeden Schritt, den sich der Mauszeiger nach rechts bewegt, um 20px vergrößert.

Mit calc() können wir das tun:

.square {
  width: calc(100px + var(--positionX) * 20px);
}

Und natürlich machen wir dasselbe für die height, aber stattdessen mit --positionY:

.square {
  width: calc(100px + var(--positionX) * 20px);
  height: calc(100px + var(--positionY) * 20px);
}

Das war's! Jetzt haben wir ein einfaches .square-Element mit einer width und height, die von der Mausposition gesteuert wird. Bewegen Sie Ihren Mauszeiger über das Fenster und sehen Sie, wie sich die square entsprechend seiner width und height ändert.

Ich habe einen kleinen transition hinzugefügt, um den Effekt sanfter zu gestalten. Das ist natürlich nicht erforderlich. Und ich habe auch den .cell-Rand auskommentiert.

Versuchen wir eine alternative Methode

Es kann vorkommen, dass Sie --positionX und --positionY 'umgehen' und den Endwert direkt innerhalb der @for-Schleife festlegen möchten. Für unser Beispiel würde das also so aussehen:

@for $i from 0 to 10 {
 .cell:nth-child(10n + #{$i + 1}):hover ~ .content {
    --squareWidth: #{100 + $i * 20}px;
  }
  .cell:nth-child(n + #{10 * $i + 1}):nth-child(-n + #{10 * ($i + 1)}):hover ~ .content {
    --squareHeight: #{100 + $i * 20}px;: #{$i};
  }
}

Dann würde das .square die benutzerdefinierten Eigenschaften wie folgt verwenden:

.square {
  width: var(--squareWidth);
  height: var(--squareHeight);
}

Diese Methode ist etwas flexibler, da sie fortgeschrittenere Sass-Mathematik- (und Zeichenketten-) Funktionen ermöglicht. Dennoch ist das allgemeine Prinzip absolut dasselbe wie das, was wir bereits behandelt haben.

Was kommt als Nächstes?

Nun, der Rest liegt bei Ihnen – und die Möglichkeiten sind endlos! Wie würden Sie es Ihrer Meinung nach verwenden? Können Sie es weiterentwickeln? Probieren Sie diesen Trick in Ihrem CSS aus und teilen Sie Ihre Arbeit in den Kommentaren oder lassen Sie es mich auf Twitter wissen. Es wäre großartig, eine Sammlung davon zusammenkommen zu sehen.

Hier sind ein paar Beispiele, um Ihr Gehirn in Gang zu bringen: