
Beim Toaster werden sie häufig gefragt, wie ein interaktives Diagramm eines Hauses erstellt werden soll, wie ein Plan der internen Struktur erstellt werden soll, ob Etagen oder Wohnungen mit entsprechenden Informationen ausgewählt werden können, ob Informationen zu den Details eines bestimmten Produkts angezeigt werden sollen, wenn Sie auf einem Foto mit der Maus darüber fahren. Es geht nicht um ein dreidimensionales Modell, sondern um ein Bild, mit dem bestimmte Details hervorgehoben werden können. All diese Aufgaben sind ähnlich und einfach zu lösen, aber es tauchen immer noch Fragen auf. Daher werden wir uns heute ansehen, wie solche Aufgaben mit SVG, einem Grafikeditor und einer Prise Javascript erledigt werden.
Die Wahl von SVG beruht auf der Tatsache, dass dies die einfachste Option für die Entwicklung und das Debuggen ist. Ich habe Leute getroffen, die geraten haben, dies alles auf Leinwand zu machen, aber dort ist es viel schwieriger zu verstehen, was passiert, und die Koordinaten aller Punkte auf den Kurven müssen irgendwie im Voraus berechnet werden, aber hier habe ich die Tools des Entwicklers geöffnet und sofort die gesamte Struktur, alle Objekte, mit denen gibt es interaktion, und alles andere kann mit einer maus in einer menschenfreundlichen oberfläche angeklickt werden. Die Leistung zwischen dem üblichen 2D-Canvas und SVG wird sich kaum unterscheiden. WebGL mag diesbezüglich einen gewissen Bonus bieten, aber der Zeitrahmen für die Entwicklung wird sich erheblich verlängern, ganz zu schweigen von der weiteren Unterstützung, die nicht immer in das Budget passt. Ich würde sogar sagen "passt nie."
In diesem Tutorial erstellen wir so etwas wie ein Widget für eine fiktive Website zum Mieten von Privathäusern in einem bestimmten Bereich. Es ist jedoch klar, dass die Prinzipien für die Erstellung solcher fraglichen Dinge für jedes Thema gelten.
Erste Schritte
Ich werde alles am Beispiel von Inkscape zeigen, aber dieselben Aktionen können mit ähnlichen Funktionen in jedem anderen Editor ausgeführt werden.
Für die Arbeit benötigen wir zwei Hauptdialogfelder:
- XML-Editor (Strg + Umschalt + X oder ein Symbol in spitzen Klammern) - um die Struktur des Dokuments in Form von Markups anzuzeigen und einzelne Elemente zu bearbeiten.
- Füllen und Konturieren (Strg + Umschalt + F oder das Symbol mit einem Pinsel im Rahmen) - hauptsächlich zum Füllen von Konturen.
Starten Sie sie sofort und fahren Sie mit der Erstellung des Dokuments fort.
Wenn Sie sie versehentlich in ein separates Fenster gezogen haben, können Sie unter den oberen Rahmen dieses Fensters klicken (wo sich nichts befindet) und sie zurück in das Hauptfenster ziehen. Dies ist nicht ganz intuitiv, sondern bequem.
Öffnen Sie ein Foto mit Blick auf den Bereich. Wir können das Bild selbst als base64-Zeichenfolge oder als externen Link einfügen. Wählen Sie den Link aus, da er groß ist. Dann werden wir den Pfad zum Bild mit unseren Händen ändern, wenn wir alles auf den Seiten der Site einfügen. Es wird ein SVG-Dokument erstellt, in das das Foto über das Bild-Tag eingebettet wird.
Bei in SVG eingebetteten Rasterbildern, die in HTML eingebettet sind, ist es möglich, das verzögerte Laden sowie normale Bilder auf Seiten zu verwenden. In diesem Beispiel werden wir nicht auf solche Optimierungen eingehen, aber sie in der praktischen Arbeit nicht vergessen.
Gegenwärtig sehen wir so etwas vor uns:

Erstellen Sie nun eine neue Ebene (Strg + Umschalt + N oder Menü> Ebene> Ebene hinzufügen). Im XML-Editor sehen wir, dass das reguläre g-Element aufgetreten ist. Obwohl wir noch nicht weit gekommen sind, können wir ihm eine Klasse geben, die wir später in Skripten verwenden werden.
Verlasse dich nicht auf id. Je komplexer die Benutzeroberfläche ist, desto einfacher ist es, sie zu wiederholen und seltsame Fehler zu bekommen. Und in unserer Aufgabe haben sie noch keinen Nutzen. Klassen oder Datenattribute sind also unsere Wahl.
Wenn Sie sich die Struktur des Dokuments im XML-Editor genau ansehen, werden Sie feststellen, dass vieles überflüssig ist. Jeder mehr oder weniger komplexe Vektorgrafik-Editor fügt Dokumenten etwas Eigenes hinzu. All dies mit den Händen zu entfernen ist eine lange und undankbare Aufgabe, der Editor wird ständig etwas hinzufügen. Daher wird SVG erst am Ende der Arbeit vom Müll befreit. Und vorzugsweise in automatisierter Form, da es fertige Optionen gibt, zum Beispiel das gleiche svgo .
Suchen Sie ein Werkzeug namens Bezierkurven und Gerade zeichnen (Umschalt + F6). Damit zeichnen wir geschlossene Konturen um die Objekte. In unserer Aufgabe müssen wir alle Gebäude umreißen. Zum Beispiel beschränken wir uns auf sechs, aber unter realen Bedingungen wäre es sinnvoll, Zeit im Voraus zuzuweisen, um alle erforderlichen Objekte genau zu umreißen. Obwohl es häufig viele ähnliche Objekte gibt, können dieselben Stockwerke in einem Gebäude absolut identisch sein. In solchen Fällen können Sie die Geschwindigkeit etwas erhöhen und die Kurven kopieren und einfügen.
Nachdem wir die erforderlichen Gebäude umrundet haben, kehren wir zum XML-Editor zurück, fügen Klassen hinzu, oder es werden höchstwahrscheinlich noch bequemere Datenattribute mit Indizes für sie sein (dies ist mit Adressen möglich, aber da wir einen fiktiven Bereich haben, gibt es nur einen Indizes), und verschieben Sie alles auf die zuvor erstellte Ebene, sodass alles „in Regalen angeordnet“ wird. Und das Bild wird übrigens auch nützlich sein, um sich dorthin zu bewegen, so dass alles an einem Ort ist, aber das sind Kleinigkeiten.
Nachdem Sie nun einen Pfad - eine Kurve um das Gebäude - ausgewählt haben, können Sie alle mit Strg + A oder Menü> Bearbeiten> Alle auswählen und gleichzeitig bearbeiten. Sie müssen sie alle im Fenster „Füllen und Konturieren“ malen und gleichzeitig den zusätzlichen Strich dort entfernen. Nun, oder fügen Sie es hinzu, wenn Sie es aus Designgründen benötigen.

Es ist sinnvoll, alle Konturen mit einer Farbe mit einem für sie minimalen Deckkraftwert zu bemalen, auch wenn dies aus gestalterischer Sicht nicht erforderlich ist. Fakt ist, dass intelligente Browser der Ansicht sind, dass Sie nicht auf einen leeren Pfad, sondern auf einen überfluteten Pfad klicken können, auch wenn niemand diese Füllung sieht.
In unserem Beispiel lassen wir ein kleines Highlight in Weiß, um besser zu sehen, mit welchen Gebäuden wir arbeiten, alles zu speichern und reibungslos zum Browser und zum bekannteren Code-Editor zu wechseln.
Einfaches Beispiel
Erstellen wir eine leere HTML-Seite, fügen die resultierende SVG-Datei direkt ein und fügen CSS hinzu, damit nichts aus dem Bildschirm herauskommt. Es gibt nichts zu kommentieren.
.map { width: 90%; max-width: 1300px; margin: 2rem auto; border: 1rem solid #fff; border-radius: 1rem; box-shadow: 0 0 .5rem rgba(0, 0, 0, .3); } .map > svg { width: 100%; height: auto; border-radius: .5rem; }
Wir erinnern uns, dass wir Gebäude mit Klassen versehen und diese so verwenden, dass CSS mehr oder weniger strukturiert ist.
.building { transition: opacity .3s ease-in-out; } .building:hover { cursor: pointer; opacity: .8 !important; } .building.-available { fill: #0f0 !important; } .building.-reserved { fill: #f00 !important; } .building.-service { fill: #fff !important; }
Da wir Inline-Stile in Inkscape definiert haben, müssen wir sie in CSS unterbrechen. Wäre es bequemer, alles in CSS zu machen? Ja und nein Es kommt auf die Situation an. Manchmal gibt es keine Wahl. Zum Beispiel, wenn ein Designer viel von allem bunt gezeichnet hat und alles in CSS verpackt hat und es so aufbläst, dass es irgendwie unmöglich ist, es nicht richtig zu machen. In diesem Beispiel verwende ich die Option "unbequem", um zu zeigen, dass sie im Kontext des zu lösenden Problems nicht besonders beängstigend ist.

Angenommen, wir haben neue Daten zu Häusern erhalten und fügen ihnen je nach aktuellem Status verschiedene Klassen hinzu:
const data = { id_0: { status: 'service' }, id_1: { status: 'available' }, id_2: { status: 'reserved' }, id_3: { status: 'available' }, id_4: { status: 'available' }, id_5: { status: 'reserved' }, messages: { 'available': ' ', 'reserved': '', 'service': ' 1-2 ' } }; const map = document.getElementById('my-map'); const buildings = map.querySelectorAll('.building'); for (building of buildings) { const id = building.getAttribute('data-building-id'); const status = data[`id_${id}`].status; building.classList.add(`-${status}`); }
Wir bekommen so etwas:

Ähnliches wie das, was wir brauchen, ist bereits sichtbar. Zu diesem Zeitpunkt haben wir Objekte auf dem Gelände markiert, die auf das Schweben der Maus reagieren. Und es ist nicht schwierig, ihnen eine Antwort auf einen Mausklick über den Standard-AddEventListener hinzuzufügen.
Führungslinie
Häufig besteht die Aufgabe darin, Linien zu erstellen, die die Objekte auf der Karte und einige Elemente auf der Seite mit zusätzlichen Informationen verbinden, sowie minimale QuickInfos zu erstellen, wenn Sie mit der Maus über dieselben Objekte fahren. Um diese Probleme zu lösen, ist die Leader-Line -Minibibliothek sehr gut geeignet, die Vektorpfeile für jeden Geschmack und jede Farbe erstellt.
Fügen wir den Daten die Preise für QuickInfos hinzu und zeichnen Sie diese Linien.
const data = { id_0: { price: '3000', status: 'service' }, id_1: { price: '3000', status: 'available' }, id_2: { price: '2000', status: 'reserved' }, id_3: { price: '5000', status: 'available' }, id_4: { price: '2500', status: 'available' }, id_5: { price: '2500', status: 'reserved' }, messages: { 'available': ' ', 'reserved': '', 'service': ' (1-2 )' } }; const map = document.getElementById('my-map'); const buildings = map.querySelectorAll('.building'); const info = map.querySelector('.info'); const lines = []; for (building of buildings) { const id = building.getAttribute('data-building-id'); const status = data[`id_${id}`].status; const price = data[`id_${id}`].price; building.classList.add(`-${status}`); const line = new LeaderLine( LeaderLine.pointAnchor(building, { x: '50%', y: '50%' }), LeaderLine.pointAnchor(info, { x: '50%', y: 0 }), { color: '#fff', startPlug: 'arrow1', endPlug: 'behind', endSocket: 'top' } ); lines.push(line); }
Wie Sie sehen, passiert nichts Kompliziertes. Die Linie hat „Befestigungspunkte“ zu den Elementen. Die Koordinaten dieser Punkte relativ zu den Elementen lassen sich in der Regel bequem in Prozent bestimmen. Im Allgemeinen gibt es viele verschiedene Optionen. Es ist nicht sinnvoll, sie aufzulisten und sich zu merken. Ich empfehle daher, nur die Dokumentation durchzusehen. Eine dieser Optionen - startLabel - wird benötigt, um einen kleinen Tooltip mit einem Preis zu erstellen.
const line = new LeaderLine( LeaderLine.pointAnchor(building, { x: '50%', y: '50%' }), LeaderLine.pointAnchor(info, { x: '50%', y: 0 }), { startLabel: LeaderLine.captionLabel(`${price}/`, { fontFamily: 'Rubik Mono One', fontWeight: 400, offset: [-30, -50], outlineColor: '#555' }), color: '#fff', startPlug: 'arrow1', endPlug: 'behind', endSocket: 'top', hide: true } );
Es stört niemanden, alle Tipps in einem grafischen Editor zu zeichnen. Wenn sie konsistenten Inhalt haben sollen, kann es sogar praktisch sein. Vor allem, wenn es darum geht, sie nach unterschiedlichen Positionen für unterschiedliche Objekte zu fragen.
Wir können auch die Option zum Ausblenden hinzufügen, damit nicht alle Linien als Besen angezeigt werden. Wir zeigen sie nacheinander, wenn Sie mit der Maus über die Gebäude fahren, denen sie entsprechen:
building.addEventListener('mouseover', () => { line.show(); }); building.addEventListener('mouseout', () => { line.hide(); });
Hier können Sie zusätzliche Informationen (in unserem Fall einfach den aktuellen Status des Objekts) an der Stelle zur Information anzeigen. Es wird sich fast herausstellen, was benötigt wird:

Solche Dinge sind selten für mobile Geräte konzipiert, aber es ist zu beachten, dass sie häufig auf dem Desktop im Vollbildmodus angezeigt werden, und sogar mit einigen Bedienfeldern an der Seite für zusätzliche Informationen, und Sie müssen alles schön strecken. So etwas zum Beispiel:
svg { width: 100%; height: 100%; }
In diesem Fall stimmen die Proportionen des SVG-Elements definitiv nicht mit den Proportionen des Bilds überein. Was zu tun
Nicht übereinstimmende Proportionen
Das erste, was mir einfällt, ist die Objektanpassung: Cover- Eigenschaft von CSS. Aber es gibt einen Punkt: Es ist absolut nicht in der Lage, mit SVG zu arbeiten. Und selbst wenn es funktionieren würde, könnten die Häuser an den Rändern des Plans aus den Rändern des Schemas herauskommen und völlig unzugänglich werden. Hier müssen Sie also einen etwas komplizierteren Weg gehen.
Erster Schritt. SVG hat ein Attribut preserveAspectRatio , das der Eigenschaft object-fit etwas ähnlich ist (natürlich nicht ganz, aber ...). Durch preserveAspectRatio="xMinYMin slice"
für das Haupt-SVG-Element unseres Plans erhalten wir eine erweiterte Schaltung ohne Hohlräume an den Rändern und ohne Verzerrung.
Zweiter Schritt Sie müssen mit der Maus ziehen und ablegen. Technisch haben wir noch eine solche Chance. Hier ist die Aufgabe vor allem für Anfänger komplizierter. Theoretisch gibt es Standardereignisse für Maus und Touchscreen, die verarbeitet werden können und den Wert dafür ermitteln, um wie viel die Karte verschoben werden muss. In der Praxis kann man sich jedoch sehr lange darauf einlassen. Hammer.js wird zur Hilfe kommen - eine weitere kleine Bibliothek, die die gesamte interne Küche in sich aufnimmt und eine einfache Oberfläche zum Arbeiten mit Drag & Drop, Wischen usw. bietet.
Wir müssen die Ebene mit den Gebäuden und dem Bild in alle Richtungen bewegen. Machen Sie es sich einfach:
const buildingsLayer = map.querySelector('.buildings_layer'); const hammertime = new Hammer(buildingsLayer); hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
Standardmäßig enthält hammer.js auch die Erkennung von svaypas, wir benötigen sie jedoch nicht auf der Karte. Deaktivieren Sie sie daher sofort, um uns nicht zu täuschen:
hammertime.get('swipe').set({ enable: false });
Jetzt musst du irgendwie verstehen, was genau du posten musst, um die Karte nur an die Ränder zu verschieben, aber nicht weiter. Ausgehend von einer einfachen Darstellung von zwei Rechtecken in unserem Kopf verstehen wir, dass wir dazu die Einrückung der Ebene mit Gebäuden aus dem übergeordneten Element (in unserem Fall SVG) von allen vier Seiten herausfinden müssen. GetBoundingClientRect kommt zur Rettung:
const layer = buildingsLayer.getBoundingClientRect(); const parent = svg.getBoundingClientRect(); const offsets = { top: layer.top - parent.top, bottom: layer.bottom - parent.bottom, right: layer.right - parent.right, left: layer.left - parent.left, };
Und warum haben wir immer noch keine zivilisierte (und stabile) Möglichkeit, dies zu tun? Das Abrufen von getBoundingClientRect ist in Bezug auf die Leistung jedes Mal sehr schlecht, aber die Auswahl ist nicht sehr reichhaltig und es ist fast unmöglich, eine Hemmung zu bemerken, sodass wir keine vorzeitigen Optimierungen finden, bei denen alles gut funktioniert. So oder so können wir die Position der Ebene bei Gebäuden überprüfen und alles nur verschieben, wenn es Sinn macht:
let translateX = 0; let translateY = 0; hammertime.on('pan', (e) => { const layer = buildingsLayer.getBoundingClientRect(); const parent = svg.getBoundingClientRect(); const offsets = { top: layer.top - parent.top, bottom: layer.bottom - parent.bottom, right: layer.right - parent.right, left: layer.left - parent.left, }; const speedX = e.velocityX * 10; const speedY = e.velocityY * 10; if (speedX > 0 && offsets.left < 0) {
An den Rändern lohnt es sich in der Regel, langsamer zu fahren, damit es nicht zu plötzlichen Stopps oder Rucken kommt. So geht alles in etwa so vor und zurück:
if (speedX < -offsets.left) { translateX += speedX; } else { translateX += -offsets.left * speedX / 10; }
Es gibt viele Möglichkeiten zur Verlangsamung. Dieser ist der einfachste. Und ja, es ist nicht sehr schön, aber es ist dumm wie ein Korken und klar. Die Koeffizienten in solchen Beispielen werden in Abhängigkeit vom gewünschten Verhalten der Karte normalerweise per Auge ausgewählt.
Wenn Sie einen Browser öffnen und mit der Fenstergröße in den Tools des Entwicklers spielen, können Sie feststellen, dass ein Fehler aufgetreten ist ...
Unreine Kräfte
Auf Desktop-Geräten funktioniert alles, aber auf Mobilgeräten geschieht Magie: Anstatt die Karte zu verschieben, bewegt sich das Körperelement. Oooooooo! Nur die Besetzung reicht da nicht. Obwohl in Ordnung, geschieht dies, weil irgendwo etwas überläuft und ein Überschreiben nicht eingestellt wurde: versteckt. In unserem Fall kann es jedoch vorkommen, dass sich überhaupt nichts bewegt.
Ein Rätsel für grüne Schriftsetzer: Es gibt das Element g im Element svg im Element div im Element body im Element html. Doctype natürlich HTML. Wenn Sie transform: translate (...) hinzufügen, um das g-Element zu ziehen, bewegt es sich auf dem Laptop wie beabsichtigt, auf dem Telefon jedoch nicht einmal. Es gibt keine Fehler in der Konsole. Aber es gibt definitiv einen Bug. Der Browser ist das letzte Chrome da und dort. Die Frage ist warum?
Ich schlage vor, Sie denken ungefähr 10 Minuten ohne Google nach, bevor Sie die Antwort lesen.

Die antwortHa ha! Ich habe dich betrogen. Genauer gesagt nicht so. Ich habe beschrieben, was wir beim manuellen Testen beobachten würden. Aber in Wirklichkeit funktioniert alles so, wie es sollte. Dies ist kein Fehler, sondern eine Funktion, die mit der CSS-Eigenschaft von touch-action zusammenhängt . Im Kontext unserer Aufgabe (ganz plötzlich!) Zeigt sich, dass sie existiert und darüber hinaus einen bestimmten Wert hat, der die gesamte Logik der Interaktion mit der Karte durchbricht. Deshalb gehen wir sehr grob mit ihm um:
svg { touch-action: none !important; }
Aber zurück zu unseren Schafen und schauen Sie sich das Ergebnis an (es ist natürlich besser, es in einem separaten Tab zu öffnen):
Ich habe beschlossen, den Code für keines der modischen Frameworks anzupassen, damit er in Form eines neutralen, formlosen Rohlings bleibt, auf dem Sie beim Erstellen Ihrer Komponenten aufbauen können.
Was ist das ergebnis
Nach langer Zeit haben wir einen Plan erstellt, auf dem ein Rasterbild angezeigt wird, das die verschiedenen Details hervorhebt und nicht miteinander verbundene Objekte mit Pfeilen und Reaktionen mit der Maus verbindet. Ich hoffe, dass es mir gelungen ist, die Grundidee zu vermitteln, wie das alles in der „Budget“ -Version gemacht wird. Wie wir am Anfang des Artikels bemerkt haben, gibt es viele verschiedene Anwendungen, einschließlich solcher, die sich nicht auf verwirrte Design-Sites beziehen (obwohl dieser Ansatz bei ihnen sehr häufig angewendet wird). Wenn Sie etwas über interaktive, aber bereits dreidimensionale Dinge lesen möchten, hinterlasse ich einen Link zu einem Artikel zum Thema - Dreidimensionale Produktpräsentationen auf Three.js für die Kleinsten .