Last active
July 2, 2023 17:04
-
-
Save kslstn/20f654fd27eb29619040c74fa6526919 to your computer and use it in GitHub Desktop.
My script to add theming support and switch between light mode and dark mode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment