1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
// Copyright Koos Looijesteijn https://www.kooslooijesteijn.net/blog/add-dark-mode-to-website
// Find if user has set a preference and react to changes
(function initializeTheme(){
syncBetweenTabs()
listenToOSChanges()
enableTheme(
returnThemeBasedOnLocalStorage() ||
returnThemeBasedOnOS() ||
returnThemeBasedOnTime(),
false)
}())
// Listen to preference changes. The event only fires in inactive tabs, so theme changes aren't applied twice.
function syncBetweenTabs(){
window.addEventListener('storage', (e) => {
const root = document.documentElement
if (e.key === 'preference-theme'){
if (e.newValue === 'light') enableTheme('light', true, false)
else if (e.newValue === 'dark') enableTheme('dark', true, false) // The third argument makes sure the state isn't saved again.
}
})
}
// Add a listener in case OS-level preference changes.
function listenToOSChanges(){
let mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)')
mediaQueryList.addListener( (m)=> {
const root = document.documentElement
if (m.matches !== true){
if (!root.classList.contains('theme-light')){
enableTheme('light', true)
}
}
else{
if(!root.classList.contains('theme-dark')) enableTheme('dark', true)
}
})
}
// If no preference was set, check what the OS pref is.
function returnThemeBasedOnOS() {
let mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)')
if (mediaQueryList.matches) return 'dark'
else {
mediaQueryList = window.matchMedia('(prefers-color-scheme: light)')
if (mediaQueryList.matches) return 'light'
else return undefined
}
}
// For subsequent page loads
function returnThemeBasedOnLocalStorage() {
const pref = localStorage.getItem('preference-theme')
const lastChanged = localStorage.getItem('preference-theme-last-change')
let now = new Date()
now = now.getTime()
const minutesPassed = (now - lastChanged)/(1000*60)
if (
minutesPassed < 120 &&
pref === "light"
) return 'light'
else if (
minutesPassed < 120 &&
pref === "dark"
) return 'dark'
else return undefined
}
// Fallback for when OS preference isn't available
function returnThemeBasedOnTime(){
let date = new Date
const hour = date.getHours()
if (hour > 20 || hour < 5) return 'dark'
else return 'light'
}
// Switch to another theme
function enableTheme(newTheme = 'light', withTransition = false, save = true){
const root = document.documentElement
let otherTheme
newTheme === 'light' ? otherTheme = 'dark' : otherTheme = 'light'
let currentTheme
(root.classList.contains('theme-dark')) ? currentTheme = 'dark' : 'light'
if (withTransition === true && newTheme !== currentTheme) animateThemeTransition()
root.classList.add('theme-' + newTheme)
root.classList.remove('theme-' + otherTheme)
let button = document.getElementById('theme-' + otherTheme + '-button')
button.classList.add('enabled')
button.setAttribute('aria-pressed', false)
button = document.getElementById('theme-' + newTheme + '-button')
button.classList.remove('enabled')
button.setAttribute('aria-pressed', true)
if (save) saveToLocalStorage('preference-theme', newTheme)
}
// Save the state for subsequent page loads
function saveToLocalStorage(key, value){
let now = new Date()
now = now.getTime()
localStorage.setItem(key, value)
localStorage.setItem(key+"-last-change", now)
}
// Add class to smoothly transition between themes
function animateThemeTransition(){
const root = document.documentElement
root.classList.remove('theme-change-active')
void root.offsetWidth // Trigger reflow to cancel the animation
root.classList.add('theme-change-active')
}
(function removeAnimationClass(){
const root = document.documentElement
root.addEventListener(supportedAnimationEvent(), ()=>root.classList.remove('theme-change-active'), false)
}())
function supportedAnimationEvent(){
const el = document.createElement("f")
const animations = {
"animation" : "animationend",
"OAnimation" : "oAnimationEnd",
"MozAnimation" : "animationend",
"WebkitAnimation": "webkitAnimationEnd"
}
for (t in animations){
if (el.style[t] !== undefined) return animations[t] // Return the name of the event fired by the browser to indicate a CSS animation has ended
}
}
|