I am developing a functionality to let the user choose a dark or light theme and also a theme color for the app. I'm doing it through the css filter, specifically invert(1)
to get the dark theme and hue-rotate
for the theme color. The dark-light part works perfectly because I excluded some classes from being affected by the invert()
filter, mainly images and some svgs, by just setting invert(0)
to them. But, as the theme color chooser works with a range selector to bring many color schemes, I can not use the same method to exclude those classes because the value in degrees of the hue-rotate()
changes with the user selection. It still can be done if I would be able to programatically calculate the opposite hue an reaply it to those classes, but I've just messed up in the proccess as my javascript knowledge is very limited.
So I would appreciate any possible help on accomplishing that.
So I came up with this code:
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link href="css/light.css" rel="stylesheet" id="theme-link">
</head>
<body id="usrhue">
<h3><label><b>Theme</b></label></h3>
<input type="checkbox" onchange="toggleTheme()"/>
<h3>Theme Color</h1>
<pre><h3><code id="value">hue-rotate(0deg)</code></h3></pre>
<input id="hue-rotate" step="45" type="range" min="0" max="315" value="0">
<div>
<div class="tile">navbar</div>
<svg class="icon" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" x="0" y="0" width="50" height="33" viewbox="0 0 50 33"><path style="fill-opacity:1;fill-rule:evenodd;stroke:none" d="m 50,16.111089 c -0.0023,-0.52719 -0.269301,-1.175411 -0.836045,-1.837787 L 33.538651,0 l 0,4.099986 10.560199,12.011103 -10.560199,12.011102 0,4.099986 15.625304,-14.273302 C 49.730699,17.286499 49.997802,16.638278 50,16.111089 Z m -50,0 c 0.0022,-0.52719 0.2693022,-1.175411 0.8360452,-1.837787 L 16.46135,0 l 0,4.099986 -10.5601995,12.011103 10.5601995,12.011102 0,4.099986 L 0.8360452,17.948875 C 0.2693022,17.286499 0.0021979,16.638278 0,16.111089 Z"/></svg>
</div>
<img src="https://campstoregear.com/wp-content/uploads/2017/09/summer-camp-2018-apparel-design-CD1816-Primary.png">
</body>
</html>
JS
// Theme Chooser
const theme = document.querySelector('#theme-link');
const currentTheme = localStorage.getItem('theme');
if( currentTheme == 'dark' && theme.getAttribute('href')=='css/light.css' ){
theme.href='css/dark.css';
}
function toggleTheme(){
if( theme.getAttribute('href') == 'css/light.css' ) {
theme.href = 'css/dark.css';
themeName = 'dark';
} else {
theme.href = 'css/light.css';
themeName = 'light';
}
localStorage.setItem( 'theme', themeName );
};
// Theme Color Chooser
const currentThemec = localStorage.getItem('themeColor');
const value = document.getElementById('value');
const body = document.getElementById('usrhue');
if (currentThemec) {
const themeColor = currentThemec;
body.style.filter = currentThemec;
value.textContent = currentThemec;
}
document.getElementById('hue-rotate').oninput = function() {
const filter = 'hue-rotate(xdeg)'.replace('x', this.value);
value.textContent = filter;
body.style.filter = filter;
localStorage.setItem( 'themeColor', body.style.filter );
}
LIGHT.CSS
html {
height:100%;
overflow:hidden;
}
body {
display: flex;
height:100%;
justify-content: center;
align-items:center;
flex-direction:column;
background:#999;
}
input {
margin-bottom:5px;
}
.tile {
width:50px;
height:50px;
display:inline-block;
}
.tile:nth-child(1) {
background: #006648;
}
.tile:nth-child(2) {
background: #007aff;
}
pre {
color:#ff0000;
}
img {
width:5%;
}
h3 {
color:#0000ff;
}
svg {
fill:#007aff;
width:45px;
display: flex;
justify-content: center;
align-items:center;
flex-direction:column;
}
DARK.CSS
Dark css is the same as light but adding this at the bottom
html, img, svg, pre, code { filter: invert(1) contrast(1) saturate(1);}
.webp, .png, .jpg { filter: invert(0) contrast(1) saturate(1);}
As you will notice if you toggle between light and dark this action do not affect the svg icon nor the image.
But when you change theme color they both are affected.
So the question is to solve this issue by this way (maybe you could think a second, third, fourth or more ways) programatically calculate the opposite hue for each stage of the hue range selector and dynamically apply it to those classes.
I have done some calculation with the help of the inspect dev tool and, for example, let's say that you choose the first stage of the selector, 45deg, then if you apply to img invert(1) hue-rotate(-45deg)
on the dark.css and invert(0) hue-rotate(-45deg)
on the light.css, then for that specific hue value, the image remains ok. Following the same thread of thinking it is very easy to calculate all the 7 values. But the question of applying them in a dynamic way is still being a huge problem to my limited javascript skills.
I also have found this chunk of code here How to transform black into any given color using only CSS filters
HUE-ROTATE CALCULATION
function hueRotate(angle = 0) {
angle = angle / 180 * Math.PI;
let sin = Math.sin(angle);
let cos = Math.cos(angle);
this.multiply([
0.213 + cos * 0.787 - sin * 0.213, 0.715 - cos * 0.715 - sin * 0.715, 0.072 - cos * 0.072 + sin * 0.928,
0.213 - cos * 0.213 + sin * 0.143, 0.715 + cos * 0.285 + sin * 0.140, 0.072 - cos * 0.072 - sin * 0.283,
0.213 - cos * 0.213 - sin * 0.787, 0.715 - cos * 0.715 + sin * 0.715, 0.072 + cos * 0.928 + sin * 0.072
]);}
Maybe someone with more js experience and ability than mine could use it to accomplish what I need.