Issue
1. Original Question & First Bounty
Given a very basic color scheme.
One that allows the website user to set a desired theme from a predefined set of CSS root variable definitions.
Is there an easy way to let the browser remember the theme, it set by the user, so that the user's input is carried over to the next pages? Thus eliminating the need for setting the color scheme on every new page!
const setTheme = theme => document.documentElement.className = theme;
document.getElementById('scheme').addEventListener('click', ({target}) => {
setTheme(target.getAttribute('id'));
});
html{margin: 10px}
#scheme p{ /* User Interface */
display: inline-block;
text-decoration: underline;
}#scheme p:hover{cursor: pointer}
:root{ /* Default Theme */
--bgr: #eee;
--txt: #000;
}
:root.light {
--bgr: #ddc;
--txt: #466;
}
:root.dark {
--bgr: #222;
--txt: #A75;
}
:root.blue{
--bgr: #246;
--txt: #eec;
}
body { /* Have something to test */
background: var(--bgr);
color: var(--txt);
}
<div id="scheme">
<p id="light">Light</p>
<p id="dark">Dark</p>
<p id="blue">Blue</p>
<p id="etc">Etc</p>
</div>
<h1>Click on a theme to change the color scheme!</h1>
2. Updated Precision & Second Bounty
Attention to the Original Question and Improvements Needed
The current two answers have some problems from the point of view of the original question: one answer has A)diverged away from the css root: {}/* Default Theme */ by introducing root.default{} and B) has implemented automatic theme selection with "light"/"dark" an and added "auto" theme, which though awesome for some, is the opposite of what is asked in the question: a simple manual user choice, overruling just one "unset" root: {} default theme.
The other answer thought nice and basic C) necessitates manually setting the optional CSS theme names in an JavaScript array, making it prone to future errors and would be nice not need setting because all manual theme options are consistently named like :root.themename{}. D) Also this solution causes a second or so delay when setting themes in mobile iOS devices!?
2nd Bounty goes to new answers that check the most of these points:
- Stick to the original questions
root:{}as the only default (unset theme). *1) - Nothing beyond the basics, no automatic theme selection please.
- Do not necessitate css theme names in JavaScript code.
- Allow SVG boxes as buttons for more design flexibility. *2)
*1) The reason why I want root: {} as default theme, is because I would like to set CSS Filters like saturation and grayscale on the themes that impact the entire page, images, logos, everything!
*2) Simpler cleaner html with svg buttons for setting the themes. In this future third and last bounty, plain SVG boxes (with one or more colours) serve as buttons for setting the themes! How awesome would that be?! Wordless, Timeless! See snippet below.
:root{ /* Default Theme, if no theme is manually selected by user */
--bgr: #eee;
--txt: #000;
--flt: none;
}
:root.blackwhite{
--bgr: #fff;
--txt: #000;
--flt: contrast(100%) grayscale(100%);
}
:root.sepia {
--bgr: lightblue;
--txt: red;
--flt: sepia(75%);
}
:root.holiday{
--bgr: #fba;
--txt: #269;
--flt: blur(.25px) saturate(4);
}
:root.moody{
--bgr: green;
--txt: yellow;
--flt: drop-shadow(16px 16px 20px yellow) blur(1px);
}
html { /* Have something to test */
background: var(--bgr);
color: var(--txt);
filter: var(--flt); /* important filter that affects everything */
}
h1{
background: var(--bgr);
color: var(--txt);
}
theme{ /* html element div that contains only SVG graphics */
display: flex;
flex-direction: row;
}
<!--I prefer a custom more logical `<theme>` over an equally
meanigless <div id="theme"> as container, since both do not
carry any semantic meaning. But, if you prefer a standard div,
then thats fine! Just explain why thats better. Thanks!-->
<theme>
<svg id="blackwhite"><rect width="100" height="50" /></svg>
<svg id="sepia" ><rect width="100" height="50" /></svg>
<svg id="holiday" ><rect width="100" height="50" /></svg>
<svg id="moody" ><rect width="100" height="50" /></svg>
</theme>
<h1>Click on a theme to change the color scheme!</h1>
<p>Some Paragraph texts.</p>
<img src="\clouds.jpg" alt="clouds"/>
3. Updated User Interface Feedback & Third Bounty
Show the user which theme he has manually clicked on.
Add a class .chosentheme to the svg element that is currently chosen or active, so that the user can see which one of the theme buttons is currently active/chosen/selected!
<theme id="scheme">
<svg id="blackwhite"><rect/></svg>
<svg id="midnight"><rect/></svg>
<svg id="beach"><rect/></svg>
<svg ><rect/></svg><!-- currently works as a (dummy) button to activate the :root{} Default Theme -->
</theme>
If no svg is selected yet, or if the memory is empty, then the last svg could be automatically selected via CSS or via setting an exception for this one id in the code like "defaulttheme", <svg id="defaulttheme">, which already works as a (dummy) button to load the default theme :root{}.
(It's okay if by default, when the memory is empty, nothing is selected, even not the last default theme svg).
Then, if any of the svg's is clicked or if a theme is loaded from memory, then the .chosentheme styling should be applied via JavaScript dynamically, and added to that svg element's list of class names, letting the user know that he has manually clicked on that theme or that that theme is currently already loaded and showing.
theme svg.chosentheme { border: 1px solid black }
/* Sets a border around the currently activated theme */
/* Because someone clicked on it or because its stored in memory */
Solution
Try this simple resolve: to save, load and select a theme from local storage. Local Storage doesn't working in snippets or sandboxes.
The localStorage read-only property of the window interface allows you to access a Storage object for the Document's origin; the stored data is saved across browser sessions. MDN documentation
JS
// Select class name as in CSS file
const CLASS_NAME = 'chosentheme';
const scheme = document.getElementById('scheme');
// Creating an array of SVG elements
const svgElementsArray = [...scheme.querySelectorAll('svg')];
// Creating a color theme array using the SVG ID attribute
const themeNameArray = svgElementsArray.map(theme => theme.id);
// Get html node (html tag)
const htmlNode = document.documentElement;
// Get color (value) from local storage
const getLocalStorageTheme = localStorage.getItem('theme');
const setTheme = theme => {
// Set class to html node
htmlNode.className = theme;
// Set theme color to local storage
localStorage.setItem('theme', theme);
svgElementsArray.forEach(svg => {
// If we click on the svg and it has a class, do nothing
if (svg.id === theme && svg.classList.contains(CLASS_NAME)) return;
// Check, if svg has the same ID and if it doesn't have a class,
// then we adding class and removing from another svg
if (svg.id === theme && !svg.classList.contains(CLASS_NAME)) {
svg.classList.add(CLASS_NAME);
} else {
svg.classList.remove(CLASS_NAME);
}
});
};
// Find current theme color (value) from array
const findThemeName = themeNameArray.find(theme => theme === getLocalStorageTheme);
// If local storage empty
if (getLocalStorageTheme) {
// Set loaded theme
setTheme(findThemeName);
} else {
// Find last svg and set the class (focus)
svgElementsArray.at(-1).classList.add(CLASS_NAME);
}
document.getElementById('scheme').addEventListener('click', ({ target }) => {
// Getting ID from an attribute
const id = target.getAttribute('id');
// Find current theme color (value) from array
const findThemeName = themeNameArray.find(theme => theme === id);
setTheme(findThemeName);
});
We also need to prevent selection of child elements inside the button (SVG) and to select exactly the button with ID attribute.
CSS
theme svg > * {
pointer-events: none;
}
theme svg {
/* to prevent small shifts,
when adding the chosentheme class */
border: 1px solid transparent;
}
theme svg.chosentheme {
border-color: black;
}
To prevent the webpage from flickering (blinking) while is loading, place this snippent at the top of the head tag. (prevent dark themes from flickering on load)
HTML
<head>
<script>
function getUserPreference() {
if(window.localStorage.getItem('theme')) {
return window.localStorage.getItem('theme')
}
}
document.documentElement.dataset.theme = getUserPreference();
</script>
....
</head>
// Select class name as in CSS file
const CLASS_NAME = 'chosentheme';
const scheme = document.getElementById('scheme');
// Creating an array of SVG elements
const svgElementsArray = [...scheme.querySelectorAll('svg')];
// Creating a color theme array using the SVG ID attribute
const themeNameArray = svgElementsArray.map(theme => theme.id);
// Get html node (html tag)
const htmlNode = document.documentElement;
// Get color (value) from local storage
const getLocalStorageTheme = localStorage.getItem('theme');
const setTheme = theme => {
// Set class to html node
htmlNode.className = theme;
// Set theme color to local storage
localStorage.setItem('theme', theme);
svgElementsArray.forEach(svg => {
// If we click on the svg and it has a class, do nothing
if (svg.id === theme && svg.classList.contains(CLASS_NAME)) return;
// Check, if svg has the same ID and if it doesn't have a class,
// then we adding class and removing from another svg
if (svg.id === theme && !svg.classList.contains(CLASS_NAME)) {
svg.classList.add(CLASS_NAME);
} else {
svg.classList.remove(CLASS_NAME);
}
});
};
// Find current theme color (value) from array
const findThemeName = themeNameArray.find(theme => theme === getLocalStorageTheme);
// If local storage empty
if (getLocalStorageTheme) {
// Set loaded theme
setTheme(findThemeName);
} else {
// Find last svg and set the class (focus)
svgElementsArray.at(-1).classList.add(CLASS_NAME);
}
document.getElementById('scheme').addEventListener('click', ({
target
}) => {
// Getting ID from an attribute
const id = target.getAttribute('id');
// Find current theme color (value) from array
const findThemeName = themeNameArray.find(theme => theme === id);
setTheme(findThemeName);
});
:root {
/* Default Theme, if no theme is manually selected by user */
--bgr: #eee;
--txt: #000;
--flt: none;
}
:root.blackwhite {
--bgr: #fff;
--txt: #000;
--flt: contrast(100%) grayscale(100%);
}
:root.midnight {
--bgr: lightblue;
--txt: red;
--flt: sepia(75%);
}
:root.beach {
--bgr: #fba;
--txt: #269;
--flt: blur(0.25px) saturate(4);
}
/* :root.moody {
--bgr: green;
--txt: yellow;
--flt: drop-shadow(16px 16px 20px yellow) blur(1px);
} */
html {
/* Have something to test */
background: var(--bgr);
color: var(--txt);
filter: var(--flt);
/* important filter that affects everything */
}
h1 {
background: var(--bgr);
color: var(--txt);
}
theme {
/* html element div that contains only SVG graphics */
display: flex;
flex-direction: row;
}
theme svg > * {
/* prevent selection */
pointer-events: none;
}
theme svg {
/* to prevent small shifts,
when adding a focus class */
border: 1px solid transparent;
}
theme svg.chosentheme {
border-color: black;
}
<theme id="scheme">
<svg id="blackwhite"><rect /></svg>
<svg id="midnight"><rect /></svg>
<svg id="beach"><rect /></svg>
<svg id="defaulttheme"><rect /></svg>
</theme>
<h1>Click on a theme to change the color scheme!</h1>
<p>Some Paragraph texts.</p>
Answered By - Anton
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.