Ich war kürzlich in einer Situation, in der ich ein iPhone auf einer Website anzeigen wollte. Ich wollte, dass die Benutzer mit einer Anwendung auf diesem „Mock“-Telefon interagieren können, daher musste es in CSS gerendert werden, nicht als Bild. Ich fand eine großartige Bibliothek namens marvelapp/devices.css. Die Bibliothek implementierte das benötigte Gerät mit reinem CSS und sah großartig aus, aber es gab ein Problem: Die angebotenen Geräte waren nicht responsiv (d. h. sie konnten nicht skaliert werden). Ein offenes Problem listete einige Optionen auf, aber jede hatte Browser-Inkompatibilitäten oder andere Probleme. Ich machte mich daran, die Bibliothek zu modifizieren, um die Geräte responsiv zu machen.
Hier ist die endgültige skalierbare Bibliothek. Im Folgenden werden wir den Code durchgehen, der zu ihrer Erstellung verwendet wurde.
Die ursprüngliche Bibliothek wurde in Sass geschrieben und implementiert die Geräte mithilfe von Elementen mit fester Pixelgröße. Die Autoren stellten auch ein unkompliziertes HTML-Beispiel für jedes Gerät zur Verfügung, einschließlich des iPhone X, mit dem wir in diesem Artikel arbeiten werden. Hier ist ein Blick auf das Original. Beachten Sie, dass das gerenderte Gerät, obwohl detailliert, eher groß ist und seine Größe nicht ändert.
Hier ist der Ansatz
Es gibt drei CSS-Tricks, die ich verwendet habe, um die Geräte skalierbar zu machen
calc(), eine CSS-Funktion, die Berechnungen durchführen kann, auch wenn Eingaben unterschiedliche Einheiten haben--size-divisor, eine CSS-Benutzerdefinierte Eigenschaft, die mit dervar()-Funktion verwendet wird@media-Abfragen, getrennt durchmin-width
Schauen wir uns jeden einzelnen an.
calc()
Die CSS calc()-Funktion ermöglicht es uns, die Größe der verschiedenen Aspekte des Geräts zu ändern. Die Funktion nimmt einen Ausdruck als Eingabe und gibt die Auswertung der Funktion als Ausgabe mit entsprechenden Einheiten zurück. Wenn wir die Geräte halb so groß wie ihre ursprüngliche Größe machen wollten, müssten wir jede Pixelmessung durch 2 teilen.
Vorher
width: 375px;
Nachher
width: calc(375px / 2);
Der zweite Ausschnitt ergibt eine Länge, die halb so groß ist wie der erste Ausschnitt. Jede Pixelmessung in der ursprünglichen Bibliothek muss in eine calc()-Funktion eingepackt werden, damit das gesamte Gerät skaliert wird.
var(–size-divisor)
Eine CSS-Variable muss zuerst am Anfang der Datei deklariert werden, bevor sie irgendwo verwendet werden kann.
:root {
--size-divisor: 3;
}
Damit ist dieser Wert über das gesamte Stylesheet mit der var()-Funktion zugänglich. Dies wird äußerst nützlich sein, da wir alle Pixelanzahlen beim Skalieren von Geräten durch dieselbe Zahl teilen möchten, während wir magische Zahlen vermeiden und den Code zur responsiven Gestaltung der Geräte vereinfachen.
width: calc(375px / var(--size-divisor));
Mit dem oben definierten Wert würde dies die ursprüngliche Breite geteilt durch drei, in Pixeln, zurückgeben.
@media
Um die Geräte responsiv zu machen, müssen sie auf Änderungen der Bildschirmgröße *reagieren*. Dies erreichen wir mit Media Queries. Diese Abfragen beobachten die Breite des Bildschirms und werden ausgelöst, wenn die Bildschirmgröße bestimmte Schwellenwerte überschreitet. Ich habe die Breakpoints basierend auf den Größen von Bootstrap für xs, sm und so weiter gewählt.
Es ist nicht notwendig, einen Breakpoint bei einer minimalen Breite von null Pixeln festzulegen; stattdessen behandelt die :root-Deklaration am Anfang der Datei diese extrakleinen Bildschirme. Diese Media Queries müssen am Ende des Dokuments stehen und in aufsteigender Reihenfolge von min-width angeordnet sein.
@media (min-width: 576px) {
:root {
--size-divisor: 2;
}
}
@media (min-width: 768px) {
:root {
--size-divisor: 1.5;
}
}
@media (min-width: 992px) {
:root {
--size-divisor: 1;
}
}
@media (min-width: 1200px) {
:root {
--size-divisor: .67;
}
}
Das Ändern der Werte in diesen Abfragen passt die Größenordnung der Skalierung an, die das Gerät erfährt. Beachten Sie, dass calc() mit Gleitkommazahlen genauso gut umgeht wie mit ganzen Zahlen.
Vorverarbeitung des Präprozessors mit Python
Mit diesen Werkzeugen zur Hand benötigte ich eine Möglichkeit, meinen neuen Ansatz auf die tausende Zeilen lange Bibliothek anzuwenden. Die resultierende Datei beginnt mit einer Variablendeklaration, enthält die gesamte Bibliothek, wobei jede Pixelmessung in eine calc()-Funktion eingepackt ist, und endet mit den oben genannten Media Queries.
Anstatt die Änderungen von Hand vorzubereiten, erstellte ich ein Python-Skript, das alle Pixelmessungen automatisch konvertiert.
def scale(token):
if token[-2:] == ';\n':
return 'calc(' + token[:-2] + ' / var(--size-divisor));\n'
elif token[-3:] == ');\n':
return '(' + token[:-3] + ' / var(--size-divisor));\n'
return 'calc(' + token + ' / var(--size-divisor))'
Diese Funktion gibt, wenn sie einen String mit NNpx erhält, calc(NNpx / var(--size-divisor)); zurück. Im gesamten File gibt es glücklicherweise nur drei Treffer für Pixel: NNpx, NNpx; und NNpx);. Der Rest ist reine String-Verkettung. Diese Tokens wurden jedoch bereits durch Trennung jeder Zeile durch Leerzeichen generiert.
def build_file(scss):
out = ':root {\n\t--size-divisor: 3;\n}\n\n'
for line in scss:
tokens = line.split(' ')
for i in range(len(tokens)):
if 'px' in tokens[i]:
tokens[i] = scale(tokens[i])
out += ' '.join(tokens)
out += "@media (min-width: 576px) {\n \
:root {\n\t--size-divisor: 2;\n \
}\n}\n\n@media (min-width: 768px) {\n \
:root {\n\t--size-divisor: 1.5;\n \
}\n}\n\n@media (min-width: 992px) { \
\n :root {\n\t--size-divisor: 1;\n \
}\n}\n\n@media (min-width: 1200px) { \
\n :root {\n\t--size-divisor: .67;\n }\n}"
return out
Diese Funktion, die die neue Bibliothek erstellt, beginnt mit der Deklaration der CSS-Variable. Dann durchläuft sie die gesamte alte Bibliothek auf der Suche nach Pixelmessungen, die skaliert werden müssen. Für jedes der Hunderte von Tokens, die px enthalten, wird dieses Token skaliert. Während der Iteration behält die Funktion die ursprüngliche Zeilenstruktur bei, indem sie die Tokens wieder zusammenfügt. Schließlich hängt sie die notwendigen Media Queries an und gibt die gesamte Bibliothek als String zurück. Ein wenig Code zum Ausführen der Funktionen und zum Lesen und Schreiben von Dateien erledigt den Rest.
if __name__ == '__main__':
f = open('devices_old.scss', 'r')
scss = f.readlines()
f.close()
out = build_file(scss)
f = open('devices_new.scss', 'w')
f.write(out)
f.close()
Dieser Prozess erstellt eine neue Bibliothek in Sass. Um die CSS-Datei für die endgültige Verwendung zu erstellen, führen Sie
sass devices_new.scss devices.css
Diese neue Bibliothek bietet die gleichen Geräte, aber sie sind responsiv! Hier ist sie
Um die tatsächliche Ausgabedatei zu lesen, die Tausende von Zeilen lang ist, sehen Sie sie sich auf GitHub an.
Andere Ansätze
Während die Ergebnisse dieses Prozesses ziemlich überzeugend sind, war es doch einiges an Arbeit, sie zu erzielen. Warum habe ich keinen einfacheren Ansatz gewählt? Hier sind drei weitere Ansätze mit ihren Vor- und Nachteilen.
zoom
Ein anfänglich vielversprechender Ansatz wäre die Verwendung von `zoom`, um die Geräte zu skalieren. Dies würde das Gerät einheitlich skalieren und könnte wie bei meiner Lösung mit Media Queries kombiniert werden, würde aber ohne die problematische calc() und Variable funktionieren.
Das funktioniert aus einem einfachen Grund nicht: `zoom` ist eine nicht standardmäßige Eigenschaft. Unter anderem wird sie in Firefox nicht unterstützt.
Ersetze px durch em
Ein weiterer Suchen-und-Ersetzen-Ansatz schlägt vor, alle Instanzen von `px` durch `em` zu ersetzen. Dann schrumpfen und wachsen die Geräte entsprechend der Schriftgröße. Um sie jedoch klein genug zu machen, um auf ein mobiles Display zu passen, sind möglicherweise winzige Schriftgrößen erforderlich, kleiner als die Mindestgrößen, die Browser wie Chrome erzwingen. Dieser Ansatz könnte auch Probleme verursachen, wenn ein Besucher Ihrer Website assistierende Technologien verwendet, die die Schriftgröße erhöhen.
Dies könnte durch Skalierung aller Werte um einen Faktor von 100 und anschließende Anwendung von Standard-Schriftgrößen behoben werden. Dies erfordert jedoch genauso viel Vorverarbeitung wie der Ansatz dieses Artikels, und ich denke, es ist eleganter, diese Berechnungen direkt durchzuführen, anstatt die Schriftgrößen zu manipulieren.
scale()
Die scale()-Funktion kann die Größe ganzer Objekte ändern. Die Funktion gibt eine <transform-function> zurück, die einem `transform`-Attribut in einem Stil zugewiesen werden kann. Insgesamt ist dies ein guter Ansatz, ändert aber nicht die tatsächliche Messung des CSS-Geräts. Ich bevorzuge meinen Ansatz, insbesondere bei der Arbeit mit komplexen UI-Elementen, die auf dem "Bildschirm" des Geräts gerendert werden.
Chris Coyier hat ein Beispiel mit diesem Ansatz vorbereitet.
Zusammenfassend
Hunderte von calc()-Aufrufen sind vielleicht nicht das erste Werkzeug, das ich zur Hand nehmen würde, wenn ich diese Bibliothek von Grund auf neu implementieren würde, aber insgesamt ist dies ein effektiver Ansatz, um bestehende Bibliotheken skalierbar zu machen. Das Hinzufügen von Variablen und Media Queries macht die Objekte responsiv. Sollte die zugrunde liegende Bibliothek aktualisiert werden, wäre das Python-Skript in der Lage, diese Änderungen in eine neue Version unserer responsiven Bibliothek zu verarbeiten.
Warum kein SVG für den Telefonrahmen verwenden? Es wäre trivial, mit CSS die Demo-App "innerhalb" des Telefonbildschirms zu positionieren.
Ich habe vor ein paar Jahren die px-em-Route mit dieser Bibliothek verwendet, aber nicht an die möglichen Auswirkungen auf die Zugänglichkeit gedacht. Guter Hinweis!
Die Skalierungsroute hat einen weiteren Vorteil, der auf der Implementierung der ursprünglichen Bibliothek basiert. Die ursprünglichen Pixelbeträge basieren auf den Abmessungen der fraglichen Geräte in CSS-Pixeln. Das bedeutet, dass, wenn Sie ein Element mit einer bestimmten Größe im gerenderten Gerät positionieren, es dieselbe Größe im Verhältnis zum gerenderten Viewport hat, wie dieses Element zum echten Ding hätte. Wenn Sie Scale verwenden, wird dieser Aspekt beibehalten.