Hi I want to make this code work for each element in the page but it only works for the first one. I can't make querySelectorAll()
work. Only the first button works and the others don't. Thanks to all.
const button = document.querySelector('#like');
const counter = '#counter';
let state = false;
let like = 0;
writeCounter(like, counter);
setState(state, button);
function writeCounter(number, id) {
const elm = button.querySelector(id);
elm.innerHTML = number;
}
function setState(state, elm) {
if (state) {
elm.classList.remove('dislike');
elm.classList.add('active');
} else {
elm.classList.remove('active');
elm.classList.add('dislike');
}
}
function addLike() {
if (!state) {
like++;
state = true;
writeCounter(like, counter);
setState(state, button);
} else {
like--;
state = false;
writeCounter(like, counter);
setState(state, button);
}
}
button.addEventListener('click', addLike);
.button__like {
border: none;
border-radius: 1rem;
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
cursor: pointer;
transition: 150ms all;
background-color: #ffffff;
width: 6rem;
}
button.dislike .icon svg.prime {
-webkit-animation: dislike 550ms forwards ease-in-out;
animation: dislike 550ms forwards ease-in-out;
}
button.active {
background-color: #c55151;
}
button.active .counter {
color: #ffffff;
}
button.active .icon svg {
stroke-width: 0;
color: #ffffff;
}
button.active .icon svg.prime {
-webkit-animation: like 550ms forwards ease-in-out;
animation: like 550ms forwards ease-in-out;
}
button .counter {
font-size: 1.8rem;
font-weight: 700;
color: var(--primary-color);
transition: 150ms all;
padding: 0.5rem 0;
}
button .icon {
padding: 1rem;
transition: 150ms all;
position: relative;
width: 2.5rem;
height: 2rem;
}
button .icon svg {
color: rgb(255, 255, 255);
position: absolute;
top: 0;
left: 0;
width: 2rem;
margin-left: 0.5rem;
stroke-width: 60;
stroke: var(--primary-color);
}
button .icon svg.normal {
z-index: 1;
opacity: 0.5;
}
button .icon svg.prime {
z-index: 2;
}
@-webkit-keyframes dislike {
0% {
color: #ff0000;
transform: translate(0, 0%);
}
100% {
color: #ffffff;
transform: translate(0, 300%) scale(0);
}
}
@keyframes dislike {
0% {
color: #ff0000;
transform: translate(0, 0%);
}
100% {
color: #ffffff;
transform: translate(0, 300%) scale(0);
}
}
@-webkit-keyframes like {
0% {
color: white;
transform: translate(0, 0%);
}
100% {
color: #ff0101;
transform: translate(0, -300%) scale(0);
}
}
@keyframes like {
0% {
color: white;
transform: translate(0, 0%);
}
100% {
color: #f60000;
transform: translate(0, -300%) scale(0);
}
}
<button class="button__like" id="like">
<div class="icon">
<svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor"
d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
</path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
</path></svg>
</div>
<div class="counter" id="counter"></div>
</button>
Event delegation is a programming paradigm that allows us to assign just one element to control an unlimited amount of elements even if added dynamically afterwards. Details are commented in the example.
.querySelectorAll()
(as requested)Using document.querySelectorAll("button")
creates a NodeList of all of the <button>
s. Using the spread operator (...
) in the NodeList converts it into a real array which is needed since we'll be using .indexOf()
to find out the index in the array that corresponds to the <button>
the user clicked.
Note: The CSS still needed to be changed because it originally animated when the page loaded which looks confusing. Moreover the <button>
is placed at the lowest point in the stacking order making it clickable only to the small space between .counter
and the right border.
The original JavaScript relied on #id which isn't used when dealing with multiple elements due to the fact that #ids must be unique so if you were to use unique #ids (ex. #like1
, #like2
, etc.) you'd be severely limiting future functionality and unnecessarily complicating your code. So as a compromise everything is determined by the index number within the buttons
array. At least with that layout you won't have to modify anything if you add or delete <button>
s before the page loads (in Example 1 the number of <button>
s can change at any time). Basically changing the JavaScript and CSS is unavoidable due to its flawed design.
Using Event Delegation (recommended)
/**
* Register a common ancestor element (eg <main>)
* to listen for all "click" events that fire
* on itself and any of its children elements.
* This strategy is part of "Event Delegation".
* When a "click" event happens on <main>, the
* event handler toggleState() is called.
*/
const main = document.querySelector("main")
main.addEventListener("click", toggleState);
/**
* Handles "click" events by delegating them only
* to any <button> that is a child of <main>.
* By acting only when the user clicks a <button>
* that's within <main> and ignoring every other
* element is the most important step in "Event
* Delegation".
* @param {object} event - Event object
*/
/**
* All event handlers pass the Event object by
* default.
*/
function toggleState(event) {
/**
* Get the actual element the user clicked by
* using Event objects property: .target
*/
const clicked = event.target;
// If the user clicked a <button>...
if (clicked.matches("button")) {
// reference .counter within this <button>...
const counter = clicked.querySelector(".counter");
// if this <button> has class .initial...
if (clicked.matches(".initial")) {
/**
* add class .like and remove class .initial
* and change .counter text to: "1"...
*/
clicked.classList.add("like");
clicked.classList.remove("initial");
counter.textContent = "1";
// otherwise if this <button> has class .like...
} else if (clicked.matches(".like")) {
/**
* add class .dislike and remove class
* .like
* and change .counter text to "0"...
*/
clicked.classList.add(".dislike");
clicked.classList.remove("like");
counter.textContent = "0";
// otherwise...
} else {
/**
* add class .like and remove class .dislike
* and change .counter text to: "1"
*/
clicked.classList.add("like");
clicked.classList.remove("dislike");
counter.textContent = "1";
}
}
};
/* All --webkit properties have been removed since
* all of the applicable properties are baseline.
*/
/* To avoid the "dislike" animation from running
* when the page loads, the transition property
* was removed.
*/
button {
display: flex;
justify-content: center;
align-items: center;
width: 6rem;
border: none;
border-radius: 1rem;
background-color: #fff;
cursor: pointer;
}
/* All of the children elements of <button> were
* interfering with clicks. So the easiest way
* to bypass the stacking order,
* pointer-events: none was assigned to all children
* elements of <button>.
*/
button * {
pointer-events: none;
}
/* Initiate animation when class .dislike is
* assigned to <button>
*/
button.dislike {
transition: 150ms all;
}
/* Default "dislike" animation is assigned to
* <button> as long as it doesn't have class
* .initial. Note "dislike" animation is overridden
* by "like" animation (when button.like)
*/
button:not(.initial) .icon svg.prime {
animation: dislike 550ms forwards ease-in-out;
}
/* .active class is now class .like (it makes
* more sense).
*/
button.like {
background-color: #c55151;
}
button.like .counter {
color: #fff;
}
button.like .icon svg {
color: #fff;
stroke-width: 0;
}
button.like .icon svg.prime {
animation: like 550ms forwards ease-in-out;
}
/* The remaining styles are unaltered */
button .counter {
padding: 0.5rem 0;
font-size: 1.8rem;
font-weight: 700;
color: var(--primary-color);
transition: 150ms all;
}
button .icon {
position: relative;
width: 2.5rem;
height: 2rem;
padding: 1rem;
transition: 150ms all;
}
button .icon svg {
position: absolute;
top: 0;
left: 0;
width: 2rem;
margin-left: 0.5rem;
color: #fff;
stroke-width: 60;
stroke: var(--primary-color);
}
button .icon svg.normal {
z-index: 1;
opacity: 0.5;
}
button .icon svg.prime {
z-index: 2;
}
@keyframes dislike {
0% {
color: #ff0000;
transform: translate(0, 0%);
}
100% {
color: #fff;
transform: translate(0, 300%) scale(0);
}
}
@keyframes like {
0% {
color: #fff;
transform: translate(0, 0%);
}
100% {
color: #f60000;
transform: translate(0, -300%) scale(0);
}
}
<!-- Everything is wrapped in <main> -->
<!-- All #ids were removed because there are
multiple targets to click. Since all #ids
must be unique, it would be problematic to
keep track of multiple #ids. -->
<!-- Each <button> is assigned a class: .initial.
.initial represents a neutral state without
any animation. -->
<main>
<button class="initial">
<div class="icon">
<svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
</path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
</path></svg>
</div>
<div class="counter">0</div>
</button>
<button class="initial">
<div class="icon">
<svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
</path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
</path></svg>
</div>
<div class="counter">0</div>
</button>
<button class="initial">
<div class="icon">
<svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
</path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
</path></svg>
</div>
<div class="counter">0</div>
</button>
</main>
Using querySelectorAll()
(as requested)
// This is for demonstration purposes only
const log = data => console.log(JSON.stringify(data));
// Collect all <button> into an array
const buttons = [...document.querySelectorAll("button")];
/**
* Create an array that has a 0 for each <button>
* 0 = dislike, 1 = like, initially all are
* 0 but .dislike isn't assigned yet.
*/
let states = Array(buttons.length).fill(0);
log("Initial state of each button");
log(states);
/**
* Initially, each <button> is registered to the
* "click" event.
* and writeCounter() is called for each <button>
*/
buttons.forEach((btn, idx) => {
btn.addEventListener('click', addLike);
writeCounter(idx);
});
/**
* With a given index number, the correct .counter
* will display the current value of 0 or 1
* according to its index in the states array
* @param {number} index - Current index number
*/
function writeCounter(index) {
const elm = buttons[index];
elm.querySelector(".counter").textContent = states[index];
}
/**
* With a given index number, the corresponding
* <button> will be assigned classes according
* to its value in the states array and if
* it's at its initial state.
* @param {number} index - Current index number
*/
function setState(index) {
const elm = buttons[index];
if (elm.matches(".initial")) {
elm.classList.remove('initial');
elm.classList.add('like');
} else if (states[index] === 1) {
elm.classList.remove('dislike');
elm.classList.add('like');
} else {
elm.classList.remove('like');
elm.classList.add('dislike');
}
}
/**
* Handles the "click" event on a <button>.
* @param {object} event - Event object
*/
function addLike(event) {
// Determine which <button> the user clicked
const clicked = event.target;
// Determine the index number of clicked <button>
const index = buttons.indexOf(clicked);
// Toggle the value of states array at index
if (states[index] === 0) {
states[index] = 1;
} else {
states[index] = 0;
}
log("State of each button");
log(states);
// Call setState() and writeCounter()
setState(index);
writeCounter(index);
}
/* For demonstration purposes only */
.as-console-wrapper { left: auto!important; top: 0; width: 60%; }
/* All --webkit properties have been removed since
* all of the applicable properties are baseline.
*/
/* To avoid the "dislike" animation from running
* when the page loads, the transition property
* was removed.
*/
button {
display: flex;
justify-content: center;
align-items: center;
width: 6rem;
border: none;
border-radius: 1rem;
background-color: #fff;
cursor: pointer;
}
/* All of the children elements of <button> were
* interfering with clicks. So the easiest way
* to bypass the stacking order,
* pointer-events: none was assigned to all children
* elements of <button>.
*/
button * {
pointer-events: none;
}
/* Initiate animation when class .dislike is
* assigned to <button>
*/
button.dislike {
transition: 150ms all;
}
/* Default "dislike" animation is assigned to
* <button> as long as it doesn't have class
* .initial. Note "dislike" animation is overridden
* by "like" animation (when button.like)
*/
button:not(.initial) .icon svg.prime {
animation: dislike 550ms forwards ease-in-out;
}
/* .active class is now class .like (it makes
* more sense).
*/
button.like {
background-color: #c55151;
}
button.like .counter {
color: #fff;
}
button.like .icon svg {
color: #fff;
stroke-width: 0;
}
button.like .icon svg.prime {
animation: like 550ms forwards ease-in-out;
}
/* The remaining styles are unaltered */
button .counter {
padding: 0.5rem 0;
font-size: 1.8rem;
font-weight: 700;
color: var(--primary-color);
transition: 150ms all;
}
button .icon {
position: relative;
width: 2.5rem;
height: 2rem;
padding: 1rem;
transition: 150ms all;
}
button .icon svg {
position: absolute;
top: 0;
left: 0;
width: 2rem;
margin-left: 0.5rem;
color: #fff;
stroke-width: 60;
stroke: var(--primary-color);
}
button .icon svg.normal {
z-index: 1;
opacity: 0.5;
}
button .icon svg.prime {
z-index: 2;
}
@keyframes dislike {
0% {
color: #ff0000;
transform: translate(0, 0%);
}
100% {
color: #fff;
transform: translate(0, 300%) scale(0);
}
}
@keyframes like {
0% {
color: #fff;
transform: translate(0, 0%);
}
100% {
color: #f60000;
transform: translate(0, -300%) scale(0);
}
}
<!-- .initial class was added to each <button> -->
<button class="initial">
<div class="icon">
<svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
</path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
</path></svg>
</div>
<div class="counter"></div>
</button>
<button class="initial">
<div class="icon">
<svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
</path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
</path></svg>
</div>
<div class="counter"></div>
</button>
<button class="initial">
<div class="icon">
<svg class="prime" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M462.3 62.6C407.5 15.9 326 24.3 275.7 76.2L256 96.5l-19.7-20.3C186.1 24.3 104.5 15.9 49.7 62.6c-62.8 53.6-66.1 149.8-9.9 207.9l193.5 199.8c12.5 12.9 32.8 12.9 45.3 0l193.5-199.8c56.3-58.1 53-154.3-9.8-207.9z">
</path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path fill="currentColor" d="M256 448a32 32 0 01-18-5.57c-78.59-53.35-112.62-89.93-131.39-112.8-40-48.75-59.15-98.8-58.61-153C48.63 114.52 98.46 64 159.08 64c44.08 0 74.61 24.83 92.39 45.51a6 6 0 009.06 0C278.31 88.81 308.84 64 352.92 64c60.62 0 110.45 50.52 111.08 112.64.54 54.21-18.63 104.26-58.61 153-18.77 22.87-52.8 59.45-131.39 112.8a32 32 0 01-18 5.56z">
</path></svg>
</div>
<div class="counter"></div>
</button>