Ein Favicon, das dem Dunkelmodus folgt, aus einem PNG-Logo

Christian Drexler

Das Logo dieser Seite ist ein einzelner schwarzer Buchstabe auf transparentem Grund. Als Favicon in einem hellen Browser sah das richtig aus. Dann habe ich die Seite auf einem Rechner im Dunkelmodus geöffnet, und der Tab hatte gar kein Symbol. Es war da. Es war schwarz auf fast schwarz.

Die üblichen Auswege sind ein weißer Kasten hinter dem Logo, der wie ein Aufkleber aussieht, oder ein Grau, das auf beiden Hintergründen mittelmäßig ist. Es gibt einen besseren. Eine SVG-Datei kann CSS enthalten, CSS kann nach dem Farbschema fragen, und Browser wenden das auf Favicons an.

Vom PNG zum sauberen Pfad

Ich hatte das Logo nur als PNG. Für ein Symbol, das bei 16 Pixeln scharf bleiben und die Farbe wechseln soll, muss es ein Vektor mit einem einzigen Pfad sein. potrace erledigt diese Umwandlung für einfarbige Formen gut. Als Eingabe will es eine Bitmap, also kommt zuerst ImageMagick.

magick logo.png -background white -alpha remove -threshold 50% logo.pbm
potrace logo.pbm --svg --flat -o logo.svg

Die erste Zeile legt die Transparenz auf Weiß und reduziert das Bild auf reines Schwarz und Weiß. Die zweite zeichnet die schwarzen Flächen nach. --flat legt alles in einen Pfad, und das macht den nächsten Schritt einfach.

Öffne das Ergebnis einmal und sieh es dir vergrößert an. Aus einem kleinen PNG entstehen beim Nachzeichnen klumpige Kurven. Fang mit der größten Fassung des Logos an, die du finden kannst.

Das Stylesheet im Symbol

Die nachgezeichnete Datei bekommt zwei kleine Änderungen: eine quadratische viewBox mit etwas Rand um den Buchstaben und einen <style>-Block.

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
  <style>
    path { fill: #111; }
    @media (prefers-color-scheme: dark) {
      path { fill: #fff; }
    }
  </style>
  <path d="M96 64h160c106 0 192 86 192 192s-86 192-192 192H96z ..." />
</svg>

Der Pfad hat kein eigenes fill-Attribut. Die Farbe kommt nur aus dem Stylesheet. Im hellen Schema ist der Buchstabe fast schwarz, im dunklen weiß.

Eines sollte klar sein: Die Media Query folgt dem Farbschema des Betriebssystems oder der Browser-Einstellung, nicht der tatsächlichen Farbe der Tab-Leiste. Wer ein helles System und ein dunkles Browser-Design hat, bekommt weiterhin den dunklen Buchstaben. Das deckt die meisten ab und verfehlt ein paar, und einen Browser nach der Farbe seiner Tabs zu fragen, geht nicht.

Die fünf Dateien

In einem Next.js-Projekt mit App Router sind Symbole Dateien mit festen Namen im Verzeichnis app. Next erzeugt daraus die Link-Tags. Das ist der Satz, bei dem ich gelandet bin.

app/icon.svg ist das anpassungsfähige Symbol von oben. Chrome, Edge und Firefox benutzen es.

app/favicon.ico ist der Rückfall für alles, was kein SVG nimmt, mit drei Größen in einer Datei.

magick logo.png -background none -define icon:auto-resize=48,32,16 favicon.ico

Safari ist hier der Hauptabnehmer. Zum Zeitpunkt des Schreibens benutzt es für Tabs kein SVG-Symbol, Safari-Nutzer sehen also die .ico. Die kann sich nicht anpassen, sie sollte deshalb auf beiden Hintergründen so gut funktionieren, wie ein einzelnes Bild es kann. Für ein schwarzes Logo heißt das, zwischen einer hellen Kachel dahinter und schwachem Kontrast im Dunkelmodus zu wählen.

app/apple-icon.png, 180 mal 180 Pixel, für den Home-Bildschirm des iPhones. iOS ersetzt Transparenz durch Schwarz. Aus einem schwarzen Buchstaben auf transparentem Grund wird ein schwarzes Quadrat. Diese Datei braucht einen deckenden Hintergrund.

magick -size 180x180 xc:white \( logo.png -resize 120x120 \) -gravity center -composite apple-icon.png

public/icon-192.png und public/icon-512.png für Android und für den Installationsdialog, auf die das Manifest verweist.

app/manifest.webmanifest, kurz:

{
  "name": "Example",
  "icons": [
    { "src": "/icon-192.png", "sizes": "192x192", "type": "image/png" },
    { "src": "/icon-512.png", "sizes": "512x512", "type": "image/png" }
  ],
  "theme_color": "#ffffff",
  "background_color": "#ffffff",
  "display": "browser"
}

Nach einem Build stehen im Seitenkopf die Links für die .ico, das SVG mit type="image/svg+xml", das Apple-Symbol und das Manifest. Von Hand muss nichts geschrieben werden, und es darf auch nichts: Ein handgeschriebenes <link rel="icon"> im Layout neben der Dateikonvention erzeugt zwei konkurrierende Einträge.

Testen

Favicons werden härter zwischengespeichert als alles andere im Browser, und das macht das Testen lästig. Was bei mir funktioniert hat:

  • Die SVG-Datei selbst in einem Tab öffnen, unter ihrer eigenen Adresse, und das System zwischen hell und dunkel umschalten. Der Buchstabe sollte dabei umspringen. Tut er es nicht, stimmt das Stylesheet nicht, und es hat noch keinen Sinn, auf das Favicon zu schauen.
  • Für das Tab-Symbol ein privates Fenster nehmen. Es startet mit leerem Symbol-Cache.
  • In den Entwicklerwerkzeugen von Chrome hat das Rendering-Panel einen Schalter „Emulate CSS media feature prefers-color-scheme“. Er schaltet Seite und Favicon um, ohne die Systemeinstellungen anzufassen.

Um zu sehen, was der Server wirklich ausliefert:

curl -s https://example.com/ | grep -o '<link rel="[^"]*icon[^>]*>'
curl -sI https://example.com/icon.svg | grep -i content-type

In der zweiten Zeile muss image/svg+xml stehen. Ein SVG, das als reiner Text ausgeliefert wird, wird als Symbol ignoriert.

Lohnt sich

Es ist eine Kleinigkeit. Aber ein Favicon ist das eine Stück einer Seite, das sichtbar bleibt, während der Leser in einem anderen Tab ist, und ein fehlendes sieht aus wie eine Seite, die jemand vergessen hat fertigzustellen. Viele Leute surfen im Dunkelmodus, Entwickler mehr als die meisten. Für ein Logo, das aus einer einzigen dunklen Form besteht, ist das anpassungsfähige SVG die einzige Fassung, die in beiden gewollt aussieht.

Hat dein Logo einen eigenen Hintergrund oder mehrere Farben, brauchst du nichts davon. Ist es ein dunkler Buchstabe oder ein dunkles Zeichen auf Transparenz, öffne deine Seite einmal in einem dunklen Browser. Vielleicht siehst du auch einen leeren Tab.