Skip to content

Instantly share code, notes, and snippets.

@kslstn
Last active July 2, 2023 17:04
Show Gist options
  • Star 41 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save kslstn/20f654fd27eb29619040c74fa6526919 to your computer and use it in GitHub Desktop.
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
// 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