Sådan opretter du adaptive favicons til lys og mørk tilstand: Komplet udviklerguide

Favicon.im

Moderne hjemmesider skal tilpasse sig brugerpræferencer, og favicon-tematisering er en ofte overset detalje, der kan forbedre brugeroplevelsen markant. Når brugere skifter mellem lys og mørk tilstand, bør dit favicon tilpasse sig tilsvarende for at opretholde visuel konsistens.

Denne omfattende guide dækker alt fra simple HTML-only løsninger til avancerede JavaScript-implementeringer på tværs af populære frameworks. Uanset om du bygger en statisk side eller en kompleks webapplikation, finder du den rette tilgang til dit projekt.

Metode 1: HTML-only løsning (anbefalet til de fleste sider)

HTML-only metoden er den mest pålidelige metode og kræver ingen JavaScript. Den bruger CSS media queries inden for media-attributten af favicon link-tags til automatisk at skifte favicons baseret på brugerens systempræference.

Hvorfor denne metode fungerer bedst:

  • Intet JavaScript påkrævet
  • Fungerer øjeblikkeligt ved sideindlæsning
  • Understøttet af alle moderne browsere
  • Ingen ydelses-overhead

Grundlæggende implementering

<head>
  <!-- Default favicon (fallback for unsupported browsers) -->
  <link rel="icon" href="/favicon-light.ico" type="image/x-icon">

  <!-- Light mode favicon -->
  <link rel="icon" href="/favicon-light.ico" type="image/x-icon" media="(prefers-color-scheme: light)">

  <!-- Dark mode favicon -->
  <link rel="icon" href="/favicon-dark.ico" type="image/x-icon" media="(prefers-color-scheme: dark)">
</head>

Komplet multi-størrelse implementering

For omfattende enhedsunderstøttelse, implementér flere størrelser med temavarianter:

<head>
  <!-- Default favicons (fallback) -->
  <link rel="icon" type="image/x-icon" href="/favicon-light.ico">
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-light-32x32.png">

  <!-- Light mode favicons -->
  <link rel="icon" type="image/x-icon" href="/favicon-light.ico" media="(prefers-color-scheme: light)">
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-light-16x16.png" media="(prefers-color-scheme: light)">
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-light-32x32.png" media="(prefers-color-scheme: light)">
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-light.png" media="(prefers-color-scheme: light)">

  <!-- Dark mode favicons -->
  <link rel="icon" type="image/x-icon" href="/favicon-dark.ico" media="(prefers-color-scheme: dark)">
  <link rel="icon" type="image/png" sizes="16x16" href="/favicon-dark-16x16.png" media="(prefers-color-scheme: dark)">
  <link rel="icon" type="image/png" sizes="32x32" href="/favicon-dark-32x32.png" media="(prefers-color-scheme: dark)">
  <link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-dark.png" media="(prefers-color-scheme: dark)">

  <!-- SVG favicons with embedded CSS -->
  <link rel="icon" type="image/svg+xml" href="/favicon-adaptive.svg">
</head>

Adaptivt SVG-favicon

Opret et enkelt SVG-favicon, der automatisk tilpasser sig farveskemaet:

<!-- favicon-adaptive.svg -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
  <style>
    .light-mode { fill: #000000; }
    .dark-mode { fill: #ffffff; }

    @media (prefers-color-scheme: dark) {
      .light-mode { display: none; }
    }

    @media (prefers-color-scheme: light) {
      .dark-mode { display: none; }
    }
  </style>

  <!-- Light mode design -->
  <circle class="light-mode" cx="16" cy="16" r="12"/>
  <text class="light-mode" x="16" y="20" text-anchor="middle" fill="#fff" font-size="14">L</text>

  <!-- Dark mode design -->
  <circle class="dark-mode" cx="16" cy="16" r="12"/>
  <text class="dark-mode" x="16" y="20" text-anchor="middle" fill="#000" font-size="14">D</text>
</svg>

Metode 2: JavaScript-implementering

Når du har brug for dynamisk favicon-skift ud over systempræferencer — som brugerdefinerede temakontroller eller realtidsopdateringer — giver JavaScript den fleksibilitet, du har brug for.

Brug JavaScript når:

  • Du har brugerdefinerede temakontroller
  • Du skal synkronisere med din apps tematilstand
  • Du vil opdatere favicons uden sidegenindlæsning
  • Du bygger en single-page application

Grundlæggende JavaScript-tilgang

// Function to update favicon based on theme
function updateFavicon(theme) {
  const favicon = document.querySelector('link[rel="icon"]') ||
                 document.createElement('link');

  favicon.rel = 'icon';
  favicon.type = 'image/png';
  favicon.href = theme === 'dark' ? '/favicon-dark.png' : '/favicon-light.png';

  if (!document.querySelector('link[rel="icon"]')) {
    document.head.appendChild(favicon);
  }
}

// Listen for system theme changes
if (window.matchMedia) {
  const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

  // Set initial favicon
  updateFavicon(mediaQuery.matches ? 'dark' : 'light');

  // Listen for changes
  mediaQuery.addEventListener('change', (e) => {
    updateFavicon(e.matches ? 'dark' : 'light');
  });
}

Avanceret JavaScript med flere størrelser

class FaviconManager {
  constructor() {
    this.sizes = [
      { size: '16x16', selector: 'link[rel="icon"][sizes="16x16"]' },
      { size: '32x32', selector: 'link[rel="icon"][sizes="32x32"]' },
      { size: '180x180', selector: 'link[rel="apple-touch-icon"]' }
    ];

    this.init();
  }

  init() {
    // Set initial theme
    this.updateTheme(this.getSystemTheme());

    // Listen for system changes
    if (window.matchMedia) {
      const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
      mediaQuery.addEventListener('change', (e) => {
        this.updateTheme(e.matches ? 'dark' : 'light');
      });
    }
  }

  getSystemTheme() {
    return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
      ? 'dark' : 'light';
  }

  updateTheme(theme) {
    this.sizes.forEach(({ size, selector }) => {
      let link = document.querySelector(selector);

      if (!link) {
        link = document.createElement('link');
        link.rel = size === '180x180' ? 'apple-touch-icon' : 'icon';
        link.type = 'image/png';
        if (size !== '180x180') link.sizes = size;
        document.head.appendChild(link);
      }

      link.href = `/favicon-${theme}-${size}.png`;
    });

    // Update default ico file
    let icoLink = document.querySelector('link[rel="icon"][type="image/x-icon"]');
    if (!icoLink) {
      icoLink = document.createElement('link');
      icoLink.rel = 'icon';
      icoLink.type = 'image/x-icon';
      document.head.appendChild(icoLink);
    }
    icoLink.href = `/favicon-${theme}.ico`;
  }

  // Method to manually set theme (for custom theme toggles)
  setTheme(theme) {
    this.updateTheme(theme);
  }
}

// Initialize
const faviconManager = new FaviconManager();

// Export for manual theme switching
window.faviconManager = faviconManager;

Metode 3: Framework-integration

Moderne frameworks tilbyder elegante måder at håndtere favicon-tematisering på. Her er hvordan du implementerer adaptive favicons i de mest populære JavaScript-frameworks.

React-implementering

import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';

function AdaptiveFavicon() {
  const [theme, setTheme] = useState('light');

  useEffect(() => {
    // Check system preference
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
    setTheme(mediaQuery.matches ? 'dark' : 'light');

    // Listen for changes
    const handleChange = (e) => {
      setTheme(e.matches ? 'dark' : 'light');
    };

    mediaQuery.addEventListener('change', handleChange);
    return () => mediaQuery.removeEventListener('change', handleChange);
  }, []);

  return (
    <Helmet>
      <link rel="icon" type="image/x-icon" href={`/favicon-${theme}.ico`} />
      <link rel="icon" type="image/png" sizes="32x32" href={`/favicon-${theme}-32x32.png`} />
      <link rel="apple-touch-icon" sizes="180x180" href={`/apple-touch-icon-${theme}.png`} />
    </Helmet>
  );
}

Vue 3-implementering

<template>
  <div>
    <!-- Your app content -->
  </div>
</template>

<script setup>
import { ref, onMounted, watch } from 'vue'
import { useHead } from '@unhead/vue'

const isDark = ref(false)

const updateFavicon = () => {
  const theme = isDark.value ? 'dark' : 'light'

  useHead({
    link: [
      { rel: 'icon', type: 'image/x-icon', href: `/favicon-${theme}.ico` },
      { rel: 'icon', type: 'image/png', sizes: '32x32', href: `/favicon-${theme}-32x32.png` },
      { rel: 'apple-touch-icon', sizes: '180x180', href: `/apple-touch-icon-${theme}.png` }
    ]
  })
}

onMounted(() => {
  // Check system preference
  if (window.matchMedia) {
    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
    isDark.value = mediaQuery.matches

    // Listen for changes
    mediaQuery.addEventListener('change', (e) => {
      isDark.value = e.matches
    })
  }

  updateFavicon()
})

watch(isDark, updateFavicon)
</script>

Nuxt 3-implementering

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      script: [
        {
          innerHTML: `
            (function() {
              const updateFavicon = (isDark) => {
                const theme = isDark ? 'dark' : 'light';
                const links = [
                  { rel: 'icon', type: 'image/x-icon', href: \`/favicon-\${theme}.ico\` },
                  { rel: 'icon', type: 'image/png', sizes: '32x32', href: \`/favicon-\${theme}-32x32.png\` }
                ];

                links.forEach(linkData => {
                  let link = document.querySelector(\`link[rel="\${linkData.rel}"][sizes="\${linkData.sizes || 'any'}"]\`);
                  if (!link) {
                    link = document.createElement('link');
                    Object.assign(link, linkData);
                    document.head.appendChild(link);
                  } else {
                    link.href = linkData.href;
                  }
                });
              };

              if (window.matchMedia) {
                const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
                updateFavicon(mediaQuery.matches);
                mediaQuery.addEventListener('change', e => updateFavicon(e.matches));
              }
            })();
          `
        }
      ]
    }
  }
})

Metode 4: CSS-in-JS favicon (avanceret)

Generér favicons dynamisk med Canvas og CSS-farver:

class DynamicFaviconGenerator {
  constructor() {
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d');
    this.canvas.width = 32;
    this.canvas.height = 32;
  }

  generateFavicon(theme) {
    const colors = {
      light: { bg: '#ffffff', text: '#000000' },
      dark: { bg: '#000000', text: '#ffffff' }
    };

    const { bg, text } = colors[theme];

    // Clear canvas
    this.ctx.clearRect(0, 0, 32, 32);

    // Draw background
    this.ctx.fillStyle = bg;
    this.ctx.fillRect(0, 0, 32, 32);

    // Draw border
    this.ctx.strokeStyle = text;
    this.ctx.lineWidth = 2;
    this.ctx.strokeRect(2, 2, 28, 28);

    // Draw icon (example: letter or symbol)
    this.ctx.fillStyle = text;
    this.ctx.font = 'bold 20px Arial';
    this.ctx.textAlign = 'center';
    this.ctx.textBaseline = 'middle';
    this.ctx.fillText('🌙', 16, 16);

    return this.canvas.toDataURL('image/png');
  }

  updateFavicon(theme) {
    const dataUrl = this.generateFavicon(theme);

    let link = document.querySelector('link[rel="icon"]');
    if (!link) {
      link = document.createElement('link');
      link.rel = 'icon';
      link.type = 'image/png';
      document.head.appendChild(link);
    }

    link.href = dataUrl;
  }
}

// Usage
const generator = new DynamicFaviconGenerator();
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');

generator.updateFavicon(mediaQuery.matches ? 'dark' : 'light');
mediaQuery.addEventListener('change', e => {
  generator.updateFavicon(e.matches ? 'dark' : 'light');
});

Bedste designpraksis

At skabe effektive adaptive favicons kræver omhyggelig opmærksomhed på designprincipper og brugeroplevelse.

Farvekontrast og synlighed

Design af lys tilstand-favicon:

  • Brug mørke elementer (tekst, ikoner) på gennemsigtige eller lyse baggrunde
  • Sigt efter WCAG AA-kontrastforhold (minimum 4,5:1)
  • Test udseende på hvide browserfaner og bogmærkelinjer
  • Sørg for tydelighed ved 16x16 pixels (mindste almindelige størrelse)

Design af mørk tilstand-favicon:

  • Brug lyse elementer på gennemsigtige eller mørke baggrunde
  • Test synlighed mod mørke browsertemaer
  • Undgå rent hvid (#ffffff) - brug off-white (#f0f0f0) for bedre balance
  • Overvej subtile skygger eller konturer for definition

Tips til designkonsistens

  1. Oprethold brandgenkendelse - Hold dine kernedesignelementer konsistente
  2. Test ved flere størrelser - 16x16, 32x32 og 180x180 pixels
  3. Brug simple former - Komplekse detaljer forsvinder ved små størrelser
  4. Tænk på farveblinde brugere - Stol ikke udelukkende på farve til differentiering

Filnavngivningskonvention

Organisér dine favicon-filer med tydelig navngivning:

/public/
├── favicon-light.ico
├── favicon-dark.ico
├── favicon-light-16x16.png
├── favicon-dark-16x16.png
├── favicon-light-32x32.png
├── favicon-dark-32x32.png
├── apple-touch-icon-light.png
├── apple-touch-icon-dark.png
└── favicon-adaptive.svg

Browserkompatibilitet

Moderne browserunderstøttelse for adaptive favicons

Browser Media Query-understøttelse Noter
Chrome 76+ Fuld understøttelse Fungerer perfekt
Firefox 67+ Fuld understøttelse Fremragende implementering
Safari 12.1+ Fuld understøttelse iOS Safari inkluderet
Edge 79+ Fuld understøttelse Chromium-baseret Edge
Internet Explorer Ingen understøttelse Brug JavaScript-fallback

Markedsdækning: Disse versioner dækker ca. 95% af det globale browserforbrug pr. 2025.

Fallback-strategi

<!-- Always provide fallbacks -->
<link rel="icon" href="/favicon-light.ico" type="image/x-icon">

<!-- Enhanced support for modern browsers -->
<link rel="icon" href="/favicon-light.ico" type="image/x-icon" media="(prefers-color-scheme: light)">
<link rel="icon" href="/favicon-dark.ico" type="image/x-icon" media="(prefers-color-scheme: dark)">

<!-- JavaScript fallback for older browsers -->
<script>
  if (!window.matchMedia || !CSS.supports('(prefers-color-scheme: dark)')) {
    // Load favicon based on time of day or other heuristics
    const hour = new Date().getHours();
    const isDark = hour < 6 || hour > 18;
    document.querySelector('link[rel="icon"]').href =
      isDark ? '/favicon-dark.ico' : '/favicon-light.ico';
  }
</script>

Test og validering

Manuel testtjekliste

  • [ ] Test i lys tilstand (systempræference)
  • [ ] Test i mørk tilstand (systempræference)
  • [ ] Bekræft at favicon ændres øjeblikkeligt ved systemtemaskift
  • [ ] Tjek forskellige browsere (Chrome, Firefox, Safari, Edge)
  • [ ] Test på mobilenheder
  • [ ] Validér fallback-adfærd i ældre browsere

Automatiseret test

// Test script for favicon theme switching
function testFaviconThemes() {
  const tests = [
    { theme: 'light', expected: '/favicon-light.ico' },
    { theme: 'dark', expected: '/favicon-dark.ico' }
  ];

  tests.forEach(({ theme, expected }) => {
    // Mock media query
    Object.defineProperty(window, 'matchMedia', {
      writable: true,
      value: jest.fn().mockImplementation(query => ({
        matches: query.includes('dark') ? theme === 'dark' : theme === 'light',
        addEventListener: jest.fn(),
        removeEventListener: jest.fn(),
      })),
    });

    // Trigger update
    updateFavicon(theme);

    // Assert
    const favicon = document.querySelector('link[rel="icon"]');
    expect(favicon.href).toContain(expected);
  });
}

Ydelsesoptimering

Forudindlæs tema-favicons

<!-- Preload both theme favicons for instant switching -->
<link rel="preload" as="image" href="/favicon-light.ico">
<link rel="preload" as="image" href="/favicon-dark.ico">

Minimér filstørrelser

  • Hold ICO-filer under 1KB
  • Optimér PNG-filer med værktøjer som TinyPNG
  • Brug SVG til simple geometriske designs
  • Overvej WebP-format til moderne browsere

Cachingstrategi

# Nginx configuration for favicon caching
location ~* \.(ico|png|svg)$ {
    expires 1y;
    add_header Cache-Control "public, immutable";
    add_header Vary "Accept-Encoding";
}

Fejlfinding af almindelige problemer

Favicon skifter ikke mellem temaer

Symptomer: Favicon forbliver det samme uanset systemtemaændringer

Almindelige årsager og løsninger:

  1. Browsercacheproblemer

    <!-- Add cache-busting parameters -->
    <link rel="icon" href="/favicon-light.ico?v=2025" media="(prefers-color-scheme: light)">
    <link rel="icon" href="/favicon-dark.ico?v=2025" media="(prefers-color-scheme: dark)">
    
  2. Forkert media query-syntaks

    <!-- ❌ Forkert -->
    <link rel="icon" href="/favicon-dark.ico" media="dark">
    
    <!-- ✅ Korrekt -->
    <link rel="icon" href="/favicon-dark.ico" media="(prefers-color-scheme: dark)">
    

Flere favicons indlæses samtidigt

Symptomer: Netværksfanen viser flere favicon-forespørgsler

Løsning: Brug JavaScript til at erstatte i stedet for at tilføje:

function replaceFavicon(href) {
  // Remove all existing favicon links
  document.querySelectorAll('link[rel*="icon"]').forEach(link => link.remove());

  // Add new favicon
  const link = document.createElement('link');
  link.rel = 'icon';
  link.type = 'image/x-icon';
  link.href = href;
  document.head.appendChild(link);
}

SVG-favicons vises ikke

Symptomer: SVG-favicon fungerer i nogle browsere, men ikke andre

Grundårsag: Begrænset SVG-favicon-understøttelse i ældre browsere

Løsning: Levér altid PNG-fallbacks:

<!-- Modern browsers: SVG with media queries -->
<link rel="icon" type="image/svg+xml" href="/favicon-adaptive.svg">

<!-- Fallback: PNG for older browsers -->
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-light-32x32.png" media="(prefers-color-scheme: light)">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-dark-32x32.png" media="(prefers-color-scheme: dark)">

Avancerede teknikker

Temabevidste notifikationsbadges

class NotificationFavicon {
  constructor() {
    this.canvas = document.createElement('canvas');
    this.ctx = this.canvas.getContext('2d');
    this.canvas.width = 32;
    this.canvas.height = 32;
    this.baseIcons = {
      light: '/favicon-light-32x32.png',
      dark: '/favicon-dark-32x32.png'
    };
  }

  async drawWithBadge(theme, count) {
    const baseIcon = new Image();
    baseIcon.src = this.baseIcons[theme];

    return new Promise(resolve => {
      baseIcon.onload = () => {
        this.ctx.clearRect(0, 0, 32, 32);
        this.ctx.drawImage(baseIcon, 0, 0, 32, 32);

        if (count > 0) {
          // Draw notification badge
          const badgeSize = 12;
          const x = 32 - badgeSize;
          const y = 0;

          // Badge background
          this.ctx.fillStyle = '#ff4444';
          this.ctx.beginPath();
          this.ctx.arc(x + badgeSize/2, y + badgeSize/2, badgeSize/2, 0, 2 * Math.PI);
          this.ctx.fill();

          // Badge text
          this.ctx.fillStyle = 'white';
          this.ctx.font = '8px Arial';
          this.ctx.textAlign = 'center';
          this.ctx.textBaseline = 'middle';
          this.ctx.fillText(
            count > 9 ? '9+' : count.toString(),
            x + badgeSize/2,
            y + badgeSize/2
          );
        }

        resolve(this.canvas.toDataURL());
      };
    });
  }

  async updateWithNotification(count = 0) {
    const theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
    const dataUrl = await this.drawWithBadge(theme, count);

    let link = document.querySelector('link[rel="icon"]');
    if (!link) {
      link = document.createElement('link');
      link.rel = 'icon';
      document.head.appendChild(link);
    }
    link.href = dataUrl;
  }
}

// Usage
const notificationFavicon = new NotificationFavicon();
notificationFavicon.updateWithNotification(3); // Show badge with count 3

Opsummering og næste skridt

Adaptive favicons repræsenterer en lille, men virkningsfuld måde at forbedre brugeroplevelsen. De viser opmærksomhed på detaljer og respekt for brugerpræferencer, hvilket bidrager til en mere poleret og professionel hjemmeside.

Vælg den rette metode til dit projekt

Metode Bedst til Kompleksitet Ydeevne
HTML-only Statiske sider, blogs, marketingsider Lav Fremragende
JavaScript SPA'er, brugerdefinerede temaer, dynamiske opdateringer Medium God
Framework-integration React/Vue/Nuxt-applikationer Medium God
Avancerede teknikker Notifikationssystemer, realtidsopdateringer Høj Variabel

Implementeringstjekliste

Før du deployer dit adaptive favicon-system:

  • [ ] Opret både lys og mørk favicon-version
  • [ ] Test i flere browsere (Chrome, Firefox, Safari, Edge)
  • [ ] Bekræft at skift fungerer med systemtemaændringer
  • [ ] Test på mobilenheder (iOS Safari, Android Chrome)
  • [ ] Optimér filstørrelser (hold under 1KB for ICO-filer)
  • [ ] Tilføj passende fallbacks til ældre browsere
  • [ ] Validér implementering med værktøjer som Favicon.im

Ydelsespåvirkning

Korrekt implementeret har adaptive favicons minimal ydelsespåvirkning:

  • HTML-only metode: Nul JavaScript-overhead
  • Filstørrelsespåvirkning: Ca. 2-4KB total (lys + mørk version)
  • Indlæsningstid: Ubetydelig ved korrekt caching

Gå videre

Overvej disse avancerede optimeringer:

  • Forudindlæs kritiske favicon-aktiver for øjeblikkelig skift
  • Brug WebP-format til moderne browsere (med PNG-fallbacks)
  • Implementér dynamiske favicon-badges til notifikationer
  • Tilføj favicon-animationer til specielle begivenheder eller statusser

Ved at implementere adaptive favicons gennemtænkt skaber du en mere sammenhængende og brugervenlig weboplevelse, der tilpasser sig moderne brugerpræferencer.

Check Your Favicon

Use favicon.im to quickly check if your favicon is configured correctly. Our free tool ensures your website's favicon displays properly across all browsers and devices.

Free Public Service

Favicon.im is a completely free public service trusted by developers worldwide.

15M+
Monthly Favicon Requests
100%
Free Forever