Optimieren von Bildern für die Webleistung mit NGINX

Bilder sind eine ständige Schmerzquelle bei der Entwicklung von Websites. Es gibt viele Formate und Auflösungen ein Entwickler berücksichtigen muss, um die Webleistung zu maximieren. Am Ende erhalten Sie oft eine kartesische Explosion desselben Bildes in verschiedenen Größen und Formaten, um verschiedene Szenarien zu unterstützen.

Beispielsweise möchten Sie kein hochauflösendes Bild, das für Bildschirme mit hoher DPI bestimmt ist, an einen Bildschirm mit niedriger DPI senden – Sie würden Bandbreite und Brennzeit verschwenden. Ebenso wichtig ist die Verwendung des richtigen Dateiformats. WebP ist normalerweise die beste Wahl, da es die Größe klein hält, aber nicht alle Browser unterstütze es.

Schmerz weg automatisieren

Das Erstellen aller Permutationen jedes Bildes, das Sie auf Ihrer Website benötigen, kann zeitaufwändig und irritierend sein. Bei Request Metrics haben wir eine Photoshop-Vorlage, die einen Teil der Arbeit beim Export für uns erledigt, aber es ist immer noch ein manueller Schritt.

Was wäre, wenn wir stattdessen NGINX die schwere Arbeit für uns erledigen lassen und die erforderlichen Bilder im Handumdrehen erstellen könnten?

Haftungsausschluss: Was folgt, ist eher ein Proof of Concept als eine Best Practice. Wie sie in Jurassic Park sagen:

Ihre Wissenschaftler waren so damit beschäftigt, ob sie es könnten oder nicht, dass sie nicht darüber nachdachten, ob sie es sollten.

Ein Bild, um sie alle zu beherrschen

Hier ist ein großes Heldenbild von unserem Leitfaden zur Webleistung:

Ein großes altes Header-Bild für Retina-Bildschirme

Dieses Bild ist 2000 Pixel breit und für Bildschirme mit hohen DPI-Werten gedacht, wie sie auf Apples Retina-Displays zu finden sind. Wir arbeiten oft im PNG-Format, da es von Kreativtools und Browsern weitgehend unterstützt wird.

Das Problem ist, dass dieses Bild riesig ist – sowohl in Bezug auf die Auflösung als auch auf die Dateigröße – und wiegt 350 KB.

Wir möchten die nutzen können srcset -Eigenschaft, um 1x- und 2x-WebP-Bilder anzugeben, wenn der Browser dies unterstützt, oder Fallback auf PNG, wenn nicht:



<img srcset="/images/header.1x.webp 1x,
             /images/header.webp 2x"
        src="/images/header.webp" />


<img srcset="/images/header.1x.png 1x,
             /images/header.png 2x"
        src="/images/header.png" />

Im Wesentlichen brauchen wir vier Versionen des gleichen Bildes: Hochauflösende und niedrigauflösende Versionen des Bildes in beiden PNG und Webp Format. Aber wir haben nur ein einziges hochauflösendes PNG und es ist sehr ärgerlich, die anderen drei Versionen für jedes Bild zu erstellen, das wir auf der Website haben möchten. Sind Computer nicht dafür da – um die langweilige Arbeit für uns zu erledigen?

NGINX-Bildmodul zur Rettung?

NGINX hat seit langem eine Bildmodul die verwendet werden können, um die Größe von Bildern im laufenden Betrieb zu ändern. Sie können sogar Endpunkte erstellen, die dauern benutzerdefinierte Breiten- und Höhenwerte.

Das ist cool, aber wir wollen nicht über Bildgrößen nachdenken. Wir wollen nur ein 1x das ist die halbe Auflösung des Originals, wie auch immer diese Größe aussehen mag. Das NGINX-Bildmodul auch nicht unterstützt das Ändern von Dateiformaten. Es bringt uns also unserem nicht näher WebP Formatziel auch nicht!

ImageMagick kann alles

Was wir wirklich brauchen, ist so etwas wie ImageMagick. Es ist eine Bibliothek, die Bildattribute wie Breite und Höhe lesen, die Größe ändern und sogar das Format ändern kann. Ein wahres Schweizer Taschenmesser für Bilder. Aber wie können wir von NGINX aus darauf zugreifen? Nun, mit ein bisschen Trickserei.

Lua-Bindungen für ImageMagick

Es gibt eine große Anzahl von Bindungsbibliotheken, die ImageMagick von fast jeder Programmiersprache aus unterstützen. Zum Glück ist jemand da der einen für Lua gebaut hat. Ähm, warum kümmern wir uns um Lua und wie bringt uns das unserem Ziel näher?

Das NGINX Lua-Modul

NGINX unterstützt seit einiger Zeit Lua als Skriptsprache. Es ist normalerweise nicht standardmäßig aktiviert, aber es ist einfach genug, es mit einer einfachen Modulinstallation hinzuzufügen. Und sobald wir Lua-Unterstützung haben, können wir diese schicke Lua-to-ImageMagick-Bindung verwenden, die wir gefunden haben! Oder zumindest ist das die Theorie.

Verwenden Sie im Ubuntu-Land Folgendes, um die Lua-Unterstützung für NGINX zu aktivieren:

 
apt-get install libnginx-mod-http-lua

Im Gespräch mit ImageMagick von NGINX

Wir haben die Lua-Unterstützung in NGINX aktiviert, aber wir sind noch nicht ganz fertig. Betrachten wir die Installationsanleitung für die ImageMagick Lua-Bindung brauchen wir noch ein paar Abhängigkeiten.

apt-get install luajit 
apt-get install libmagickwand-dev 
apt-get install luarocks 

luarocks install magick 

Jetzt, da wir alle Abhängigkeiten haben, sollten wir in der Lage sein, direkt von NGINX aus mit ImageMagick zu sprechen. Mal sehen, auf was für schlechte Ideen wir kommen können!

Automatisches Konvertieren von PNG in WebP

Für alle unsere Bilder möchten wir die Möglichkeit haben, sie automatisch von PNG in WebP zu konvertieren. Wir können einen speziellen Ort in NGINX schaffen, um genau das für uns zu tun!

Der Hauptstoß ist, dass wir einen speziellen Standort in NGINX einrichten, der alle Anfragen für bearbeitet .webp Bilder. Wenn das Image auf der Festplatte vorhanden ist, großartig, geben wir es einfach mit dem ersten Argument von zurück try_files. Wenn dies nicht der Fall ist, suchen wir nach einem PNG mit demselben Namen und versuchen, es im Handumdrehen in WebP zu konvertieren!

# Handle any requests for .webp images.
# We first look for the file on disk, and if it's not present,
# call our internal location that does the conversion for us.
location ~* /images/(?<filename>.+).webp {
    try_files $uri @webp;
}

# Internal-only location that will convert an existing PNG to WebP
# And save that WebP image to disk for future requests
location @webp {
    content_by_lua_block {
        local magick = require("magick")
        local rootDir="/var/www/images/"
        local srcImgPath = rootDir .. ngx.var.filename .. '.png'
        local img = magick.load_image(srcImgPath)
        if not img then
            ngx.status = 404
            ngx.exit(0)
        end

        img:set_format('webp')
        img:set_quality(100)
        local destFileName = ngx.var.filename .. '.webp'
        local destImgPath = rootDir .. destFileName
        img:write(destImgPath)

        ngx.exec("/images/" .. destFileName)
    }
}

Hier gibt es ein bisschen auszupacken. Beginnen wir mit dem Standort auf oberster Ebene.

location ~* /images/(?<filename>.+).webp {
    try_files $uri @webp;
}

Dieser Standort verarbeitet alle Anfragen an .webp Bilder. Es sucht zuerst nach der Datei auf der Festplatte, und wenn es sie nicht findet, delegiert es an einen internen Fallback-Speicherort namens @webp.

location @webp {
    content_by_lua_block {
    ...

Das merkt man dann evtl content_by_lua_block Richtlinie. Dadurch können wir beliebigen Lua-Code direkt in der NGINX-Konfiguration ausführen. Es gibt einige dieser Richtlinien an verschiedenen Stellen im NGINX-Request/Response-Lebenszyklus, und es gibt Versionen, die anstelle von Inline-Code eine Datei auf der Festplatte verwenden.

    local magick = require("magick")
    local rootDir = '/var/www/images/'
    local srcImgPath = rootDir .. ngx.var.filename .. '.png'
    local img = magick.load_image(srcImgPath)
    if not img then
        ngx.status = 404
        ngx.exit(0)
    end

Als nächstes laden wir die von uns installierte ImageMagick-Bindung und verwenden sie, um nach einer vorhandenen PNG-Datei auf der Festplatte zu suchen. Wenn wir keine vorhandene PNG-Datei zum Konvertieren finden, verwenden wir die intrinsische ngx Objekt, um eine 404-Antwort zu erstellen.

    img:set_format('webp')
    img:set_quality(100)
    local destFileName = ngx.var.filename .. '.webp'
    local destImgPath = rootDir .. destFileName
    img:write(destImgPath)

Wenn wir ein Bild haben, stellen wir das Format auf ein webp und Qualität zu 100 und speichern Sie es erneut mit der .webp Dateierweiterung. Dadurch wird das PNG effektiv mit verlustfreier Komprimierung in WebP konvertiert.

    ...
    ngx.exec("/images/" .. destFileName)

Schließlich verwenden wir ngx.exec() um intern auf unsere neu gespeicherte WebP-Datei umzuleiten! Nachfolgende Anfragen überspringen diesen gesamten Lua-Code und erhalten nur die Datei von der Festplatte.

Es weiter bringen

Wir können jetzt PNG im laufenden Betrieb und mit guter Leistung in WebP konvertieren! Aber wir wollen auch Versionen mit niedrigerer Auflösung bei Bedarf automatisch erstellen. Wir können dies mit ein paar weiteren Codezeilen in einem neuen Lua-Block erreichen.

Erstellen von 1x-Bildern

Wenn wir sagen 1x oder 2x Bilder, über die wir wirklich sprechen devicePixelRatio. Das heißt, das Verhältnis von eins CSS Pixel zu eins körperlich Pixel. Nehmen wir also an, unsere Website ist gemäß CSS-Definition 1000 Pixel breit. Auf einem Retina-Gerät mit einem 2-fachen Pixelverhältnis benötigen wir ein 2000 Pixel breites Bild, um diesen Platz auszufüllen, ohne dass der Browser es hochskaliert.

Wenn der Benutzer jedoch kein hochauflösendes Display verwendet, wäre es besser, ihm die „1x“-Version des Bildes zu senden – eine, die 1000 Pixel breit ist. Dies spart Bandbreite und Zeit auf der Leitung. Da wir nur mit einem einzigen Bild arbeiten möchten, erstellen wir nur eine Version mit hoher Auflösung und lassen NGINX die Größe automatisch auf die niedrigere Auflösung ändern.

Innerhalb unserer Site-Konfiguration können wir so etwas tun:

# Look for any images with a {filename}.1x.{extension}
# We'll support PNG *or* WebP for fun
location ~* /images/(?<filename>.+).1x.(?<extension>png|webp) {
    try_files $uri @1x;
}

location @1x {
    content_by_lua_block {
        #... same directory setup and 404 returning code as before

        if ngx.var.extension == 'webp' then
            img:set_format('webp')
            img:set_quality(100)
        end

        local imgWidth = img:get_width()
        local imgHeight = img:get_height()
        local destFileName = ngx.var.filename .. '.1x.' .. ngx.var.extension
        local destImgPath = rootDir .. destFileName
        img:resize(imgWidth / 2, imgHeight / 2)
        img:write(destImgPath)

        ngx.exec("/images/" .. destFileName)
    }
}

Der Hauptunterschied zum vorherigen Code besteht darin, dass wir das Bild nach seiner Breite und Höhe abfragen und diese Werte dann durch zwei teilen, um a zu erhalten 1x Bild.

    local imgWidth = img:get_width()
    local imgHeight = img:get_height()
    img:resize(imgWidth / 2, imgHeight / 2)

Die Ergebnisse

Unser Original-PNG war 350 KB. Das Ergebnis 1x PNG ist viel kleiner 204 KB.

Die WebP-Bilder sind noch kompakter! Das 2x Bild ist nur 110 KB und die 1x Bild ist 108 KB (nicht annähernd so große Einsparungen aufgrund der Art der Komprimierung von WebP).

Es ist nicht viel Arbeit, Ihren statischen NGINX-Inhaltsserver so zu konfigurieren, dass er Bildkonvertierungen für Sie durchführt. Das Lua-Skript wird nur einmal pro Bild ausgeführt, sodass der Server selbst nicht übermäßig belastet werden sollte. Und 2-3x kleinere Nutzlasten „kostenlos“ zu bekommen, ist etwas, worüber sich jeder freuen kann!

Macht es einen Unterschied?

Die einzige Möglichkeit, dies herauszufinden, besteht darin, ein Produkt wie z Metriken anfordern um die tatsächlichen Leistungszahlen für Ihre Website zu messen. Finden Sie heraus, was die tatsächlichen Benutzer erleben und ob Ihre Änderungen einen Unterschied machen!

Similar Posts

Leave a Reply

Your email address will not be published.