Skip to content

Instantly share code, notes, and snippets.

@adamstddrd
Created September 18, 2023 22:54
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
A theme picker custom element with a "random" option
/* ----------------------------------------------------------------------------
switch between color themes
---------------------------------------------------------------------------- */
export default class ThemePicker extends HTMLElement {
static randomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
static getLuminanace(values) {
const rgb = values.map((v) => {
const val = v / 255;
return val <= 0.03928 ? val / 12.92 : ((val + 0.055) / 1.055) ** 2.4;
});
return Number((0.2126 * rgb[0] + 0.7152 * rgb[1] + 0.0722 * rgb[2]).toFixed(3));
}
static RGBToHSL(r, g, b) {
const red = r / 255;
const green = g / 255;
const blue = b / 255;
const l = Math.max(red, green, blue);
const s = l - Math.min(red, green, blue);
const h = s
? l === red
? (green - blue) / s
: l === green
? 2 + (blue - red) / s
: 4 + (red - green) / s
: 0;
return [
Math.round(60 * h < 0 ? 60 * h + 360 : 60 * h),
Math.round(100 * (
s ? (l <= 0.5 ? s / (2 * l - s) : s / (2 - (2 * l - s))) : 0
)),
Math.round((100 * (2 * l - s)) / 2),
];
}
static generateRgb() {
const red = ThemePicker.randomInt(0, 255);
const green = ThemePicker.randomInt(0, 255);
const blue = ThemePicker.randomInt(0, 255);
return [red, green, blue];
}
static getContrastRatio(colorA, colorB) {
const lumA = ThemePicker.getLuminanace(colorA);
const lumB = ThemePicker.getLuminanace(colorB);
return (Math.max(lumA, lumB) + 0.05) / (Math.min(lumA, lumB) + 0.05);
}
generateTheme() {
const c1 = ThemePicker.generateRgb();
const c2 = ThemePicker.generateRgb();
const contrast = ThemePicker.getContrastRatio(c1, c2);
if (contrast < 4.5) {
this.generateTheme();
} else {
const c1HSL = ThemePicker.RGBToHSL(c1[0], c1[1], c1[2]);
const c2HSL = ThemePicker.RGBToHSL(c2[0], c2[1], c2[2]);
const c1HSLString = `${c1HSL[0]}deg, ${c1HSL[1]}%, ${c1HSL[2]}%`;
const c2HSLString = `${c2HSL[0]}deg, ${c2HSL[1]}%, ${c2HSL[2]}%`;
document.documentElement.style.setProperty('--color-random-text', c1HSLString);
document.documentElement.style.setProperty('--color-random-sheet', c2HSLString);
localStorage.setItem('color-text', c1HSLString);
localStorage.setItem('color-sheet', c2HSLString);
}
}
save(theme) {
document.documentElement.setAttribute('theme', theme);
localStorage.setItem('theme', theme);
this.update(theme);
}
update(theme) {
if (this.querySelector('.--active')) {
this.querySelector('.--active').classList.remove('--active');
}
this.querySelector(`[data-theme="${theme}"]`).classList.add('--active');
}
selectable() {
const themeButtons = this.querySelectorAll('[data-theme]');
themeButtons.forEach((element) => {
element.addEventListener('click', (e) => {
const newTheme = element.getAttribute('data-theme');
this.save(newTheme);
if (newTheme === 'random') {
this.generateTheme();
}
});
});
}
closeable() {
document.addEventListener('click', (e) => {
const isClickInside = this.contains(e.target);
const isOpen = this.querySelector('details[open]');
if (!isClickInside && isOpen) {
this.querySelector('details[open]').removeAttribute('open');
}
});
}
connectedCallback() {
this.selectable();
this.closeable();
this.update(document.documentElement.getAttribute('theme'));
}
}
customElements.define('theme-picker', ThemePicker);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment