Wie man eine pixelgenaue, linear skalierte Benutzeroberfläche erhält

Avatar of Georgi Nikoloff
Georgi Nikoloff am

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

Das dynamische Skalieren von CSS-Werten basierend auf der Viewport-Breite ist kaum ein neues Thema. Hier auf CSS-Tricks finden Sie zahlreiche ausführliche Berichte in Artikeln wie diesem oder diesem.

Die meisten dieser Beispiele verwenden zwar relative CSS-Einheiten und wertlose Werte, um eine flüssige Skalierung zu erreichen. Das geht auf Kosten der Pixelgenauigkeit und führt normalerweise zu Textumbrüchen und Layoutverschiebungen, sobald der Bildschirm unter oder über einen bestimmten Schwellenwert fällt.

Aber was, wenn wir wirklich Pixelgenauigkeit wollen? Was, wenn wir zum Beispiel ein komplexes Echtzeit-Analyse-Dashboard für große Fernseher in einem Konferenzraum entwickeln oder eine PWA, die ausschließlich auf mobilen Geräten und Tablets geöffnet wird, im Gegensatz zu textlastigen Blogs und Nachrichtenseiten? Das sind Fälle, in denen wir mehr Präzision benötigen.

Mit anderen Worten, was, wenn wir Designs einheitlich skalieren wollen? Natürlich kann man den Inhalt mit CSS-Transformationen basierend auf der verfügbaren Breite skalieren, wie in diesem Artikel beschrieben – so bleiben die richtigen Verhältnisse erhalten.

Wir können jedoch auch flüssige proportionale Skalierungs-Benutzeroberflächen mithilfe von Pixelwerten in CSS erzielen. Diese skalieren entsprechend der Bildschirmfläche des Geräts, während sie gleichzeitig ihre pixelgenauen Proportionen beibehalten. Darüber hinaus können wir weiterhin Pixelwerte verwenden und sie automatisch in relative CSS-Einheiten umwandeln, wenn die Arbeit in Pixeln angenehmer oder vertrauter ist.

Unsere Benutzeroberfläche skalieren

Versuchen wir, dieses fantastische Dashboard von Craftwork zu implementieren. Wir müssen es so gestalten, dass es perfekt skaliert und alle Textzeilenanzahlen, Abstände, Bildgrößen usw. beibehält.

Wir arbeiten mit CSS-Pixelwerten und verwenden SCSS für Geschwindigkeit und Komfort. Wenn wir also den Titel eines dieser Karten-Widgets ansprechen wollen, könnte unser SCSS so aussehen:

.cardWidget {
  .cardHeading {
    font-size: 16px;
  }
}

Nichts Besonderes. Nichts, was wir nicht schon gesehen hätten. Als Pixelwert wird dies nicht skaliert.

Dieses Design wurde mit einem Container mit einer Breite von 1600px erstellt. Nehmen wir an, dass bei 1600px die ideale Schriftgröße für die Titel der Karten 16px sein sollte, da es so entworfen wurde.

Nachdem wir nun die ideale Schriftgröße für den Container für diese Breite haben, skalieren wir unsere CSS-Pixelwerte entsprechend der aktuellen* Ansichtsbreite.

/*
  1600px is the ideal viewport width that the UI designers who
  created the dashboard used when designing their Figma artboards

  Please not we are not using pixel units here, treating it purely
  as a numeric value.
*/
--ideal-viewport-width: 1600;
/*
  The actual width of the user device
*/
--current-viewport-width: 100vw;

.cardWidget {
  .cardHeading {
    /*
      16px is the ideal font size that the UI designers want for
      1600px viewport width.

      Please note that we are not using pixel units here,
      treating it purely as a numeric value.
    */
    --ideal-font-size: 16;
    /*
      Calculate the actual font size:

      We take our idealFontSize and multiply it by the difference
      between the current viewport width and the ideal viewport width.
    */
    font-size: calc(
      var(--ideal-font-size) * (var(--current-viewport-width) / var(--ideal-viewport-width)
    );
  }
}

Wie Sie sehen, behandeln wir die ideale Schriftgröße, die wir aus dem Design erhalten haben, als Basis und multiplizieren sie mit der Differenz zwischen der aktuellen und der idealen Ansichtsbreite. Wie sieht das mathematisch aus? Nehmen wir an, wir betrachten diese Webanwendung auf einem Bildschirm mit exakt derselben Breite wie das Mockup.

--current-device-width: 100vw; // represents 1600px or full width of the screen
--ideal-viewport-width: 1600; // notice that the ideal and current width match
--ideal-font-size: 16;
// this evaluates to:
font-size: calc(16 * 1600px / 1600);
// same as:
font-size: calc(16 * 1px);
// final result:
font-size: 16px;

Da unsere Ansichtsbreite perfekt übereinstimmt, beträgt unsere Schriftgröße bei der idealen Ansichtsbreite von 1600px genau 16px.

Als weiteres Beispiel nehmen wir an, wir betrachten die Webanwendung auf einem kleineren Laptop-Bildschirm mit einer Breite von 1366px. Hier ist die aktualisierte Mathematik:

font-size: calc(16 * 1366px / 1600);
// same as:
font-size: calc(16 * 0.85375px);
// final result:
font-size: 13.66px;

Oder nehmen wir an, wir betrachten dies auf einem Full-HD-Display mit 1920px Breite.

font-size: calc(16 * 1920px / 1600);
// same as:
font-size: calc(16 * 1.2px);
// final result:
font-size: 19.2px;

Sie sehen selbst, dass wir, obwohl wir Pixelwerte als Referenz verwenden, unsere CSS-Werte proportional zur Differenz der Breite zwischen der idealen und der aktuellen Ansichtsgröße skalieren können.

Hier ist eine kleine Demo, die ich zur Veranschaulichung der Technik erstellt habe:

Hier ist ein Video zur Bequemlichkeit:

Begrenzung der minimalen und maximalen Ansichtsbreite

Mit dem aktuellen Ansatz skaliert das Design, um der Ansichtsgröße zu entsprechen, egal wie groß oder klein die Ansicht wird. Wir können dies mit dem CSS-Befehl clamp() verhindern, der es uns ermöglicht, eine minimale Breite von 350px und eine maximale Breite von 3840px festzulegen. Das bedeutet, dass, wenn wir die Webanwendung auf einem Gerät mit 5000px Breite öffnen, unser Layout bei 3840px gesperrt bleibt.

--ideal-viewport-width: 1600;
--current-viewport-width: 100vw;
/*
  Set our minimum and maximum allowed layout widths:
*/
--min-viewport-width: 350px;
--max-viewport-width: 3840px;

.cardWidget {
  .cardHeading {
    --ideal-font-size: 16;
    font-size: calc(
      /*
        The clamp() function takes three comma separated expressions
        as its parameter, in the order of minimum value, preferred value
        and maximum value:
      */
      --clamped-viewport-width: clamp(var(--min-viewport-width), var(--current-viewport-width), var(--max-viewport-width);
      /*
        Use the clamped viewport width in our calculation
      */
      var(--ideal-font-size) * var(--clamped-viewport-width) / var(--ideal-viewport-width)
    );
  }
}

Machen wir einen Helfer für die Einheitenumrechnungen

Unser Code ist ziemlich ausführlich. Schreiben wir eine einfache SCSS-Funktion, die unsere Werte von Pixeln in relative Einheiten umwandelt. So können wir sie importieren und überall wiederverwenden, ohne viel Duplikation.

/*
  Declare a SCSS function that takes a value to be scaled and
  ideal viewport width:
*/
@function scaleValue(
  $value,
  $idealViewportWidth: 1600px,
  $min: 350px,
  $max: 3840px
) {
  @return calc(
    #{$value} * (clamp(#{$min}, 100vw, #{$max}) / #{$idealViewportWidth})
  );
}

/*
  We can then apply it on any numeric CSS value.

  Please note we are passing not pixel based, but numeric values:
*/
.myElement {
  width: #{scaleValue(500)};
  height: #{scaleValue(500)};
  box-shadow: #{scaleValue(2)} #{scaleValue(2)} rgba(black, 0.5);
  font-size: #{scaleValue(24)};
}

Portierung auf JavaScript

Manchmal reicht CSS nicht aus und wir müssen JavaScript verwenden, um eine Komponente zu dimensionieren. Nehmen wir an, wir erstellen dynamisch eine SVG und müssen ihre Breite und Höhe basierend auf einer idealen Designbreite festlegen. Hier ist das JavaScript, um das zu erreichen:

/*
  Our helper method to scale a value based on the device width
*/
const scaleValue = (value, idealViewportWidth = 1600) => {
  return value * (window.innerWidth / idealViewportWidth)
}

/*
  Create a SVG element and set its width, height and viewbox properties
*/
const IDEAL_SVG_WIDTH = 512
const IDEAL_SVG_HEIGHT = 512

const svgEl = document.createElement('svg')
/* Scale the width and height */
svgEl.setAttribute('width', scaleValue(IDEAL_SVG_WIDTH))
svgEl.setAttribute('height', scaleValue(IDEAL_SVG_WIDTH))

/*
  We don't really need to scale the viewBox property because it will
  perfectly match the ratio of the scaled width and height
*/
svg.setAttribute('viewBox', `0 0 ${IDEAL_SVG_WIDTH} ${IDEAL_SVG_HEIGHT}`)

Die Nachteile dieser Technik

Diese Lösung ist nicht perfekt. Ein großer Nachteil ist zum Beispiel, dass die Benutzeroberflächen nicht mehr zoombar sind. Egal wie viel der Benutzer zoomt, die Designs bleiben gesperrt, als würden sie mit 100% Zoom betrachtet.

Dennoch können wir traditionelle Media-Queries verwenden, bei denen wir für verschiedene Ansichtsbreiten unterschiedliche ideale numerische Werte festlegen.

.myElement {
  width: #{scaleValue(500)};
  height: #{scaleValue(500)};
  box-shadow: #{scaleValue(2)} #{scaleValue(2)} rgba(black, 0.5);
  font-size: #{scaleValue(24)};
  @media (min-width: 64em) {
    width: #{scaleValue(800)};
    font-size: #{scaleValue(42)};
  }
}

Jetzt können wir sowohl von Media-Queries als auch von unserer pixelgenauen linearen Skalierung profitieren.

Zusammenfassung

Dies alles ist eine alternative Methode zur Implementierung von flüssigen Benutzeroberflächen. Wir behandeln die pixelgenauen Werte als reine numerische Werte und multiplizieren sie mit der Differenz zwischen der aktuellen Ansichtsbreite und der "idealen" Ansichtsbreite aus den Designs.

Ich habe diese Technik in meiner Arbeit ausgiebig genutzt und hoffe, dass Sie sie auch nützlich finden werden.