Ein Objekt fester Größe in ein responsives Element verwandeln

Avatar of Philip Kiely
Philip Kiely am

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

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

  1. calc(), eine CSS-Funktion, die Berechnungen durchführen kann, auch wenn Eingaben unterschiedliche Einheiten haben
  2. --size-divisor, eine CSS-Benutzerdefinierte Eigenschaft, die mit der var()-Funktion verwendet wird
  3. @media-Abfragen, getrennt durch min-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.