Sådan opretter du adaptive favicons til lys og mørk tilstand: Komplet udviklerguide
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
- Oprethold brandgenkendelse - Hold dine kernedesignelementer konsistente
- Test ved flere størrelser - 16x16, 32x32 og 180x180 pixels
- Brug simple former - Komplekse detaljer forsvinder ved små størrelser
- 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:
-
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)"> -
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.
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.