Cycle through 2 classes on `html` after clicking a link/toggle - javascript

I currently have a toggle that when clicked adds/removes a class to the html tag.
I'd like to update this so if you click after the original class is added the class is changed to .new-mode rather than removing the current class and the html tag being class-less. If the link is clicked again, it will then return to the default state.
So in essence it's got 3 states:
No class (default / on load)
Class One added (on 1st click)
Class One removed, Class Two added (on 2nd click)
Then on the next click it would return to the default state without a class. So essentially just cycling through 2 classes on click. You can see I have the 1st toggle working in my example - but I'm unsure how to target the next click(s) and I'd really appreciate some help.
const html = document.querySelector('html');
const button = document.querySelector('.contrast__link');
button.addEventListener('click', e => {
e.preventDefault();
html.classList.toggle('dark-mode');
});
html { background: white; color: black; }
.dark-mode { background: black; color: white;}
.new-mode { background: blue; color: white;}
<p class="contrast__link">Click here</p>

Check what the current state is and handle the transition to the next state. Since you have a reference to the html element, you can use its classList property to see which classes are currently applied to it.
The return value is not an array, it's a DOMTokenList, so be sure to use DOMTokenList.contains() instead of Array#includes. The collection also supports adding, removing, and toggling one or more classes.
The simplest way to check and change the state is an if-else chain:
const html = document.querySelector('html');
const button = document.querySelector('.contrast__link');
button.addEventListener('click', e => {
e.preventDefault();
if (html.classList.contains('dark-mode')) {
html.classList.remove('dark-mode');
html.classList.add('new-mode');
}
else if (html.classList.contains('new-mode')) {
html.classList.remove('new-mode');
}
else {
html.classList.add('dark-mode');
}
});
html { background: white; color: black; }
.dark-mode { background: black; color: white;}
.new-mode { background: blue; color: white;}
<p class="contrast__link">Click here</p>

Related

How to remember the color scheme once its set by user? (Simple Color Scheme) [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 5 months ago.
Improve this question
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 */
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>
There are several ways to to persist data in the browser between page loads:
cookies
localStorage
sessionStorage
IndexedDB
I suggest using localStorage. Here is a very detailed comparison of the different methods.
update:
localStorage never expires. The user may manually delete/reset the localStorage via the browser settings/dev console.
You can control the expiration of values in localStorage by storing the expiration timestamp with the value and checking the timestamp each time the value is retrieved. (If you just want the values to expire after a page session, use sessionStorage.)
amplify.store is a wrapper around the browser storage API's. It lets you set an optional expiration time.
Using matchMedia and prefers-color-scheme you can apply a default theme based on the user's system-wide preference. This will adjust automatically if the user has enabled auto-switching based on the time of day or through the light sensor on their device.
Then, if they choose to override this, save their selection in localStorage. This preference will remain until the user clears the storage for your origin.
<!DOCTYPE html>
<head>
<title> Theme Selector Test </title>
<style>
:root.default { --bgr: #eee; --txt: #000; }
:root.light { --bgr: #ddc; --txt: #446; }
:root.dark { --bgr: #222; --txt: #a75; }
:root.blue { --bgr: #246; --txt: #eec; }
body { background: var(--bgr); color: var(--txt); margin: 1.5rem; }
</style>
<script>
let prefersDark = matchMedia('(prefers-color-scheme: dark)');
prefersDark.addEventListener('change', event => loadTheme());
function setTheme(theme) {
if (theme == 'auto') {
localStorage.removeItem('theme');
loadTheme(null);
} else {
localStorage.setItem('theme', theme);
applyTheme(theme);
}
}
function loadTheme(theme) {
theme = localStorage.getItem('theme');
theme ??= (prefersDark.matches) ? 'dark' : 'default';
applyTheme(theme);
}
function applyTheme(theme) {
document.documentElement.className = theme;
}
window.setTheme = setTheme;
loadTheme();
</script>
</head>
<body>
<h1> Select a theme to change the color scheme! </h1>
<select id="scheme">
<option value="auto">Auto</option>
<option value="default">Default</option>
<option value="light">Light</option>
<option value="dark">Dark</option>
<option value="blue">Blue</option>
</select>
<script>
let selector = document.getElementById('scheme');
selector.value = localStorage.getItem('theme') || 'auto';
selector.addEventListener('click', event => window.setTheme(selector.value));
</script>
</body>
See this answer for instructions on how to simulate the system-wide preference for testing purposes.
As #Anton mentioned, localStorage doesn't work in snippets here on Stack Overflow due to sandboxing so instead I've written this as a full page example to demonstrate the best way to implement it in a real-world environment.
I have also published an ES Module version of this. Implementing the inline version as demonstrated in this post is better for performance but the module version is better if you want to avoid polluting the global scope.
I've used a <select> element in the example since this is probably how most user's who find this in the future will likely want to use it. To display the options like you have shown in your question you can implement as demonstrated below. Note that I've replaced the <p> tags with <button> for better accessibility. I've also added an extra check in the click handler to avoid setTheme from being called if the background area of the container <div> is clicked.
In your CSS :
#scheme button {
border: 0;
background: none;
color: inherit;
text-decoration: underline;
cursor: pointer;
}
#scheme button * {
pointer-events: none;
}
In your HTML <body> :
<div id="scheme">
<button id="auto">Auto</button>
<button id="default">Default</button>
<button id="light">Light</button>
<button id="dark">Dark</button>
<button id="blue">Blue</button>
</div>
<h1>Click on a theme to change the color scheme!</h1>
<script>
let selector = document.getElementById('scheme');
selector.addEventListener('click', event => {
if (event.target == selector) { return; }
window.setTheme(event.target.id);
});
</script>
If used inside a form, you'll need to add event.preventDefault(); to the click handler to avoid submitting when the buttons are clicked.

How do i change one of the classes in an element with Javascript

If i have the library materialize and an element "h1", how do i change only its background color "black" to "white" using javascript? For example using a button.
<h1 class="black red-text">Test</h1>
Give the element an id:
<h1 id="test" class="black red-text">Test</h1>
Then you can select it like the following:
var el = document.getElementById('test');
You can then just remove the class black with this code:
el.classList.remove('black');
And append the new class:
el.classList.add('white');
Create a function and a white class. Inside the function use document.getElementsByClassName . Since document.getElementsByClassName is a collection so you need to use index like [0] to access it
function changeColor() {
document.getElementsByClassName("black")[0].classList.add('white')
}
.white {
background: white !important;
}
.black {
background: black;
}
.red-text {
color: red;
}
<h1 class="black red-text">Test</h1>
<button onclick='changeColor()'>Change Color</button>
Here is the answer.
Select the element using document. getElementById
and on button click
You can use title.classList.toggle("white"); to toggle given class.
const button = document.getElementById('btn');
const title = document.getElementById('title');
btn.addEventListener('click', () => {
title.classList.toggle("white");
});
.red-text {
color: red;
}
.black {
background-color: #000;
}
.white {
background-color: #fff;
}
<h1 id="title" class="black red-text">Test</h1>
<button id="btn">Change Bg</button>
Hope this resolves your problem. just check the condition whether the class already exist if not add it.
let h1 = document.querySelector(".red-text");
let btn = document.querySelector("#btn");
function changeColor(){
if(h1.classList.contains("black")){
h1.classList.remove("black");
h1.classList.add("white");
}else{
h1.classList.add("black");
h1.classList.remove("white");
}
}
btn.addEventListener("click", changeColor);
.black{
background: black;
color: red
}
.white{
background: white;
}
<h1 class="black red-text">Test</h1>
<button id="btn">Change Color</button>
One approach that keeps things neat and tidy in terms of Materialize conventions could be to add or remove classnames, and thus triggering pre-existing styles. Note, you may need a more specific selector if you have multiple elements that share the class:
document.querySelector('h1.black').classList.add('white');
Materialize already has a vast array of colour classes so no need to create new ones.
Codepen here:
And just for clarification:
document.querySelector('h1.black') - this finds the element with a matching class. You could also use ID to be more specific, while
.classList retrieves all the classes applied to that element (in this case .black and .red-text) and .add('white') - well, that adds the class you state in the brackets.

How do I dim other elements in the webpage when I click on that element?

I am building a wordpress website and in the header part there is a search bar. How to create a dim effect(background:rgba(0,0,0,0.3)) on other elements except in search bar when I use the search bar (for example like in Quora.com). Also this code(your answer) should be working if I use code for other elements(other than search bar) like input box,or any other div.
I will be more satisfied if you come with an answer that is flexible to use for any div.
Thanking you in advance.
You need to add a click event to the element that needs to be clicked. You can then bind a function to that event that is triggered upon the click, as follows:
var test = document.getElementById("test");
test.addEventListener("click", changeColour);
function changeColour() {
var changeme = document.getElementById("change");
changeme.classList.add("addopacity");
}
#change {
background: #000;
width: 100px;
height: 100px;
}
#change.addopacity {
opacity: 0.2;
}
<button id="test">Click me please</button>
<div id="change"></div>
Please note, you don't have to add a class to the target element like I have above, you can just as easily add an inline style with changeme.style.opacity = "0.2"; within the function.
If you want to click on the element itself and have it change colour, you can do something similar:
var test = document.getElementById("change");
test.addEventListener("click", changeColour);
function changeColour() {
this.classList.add("addopacity");
}
#change {
background: #000;
width: 100px;
height: 100px;
color: #fff;
}
#change.addopacity {
opacity: 0.2;
}
<div id="change">CLICK ME</div>
Use the opacity property and apply it wherever you need it.
div {
opacity: 0.3;
}

CSS Browser's text selection highlight color

Is it possible to get the browser's default (or themed, assuming there are browser themes? I've never looked) text selection color?
Background
I'm trying to make an <input type="date" /> act like it has a placeholder attribute, like the HTML5 <input type="text" /> does. In case it matters, I'm using Bootstrap.
So far, I've got
CSS
/* allow date inputs to have placeholders */
/* display placeholder text */
input[type="date"].emptyDate:before {
content: attr(placeholder);
color: #999;
}
/* hide default mm/dd/yyyy text when empty value and not in focus */
input[type=date].emptyDate:not(:focus) {
color: transparent;
}
/* hide default mm/dd/yyyy text when empty value, not in focus, and
selected (ctrl + a) */
input[type="date"].emptyDate::selection:not(:focus) {
color: transparent;
background-color: lightblue;
}
/* hide placeholder text when empty value, but in focus */
input[type="date"].emptyDate:focus:before {
content: "";
}
Javascript
function setEmptyDateInputClass(input) {
if ($(input).val()) {
$(input).removeClass("emptyDate");
} else {
$(input).addClass("emptyDate");
}
}
$(document).ready(function () {
$.each($("input[type=date]"), function (i, input) {
// set initial class
setEmptyDateInputClass(input);
// set class on value change
$(input).change(function () { setEmptyDateInputClass(this);});
});
});
Issues
I'm having two issues. The first, and the one I'm asking in this question (I'll post another question if everyone obeys the rules and no one posts answers to multiple questions) is, is there a way to get the browser's default (or themed) selection background color so that, either with CSS or manually with Javascript, the lightblue isn't static? (Also, light blue isn't the right color, but that's just a matter of a screenshot and mspaint.)
input[type="date"].emptyDate::selection:not(:focus) {
background-color: lightblue;
}
My second, bonus issue is, I'm having issues selecting :before::selection in order to set the background color of selected ::before content.
/* always active when .emptyDate */
input[type="date"].emptyDate::selection:before {
background-color: lightblue;
}
/* never active */
input[type="date"].emptyDate:before::selection {
background-color: lightblue;
}
You can use specificity on ::selection like so:
CSS
.red::selection {
background-color: red;
color: #fff;
}
.green::selection{
background-color:green;
color:#fff;
}
HTML
<span class="red">I am highlighted in red, </span>
<span class="green">and I am highlighted in green,</span>
<span class="">and I am highlighted as per browser default.</span>
Example: http://www.bootply.com/KEEvWSlP0F

How to keep hover attributes while disabling click

So I have this list with some hover effect added through CSS.
HTML:
<li>Current Period
<ul>
<li>2012
<li> a href="#">2011</a> //...you get the point
CSS:
#nav a:hover {
background-color: #fff;
color: #333;
}
When the user hovers over current period a list of children elements appear (2012, 2011... which have children of their own). My problem is that users can click on "Current Period". I have managed to remove the click by adding a class to the anchor like so:
<li>Current Period ....
CSS:
.noclick {
pointer-events: none;
cursor: default;
}
but this of course removes the hover feature. I want to keep the hover effect while making the button un-clickable (I was thinking javascript, but I want a more "direct" solution). I appreciate any help :)
In your click handler test whether the clicked item has that class:
$("#nav a").click(function(e){
if ($(e.target).hasClass("noclick"))
return false;
// your other code here
});
Note that by testing the target element for the event you don't then prevent the clicks on child elements from working.
Or if the "noclick" class is not changed dynamically, i.e., those "noclick" links start out as and will always be "noclick", you could change the selector so that your click handler isn't bound to those particular elements:
$("#nav a").not(".noclick").click(function() { ...
Have you tried?
$('.noclick').unbind('click');
or
$('.noclick').click(function(e){e.preventDefault();});
or
Text
Just change your following line:
<li>Current Period ....
for this one
<li>Current Period ....
and change your following css:
.noclick { pointer-events: none; cursor: default; }
for this one
.noclick { cursor: default; }
that should do what you want.
You can use the noClick class to prevent the default event
('.noclick').on('click' , function(e) {
e.preventDefault();
});
on .noclick class remove pointer-events
.noclick { cursor: default; }
and add js to .noclick element
$('.noclick').each(function() {
var $this = $(this);
$this.hover(function() {
// do something...
});
$this.click(function() {
return false;
});
});

Categories

Resources