Anfang dieses Monats hatten wir im Animation at Work Slack eine Diskussion darüber, wie wir Benutzern das Schwenken innerhalb eines SVG ermöglichen können.
Ich habe die untenstehende Demo erstellt, um zu zeigen, wie ich diese Frage angehen würde
Siehe den Pen Demo – SVG Panning von Louis Hoebregts (@Mamboleoo) auf CodePen.
Hier sind die vier Schritte, um die obige Demo zum Laufen zu bringen
- Erfassen von Maus- und Touch-Events des Benutzers
- Berechnen der Maus-Offsets von ihrem Ursprung
- Speichern der neuen
viewBox-Koordinaten - Behandeln des dynamischen Viewports
Sehen wir uns diese Schritte einzeln genauer an.
1. Maus- & Touch-Events
Um die Maus- oder Touch-Position zu erhalten, müssen wir zuerst Event-Listener auf unserem SVG hinzufügen. Wir können die Pointer Events verwenden, um alle Arten von Zeigern (Maus/Touch/Stift/...) zu verarbeiten, aber diese Events werden noch nicht von allen Browsern unterstützt. Wir müssen einen Fallback hinzufügen, um sicherzustellen, dass alle Benutzer das SVG ziehen können.
// We select the SVG into the page
var svg = document.querySelector('svg');
// If browser supports pointer events
if (window.PointerEvent) {
svg.addEventListener('pointerdown', onPointerDown); // Pointer is pressed
svg.addEventListener('pointerup', onPointerUp); // Releasing the pointer
svg.addEventListener('pointerleave', onPointerUp); // Pointer gets out of the SVG area
svg.addEventListener('pointermove', onPointerMove); // Pointer is moving
} else {
// Add all mouse events listeners fallback
svg.addEventListener('mousedown', onPointerDown); // Pressing the mouse
svg.addEventListener('mouseup', onPointerUp); // Releasing the mouse
svg.addEventListener('mouseleave', onPointerUp); // Mouse gets out of the SVG area
svg.addEventListener('mousemove', onPointerMove); // Mouse is moving
// Add all touch events listeners fallback
svg.addEventListener('touchstart', onPointerDown); // Finger is touching the screen
svg.addEventListener('touchend', onPointerUp); // Finger is no longer touching the screen
svg.addEventListener('touchmove', onPointerMove); // Finger is moving
}
Da wir Touch-Events und Pointer-Events haben könnten, müssen wir eine kleine Funktion erstellen, die die Koordinaten entweder vom ersten Finger oder von einem Zeiger zurückgibt.
// This function returns an object with X & Y values from the pointer event
function getPointFromEvent (event) {
var point = {x:0, y:0};
// If event is triggered by a touch event, we get the position of the first finger
if (event.targetTouches) {
point.x = event.targetTouches[0].clientX;
point.y = event.targetTouches[0].clientY;
} else {
point.x = event.clientX;
point.y = event.clientY;
}
return point;
}
Sobald die Seite bereit ist und auf Benutzerinteraktionen wartet, können wir die Ereignisse `mousedown`/`touchstart` verarbeiten, um die ursprünglichen Koordinaten des Zeigers zu speichern und eine Variable zu erstellen, die uns wissen lässt, ob der Zeiger gedrückt ist oder nicht.
// This variable will be used later for move events to check if pointer is down or not
var isPointerDown = false;
// This variable will contain the original coordinates when the user start pressing the mouse or touching the screen
var pointerOrigin = {
x: 0,
y: 0
};
// Function called by the event listeners when user start pressing/touching
function onPointerDown(event) {
isPointerDown = true; // We set the pointer as down
// We get the pointer position on click/touchdown so we can get the value once the user starts to drag
var pointerPosition = getPointFromEvent(event);
pointerOrigin.x = pointerPosition.x;
pointerOrigin.y = pointerPosition.y;
}
2. Maus-Offsets berechnen
Nachdem wir die Koordinaten der ursprünglichen Position haben, an der der Benutzer begonnen hat, innerhalb des SVG zu ziehen, können wir den Abstand zwischen der aktuellen Zeigerposition und ihrem Ursprung berechnen. Dies tun wir für die X- und Y-Achse und wenden die berechneten Werte auf die viewBox an.
// We save the original values from the viewBox
var viewBox = {
x: 0,
y: 0,
width: 500,
height: 500
};
// The distances calculated from the pointer will be stored here
var newViewBox = {
x: 0,
y: 0
};
// Function called by the event listeners when user start moving/dragging
function onPointerMove (event) {
// Only run this function if the pointer is down
if (!isPointerDown) {
return;
}
// This prevent user to do a selection on the page
event.preventDefault();
// Get the pointer position
var pointerPosition = getPointFromEvent(event);
// We calculate the distance between the pointer origin and the current position
// The viewBox x & y values must be calculated from the original values and the distances
newViewBox.x = viewBox.x - (pointerPosition.x - pointerOrigin.x);
newViewBox.y = viewBox.y - (pointerPosition.y - pointerOrigin.y);
// We create a string with the new viewBox values
// The X & Y values are equal to the current viewBox minus the calculated distances
var viewBoxString = `${newViewBox.x} ${newViewBox.y} ${viewBox.width} ${viewBox.height}`;
// We apply the new viewBox values onto the SVG
svg.setAttribute('viewBox', viewBoxString);
document.querySelector('.viewbox').innerHTML = viewBoxString;
}
Wenn Sie sich mit dem Konzept von viewBox nicht wohlfühlen, empfehle ich Ihnen, zuerst diesen grossartigen Artikel von Sara Soueidan zu lesen.
3. Aktualisierte viewBox speichern
Nachdem die viewBox aktualisiert wurde, müssen wir ihre neuen Werte speichern, wenn der Benutzer aufhört, das SVG zu ziehen.
Dieser Schritt ist wichtig, da wir sonst immer die Zeiger-Offsets von den ursprünglichen viewBox-Werten berechnen würden und der Benutzer das SVG jedes Mal vom Startpunkt aus ziehen würde.
function onPointerUp() {
// The pointer is no longer considered as down
isPointerDown = false;
// We save the viewBox coordinates based on the last pointer offsets
viewBox.x = newViewBox.x;
viewBox.y = newViewBox.y;
}
4. Dynamischen Viewport handhaben
Wenn wir eine benutzerdefinierte Breite für unser SVG festlegen, bemerken Sie möglicherweise beim Ziehen in der untenstehenden Demo, dass sich der Vogel schneller oder langsamer bewegt als Ihr Zeiger.
Siehe den Pen Dynamischer Viewport – SVG Panning von Louis Hoebregts (@Mamboleoo) auf CodePen.
In der ursprünglichen Demo entspricht die Breite des SVG genau seiner viewBox-Breite. Die *tatsächliche Grösse* Ihres SVG kann auch als viewport bezeichnet werden. In einer perfekten Situation möchten wir, dass die viewBox um 1px verschoben wird, wenn sich der Zeiger des Benutzers um 1px bewegt.
Meistens hat das SVG jedoch eine responsive Grösse und die viewBox entspricht wahrscheinlich nicht dem SVG-viewport. Wenn die Breite des SVG doppelt so gross ist wie die viewBox, verschiebt sich das Bild im SVG um 2px, wenn sich der Zeiger des Benutzers um 1px bewegt.
Um dies zu beheben, müssen wir das **Verhältnis** zwischen der viewBox und dem viewport berechnen und dieses Verhältnis bei der Berechnung der neuen viewBox anwenden. Dieses Verhältnis muss auch aktualisiert werden, wenn sich die Grösse des SVG ändert.
// Calculate the ratio based on the viewBox width and the SVG width
var ratio = viewBox.width / svg.getBoundingClientRect().width;
window.addEventListener('resize', function() {
ratio = viewBox.width / svg.getBoundingClientRect().width;
});
Sobald wir das **Verhältnis** kennen, müssen wir die Maus-Offsets mit dem Verhältnis multiplizieren, um die Offsets proportional zu erhöhen oder zu verringern.
function onMouseMove (e) {
[...]
newViewBox.x = viewBox.x - ((pointerPosition.x - pointerOrigin.x) * ratio);
newViewBox.y = viewBox.y - ((pointerPosition.y - pointerOrigin.y) * ratio);
[...]
}
Hier sehen Sie, wie dies mit einem kleineren viewport als der Breite der viewBox funktioniert
Siehe den Pen Kleinerer Viewport – SVG Panning von Louis Hoebregts (@Mamboleoo) auf CodePen.
Und eine weitere Demo mit einem viewport, der grösser als die Breite der viewBox ist
Siehe den Pen Grösserer Viewport – SVG Panning von Louis Hoebregts (@Mamboleoo) auf CodePen.
[Bonus] Code optimieren
Um unseren Code etwas kürzer zu machen, gibt es zwei sehr nützliche Konzepte in SVG, die wir verwenden könnten.
SVG-Punkte
Das erste Konzept ist die Verwendung von SVG Points anstelle von einfachen JavaScript-Objekten zur Speicherung der Zeigerpositionen. Nach dem Erstellen einer neuen SVG Point-Variable können wir eine Matrix-Transformation darauf anwenden, um die Position relativ zum Bildschirm in eine Position relativ zu den aktuellen SVG-Benutzereinheiten umzuwandeln.
Überprüfen Sie den untenstehenden Code, um zu sehen, wie sich die Funktionen getPointFromEvent() und onPointerDown() geändert haben.
// Create an SVG point that contains x & y values
var point = svg.createSVGPoint();
function getPointFromEvent (event) {
if (event.targetTouches) {
point.x = event.targetTouches[0].clientX;
point.y = event.targetTouches[0].clientY;
} else {
point.x = event.clientX;
point.y = event.clientY;
}
// We get the current transformation matrix of the SVG and we inverse it
var invertedSVGMatrix = svg.getScreenCTM().inverse();
return point.matrixTransform(invertedSVGMatrix);
}
var pointerOrigin;
function onPointerDown(event) {
isPointerDown = true; // We set the pointer as down
// We get the pointer position on click/touchdown so we can get the value once the user starts to drag
pointerOrigin = getPointFromEvent(event);
}
Durch die Verwendung von SVG-Punkten müssen Sie nicht einmal Transformationen verarbeiten, die auf Ihrem SVG angewendet werden! Vergleichen Sie die folgenden beiden Beispiele, bei denen das erste bei einer Drehung des SVG fehlschlägt und das zweite Beispiel SVG-Punkte verwendet.
Siehe den Pen Demo + Transformation – SVG Panning von Louis Hoebregts (@Mamboleoo) auf CodePen.
Siehe den Pen Demo Bonus + Transform – SVG Panning von Louis Hoebregts (@Mamboleoo) auf CodePen.
SVG Animiertes Rechteck
Das zweite unbekannte Konzept in SVG, das wir zur Verkürzung unseres Codes verwenden können, ist die Verwendung von Animated Rect.
Da die viewBox tatsächlich als SVG-Rechteck (x, y, Breite, Höhe) betrachtet wird, können wir eine Variable aus ihrem Basiswert erstellen, die die viewBox automatisch aktualisiert, wenn wir diese Variable aktualisieren.
Sehen Sie, wie viel einfacher es jetzt ist, die viewBox unseres SVG zu aktualisieren!
// We save the original values from the viewBox
var viewBox = svg.viewBox.baseVal;
function onPointerMove (event) {
if (!isPointerDown) {
return;
}
event.preventDefault();
// Get the pointer position as an SVG Point
var pointerPosition = getPointFromEvent(event);
// Update the viewBox variable with the distance from origin and current position
// We don't need to take care of a ratio because this is handled in the getPointFromEvent function
viewBox.x -= (pointerPosition.x - pointerOrigin.x);
viewBox.y -= (pointerPosition.y - pointerOrigin.y);
}
Und hier ist die finale Demo. Sehen Sie, wie viel kürzer der Code jetzt ist? 😀
Siehe den Pen Demo Bonus – SVG Panning von Louis Hoebregts (@Mamboleoo) auf CodePen.
Fazit
Diese Lösung ist definitiv nicht der einzige Weg, um ein solches Verhalten zu realisieren. Wenn Sie bereits eine Bibliothek zur Verarbeitung Ihrer SVGs verwenden, verfügt diese möglicherweise bereits über eine integrierte Funktion dafür.
Ich hoffe, dieser Artikel hilft Ihnen, die Leistungsfähigkeit von SVG besser zu verstehen! Fühlen Sie sich frei, zum Code beizutragen, indem Sie Ihre Ideen oder Alternativen zu dieser Lösung kommentieren.
Danksagungen
- Vogel entworfen von Freepik
- Ein grosses Dankeschön an Blake für seine wertvolle Hilfe und all die netten Leute aus dem AAW Slack für ihr Feedback.
Könnte dies mit Resize-Events und Browserdimensionen kombiniert werden, um „responsive SVGs“ zu erstellen, die nicht nur standardmässig mehr Details rendern?
Ich frage, weil ich, obwohl ich SVGs liebe, denke, dass es für Banner erstaunlich wäre, SVGs zu erstellen, bei denen verschiedene Elemente unterschiedliche Stile haben und je nach Bildschirmgrösse angezeigt werden.
Sicher!
Sie können diese beiden Demos von Sarah Drasner überprüfen.
Beide verwenden Media Queries, um das Aussehen der SVGs basierend auf dem Viewport zu ändern. Die erste aktualisiert auch die viewBox des SVG, um einen bestimmten Bereich pro Bildschirmgrösse anzuzeigen!
Grossartig, und alles, was wir jetzt noch brauchen, sind Zwei-Finger-Gesten
Coole Demo. Kürzlich habe ich dieses Juwel einer SVG-Karte entdeckt (https://www.mallofamerica.com/directory — auf Kartenansicht klicken) und hatte noch keine Zeit, herauszufinden, wie sie funktioniert, aber das ist ein guter Anfang. Ich liebe, wie sauber sie ist und wie gut ihre Responsivität funktioniert.
Nach allem, was ich gesehen habe, wird die viewBox beim Ziehen nicht aktualisiert, stattdessen wird ein CSS-Transform auf den Container der gesamten Karte angewendet, um sicherzustellen, dass auch die mit den Geschäften verknüpften HTML-Elemente gezogen werden :)
Aber es ist trotzdem eine grossartige interaktive Karte!