Wir entwickeln eine
Plattform für die visuelle Zusammenarbeit . Wir verwenden Canvas, um Inhalte anzuzeigen: Alles wird darauf gezeichnet, einschließlich Texte. Es gibt keine vorgefertigte Lösung für die Anzeige von Texten auf Canvas eins zu eins wie in HTML. Während unserer mehrjährigen Arbeit mit Text-Rendering haben wir verschiedene Implementierungsoptionen untersucht, viele Probleme behoben und anscheinend eine gute Lösung gefunden. Ich werde Ihnen in einem Artikel erzählen, wie wir von Flash zu Canvas gewechselt sind und warum wir SVG ForeignObject aufgegeben haben.

Mit Flash bewegen
Wir haben das Produkt 2015 auf Flash erstellt. In Flash gibt es einen Texteditor, der gut mit Texten arbeiten kann, sodass wir nichts extra tun mussten, um mit Texten zu arbeiten. Aber zu diesem Zeitpunkt war Flash bereits im Sterben, also sind wir von HTML zu Canvas übergegangen. Vor uns bestand die Aufgabe darin, den Text auf der Leinwand wie im HTML-Editor anzuzeigen, ohne die in der Flash-Version erstellten Texte beim Verschieben zu beschädigen.
Wir wollten es so gestalten, dass der Benutzer den Text direkt in unserem Produkt bearbeiten kann, ohne den Übergang zwischen dem Bearbeitungs- und dem Rendering-Modus zu bemerken. Die Lösung, die wir gesehen haben, ist folgende: Wenn Sie auf einen Bereich mit Text klicken, wird ein Texteditor geöffnet, in dem Sie den Text ändern können. Sie können den Editor schließen, indem Sie den Cursor vom Textbereich wegbewegen. In diesem Fall sollte die Anzeige von Text auf der Leinwand 1 zu 1 der Anzeige von Text im Editor entsprechen.
Als Editor verwendeten wir eine offene Bibliothek, aber vorgefertigte Bibliotheken zum Rendern von HTML auf Canvas passten nicht zu der Arbeitsgeschwindigkeit und der unzureichenden Funktionalität.
Wir haben verschiedene Lösungen untersucht:
- Standard Canvas.fillText. Kann Text wie in HTML zeichnen, kann gestylt werden und funktioniert in allen Browsern. Es ist jedoch nicht bekannt, wie Links wie in einem HTML-Editor mehrzeilige Texte mit unterschiedlicher Formatierung gezeichnet werden. Diese Schwierigkeiten können gelöst werden, erfordern jedoch viel Zeit.
- Zeichne DOM auf Leinwand. Die Option passte nicht zu uns, weil In unserem Produkt hat jedes erstellte Objekt einen eigenen Z-Index auf der Leinwand. Und das Mischen mit dem DOM-Z-Index funktioniert nicht.
- Konvertieren Sie HTML in SVG. Dank des ForeignObject-Elements kann er HTML in ein Bild verwandeln. Auf diese Weise können Sie HTML in svg backen und als Bild damit arbeiten. Wir haben diese Option gewählt.
Eigenschaften SVG ForeignObject
So funktioniert SVG ForeignObject: Wir haben HTML aus dem Editor → HTML in ForeignObject einfügen → etwas Magie → Holen Sie sich das Bild → Fügen Sie das Bild zur Leinwand hinzu
Über Magie. Trotz der Tatsache, dass die meisten Browser das ForeignObject-Tag unterstützen, hat jeder seine eigenen Eigenschaften für die Verwendung des Ergebnisses mit Canvas. FireFox funktioniert mit einem Blob-Objekt. In Edge müssen Sie Base64 für das Image ausführen und die Daten-URL zurückgeben. In IE11 funktioniert das Tag überhaupt nicht.
getImageUrl(svg: string, browser: string): string { let dataUrl = '' switch (browser) { case browsers.FIREFOX: let domUrl = window.URL || window.webkitURL || window let blob = new Blob([svg], {type: 'image/svg+xml;charset=utf-8'}) dataUrl = domUrl.createObjectURL(blob) break case browsers.EDGE: let encodedSvg = encodeURIComponent(svg) dataUrl = 'data:image/svg+xml;base64,' + btoa(window.unescape(encodedSvg)) break default: dataUrl = 'data:image/svg+xml,' + encodeURIComponent(svg) return dataUrl }
Nach der Arbeit mit SVG haben wir interessante Fehler erhalten, die wir bei Flash nicht bemerkt haben. Text mit derselben Größe und Schriftart in verschiedenen Browsern wurde unterschiedlich angezeigt. Zum Beispiel könnte das letzte Wort in einer Zeile umbrochen und in den folgenden Text eingefügt werden. Für uns war es wichtig, dass Benutzer unabhängig von den Browsern, in denen sie arbeiten, die gleichen Widgets erhalten. Es gab kein Problem mit Flash, da er ist überall gleich.

Wir haben dieses Problem gelöst. Erstens wurde bei allen einzeiligen Texten immer die Breite berücksichtigt, unabhängig vom Browser und den Daten vom Server. Bei der Höhe bleibt der Unterschied bestehen, in unserem Fall stört er die Benutzer jedoch nicht.
Zweitens kamen wir experimentell zu dem Schluss, dass es notwendig ist, einige ungewöhnliche CSS-Stile für den Editor und das SVG hinzuzufügen, um den Unterschied in der Anzeige zwischen Browsern zu verringern:
- Font-Kerning: Auto; steuert den Kerning der Schriftart. Weitere Details
- Webkit-Font-Glättung: Antialiasing; verantwortlich für die Glättung. Weitere Details .
Was wir am Ende dank SVG <foreignObject> bekommen haben:
- Wir können jedes HTML zeichnen: Text, Tabellen, Grafiken
- Das Tag gibt ein Vektorbild zurück.
- Das Tag funktioniert in allen modernen Browsern außer IE11
Warum wir ForeignObject aufgegeben haben
Alles hat gut funktioniert, aber als Designer zu uns kamen und darum baten, Schriftartenunterstützung hinzuzufügen, um Modelle zu erstellen.

Wir haben uns gefragt, ob wir das mit ForeignObject machen können. Es stellte sich heraus, dass er eine Funktion hat, die bei der Lösung dieses Problems zu einem fatalen Fehler wird. Es kann HTML in sich selbst anzeigen, aber nicht auf externe Ressourcen zugreifen. Daher müssen alle Ressourcen, mit denen es arbeitet, in base64 konvertiert und in svg hinzugefügt werden.

Wenn Sie also vier von OpenSans geschriebene Texte haben, müssen Sie diese Schriftart viermal auf den Benutzer herunterladen. Diese Option passte nicht zu uns.
Wir haben beschlossen, unseren Canvas-Text mit ... guter Leistung und Unterstützung für Vektorbilder zu schreiben. Wir werden IE 11 nicht vergessen
Warum ist uns ein Vektorbild wichtig? In unserem Produkt kann jedes Objekt auf der Tafel gezoomt werden, und mit einem Vektorbild können wir es nur einmal erstellen und unabhängig vom Zoom wiederverwenden. Canvas.fillText zeichnet eine Bitmap: In diesem Fall müssen wir das Bild bei jedem Zoom neu zeichnen, was, wie wir dachten, die Leistung stark beeinträchtigt.
Erstellen Sie einen Prototyp
Zunächst haben wir einen einfachen Prototyp erstellt, um seine Leistung zu testen.

Das Funktionsprinzip des Prototyps:
- Wir geben der Funktion "Text";
- Daraus erhalten wir ein Objekt, in dem jedes Wort aus dem Text enthalten ist, mit Koordinaten und Stilen zum Rendern.
- Gib das Objekt Canvas;
- Leinwand zeichnet Text.
Der Prototyp hatte mehrere Aufgaben: zu überprüfen, ob das Neuzeichnen von Canvas mit der Skalierung unverzüglich erfolgt und dass die Zeit für die Umwandlung von HTML in ein Objekt nur die Erstellung eines SVG-Bildes beträgt.
Der Prototyp bewältigte die erste Aufgabe, die Skalierung hatte fast keinen Einfluss auf die Leistung beim Zeichnen von Texten. Bei der zweiten Aufgabe gab es Probleme: Die Verarbeitung großer Textmengen nimmt genügend Zeit in Anspruch, und die ersten Leistungsmessungen zeigten schlechte Ergebnisse. Um Text aus 1K-Zeichen zu zeichnen, dauerte der neue Ansatz fast zweimal länger als svg.

Wir haben uns entschieden, den Code auf die zuverlässigste Weise zu optimieren - "Ersetzen Sie den Test durch den, den wir brauchen" ;-). Aber im Ernst, wir gingen zu den Analysten und fragten, wie lange die Texte am häufigsten von unseren Benutzern erstellt werden. Es stellte sich heraus, dass die durchschnittliche Textgröße 14 Zeichen beträgt. Für solche Kurztexte zeigte unser Prototyp deutlich bessere Leistungsergebnisse als Die Abhängigkeit der Geschwindigkeit vom Textvolumen ist linear, und das Umbrechen in SVG erfolgt fast immer zur gleichen Zeit, unabhängig von der Länge des Textes. Es hat uns gepasst: Wir können bei langen Texten an Leistung verlieren, aber in den meisten Fällen ist unsere Geschwindigkeit besser als bei SVG.
Nach mehreren Iterationen der Arbeit am Canvas Text-Update haben wir den folgenden Algorithmus erhalten:
Stufe 1. Wir brechen in logische Blöcke auf- Wir teilen den Text in Blöcke: Absätze, Listen;
- Wir teilen Blöcke nach Stilen in kleinere Blöcke auf.
- Wir brechen die Blöcke in Worte.
Stufe 2. Wir sammeln in einem Objekt mit Koordinaten und Stilen- Zählen Sie die Breite und Höhe jedes Wortes in px;
- Wir verbinden die geteilten Wörter, da in Punkt 2 einige Wörter in mehrere geteilt wurden;
- Aus den Wörtern, die wir sammeln, schneiden wir, wenn das Wort nicht in die Linie passt, bis es passt;
- Wir sammeln Absätze und Listen;
- Wir berechnen x, y für jedes Wort;
- Wir erhalten ein fertiges Objekt zum Rendern.
Der Vorteil dieses Ansatzes besteht darin, dass wir den gesamten Code von HTML bis zu einem Textobjekt mit Unit-Tests abdecken können. Dank dessen können wir das Rendering und das Parsen selbst separat überprüfen, was uns geholfen hat, die Entwicklung erheblich zu beschleunigen.
Infolgedessen haben wir Schriftarten und IE 11 unterstützt, alles mit Komponententests abgedeckt und die Rendergeschwindigkeit wurde in den meisten Fällen höher als die von ForeignObject. In Beta-Benutzern eingecheckt und freigegeben. Erfolg scheint zu sein!
Der Erfolg dauerte 30 Minuten
Bisher haben Leute mit einem rechtshändigen Schriftsystem keinen technischen Support geschrieben. Es stellte sich heraus, dass wir die Existenz solcher Sprachen vergessen haben:

Glücklicherweise war das Hinzufügen von Unterstützung für das rechtshändige Schriftsystem nicht schwierig, da dies bereits von Canvas.fillText unterstützt wird.
Während wir uns damit befassten, stießen wir auf noch interessantere Fälle, die fillText nicht mehr unterstützen konnte. Wir sind auf bidirektionale Texte gestoßen, in denen ein Teil des Textes von rechts nach links, dann von links nach rechts und wieder von rechts nach links geschrieben ist.

Die einzige Lösung, die wir kannten, bestand darin, in die W3C-Spezifikation für Browser zu gehen und zu versuchen, dies in Canvas Text zu wiederholen. Es war schwierig und schmerzhaft, aber wir konnten grundlegende Unterstützung hinzufügen. Mehr zu bidirektional:
eins und
zwei .
Kurze Schlussfolgerungen, die wir für uns selbst gezogen haben
- Verwenden Sie SVG ForeignObject, um HTML in einem Bild anzuzeigen.
- Analysieren Sie Ihr Produkt immer zur Entscheidungsfindung.
- Prototypen herstellen. Sie können zeigen, dass komplexe Entscheidungen nur auf den ersten Blick so erscheinen können;
- Schreiben Sie den Code sofort, damit er mit Tests abgedeckt werden kann.
- Bei einem internationalen Produkt ist es wichtig, nicht zu vergessen, dass es viele verschiedene Sprachen gibt, einschließlich biderektional.
Wenn Sie Erfahrung in der Lösung solcher Probleme haben, teilen Sie diese in den Kommentaren mit.