When I click I want to smoothly add segments to the progress bar. They are added but instantly. What could be the problem?
I tried to implement a smooth animation with setInterval, but nothing comes out. Percentages are also added instantly.
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
let progressBarStartValue = 0;
let progressBarEndValue = 100;
let speed = 50;
body.addEventListener("click", function(e) {
if (progressBarStartValue === progressBarEndValue) {
alert("you have completed all the tasks");
} else {
let progress = setInterval(() => {
if (progressBarStartValue != 100) {
progressBarStartValue += 10;
clearInterval(progress);
}
progressBarValue.textContent = `${progressBarStartValue}%`;
progressBar.style.background = `conic-gradient(
#FFF ${progressBarStartValue * 3.6}deg,
#262623 ${progressBarStartValue * 3.6}deg
)`;
}, speed);
}
});
.progressbar {
position: relative;
height: 150px;
width: 150px;
background-color: #262623;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.progressbar::before {
content: "";
position: absolute;
height: 80%;
width: 80%;
background-color: #0f0f0f;
border-radius: 50%;
}
.progressbar__value {
color: #fff;
z-index: 9;
font-size: 25px;
font-weight: 600;
}
<main class="main">
<section class="statistic">
<div class="container">
<div class="statistic__inner">
<div class="statistic__text">
<h2 class="statistic__title">You're almost there!</h2>
<p class="statistic__subtitle">keep up the good work</p>
</div>
<div class="progressbar"><span class="progressbar__value">0%</span></div>
</div>
</div>
</section>
</main>
This may not be exactly what you're looking for, but with the conic-gradient() implementation you're using, I'd recommend checking out a library call anime.js.
Here's an example with your implementation (same html and css):
// your.js
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
// Switched to object for target in anime()
let progressBarObject = {
progressBarStartValue: 0,
progressBarEndValue: 100,
progressBarAnimationValue: 0 * 3.6 // New value needed for smoothing the progress bar, since the progress value needs to be multiplied by 3.6
}
// Not necessary, but I recommend changing the event listener to pointerup for better support
// Also not necessary, I changed function to arrow function for my own preference
body.addEventListener("pointerup", e => {
e.preventDefault()
if (progressBarObject.progressBarStartValue === progressBarObject.progressBarEndValue) {
alert("you have completed all the tasks");
} else {
let newValue = 0 // Needed so we can set the value, before it's applied in anime()
if (progressBarObject.progressBarStartValue != 100) {
// Math.ceil() allows us to round to the nearest 10 to guarantee the correct output
newValue = Math.ceil((progressBarObject.progressBarStartValue + 10) / 10) * 10;
}
// Optional: Prevents accidentally going over 100 somehow
if (newValue > 100) {
newValue = 100
}
anime({
targets: progressBarObject,
progressBarStartValue: newValue,
progressBarAnimationValue: newValue * 3.6,
easing: 'easeInOutExpo',
round: 1, // Rounds to nearest 1 so you don't have 0.3339...% displayed in progressBarValue
update: () => {
progressBar.style.backgroundImage = `conic-gradient(
#FFF ${progressBarObject.progressBarAnimationValue}deg,
#262623 ${progressBarObject.progressBarAnimationValue}deg)`;
progressBarValue.textContent = `${progressBarObject.progressBarStartValue}%`;
},
duration: 500
});
}
});
Here's a CodePen using the anime.js CDN: Circular Progress Bar Smoothing
If you don't want to use a javascript library, then I'd recommend switching from the conic-gradient() to something else. I hear using an .svg circle with stroke and stroke-dasharray can work great with CSS transition.
You shouldn't setInterval your progress variable like this. instead, put it as a global variable outside the function then use it to gradually add 1 as long as the start value is less than progress, and you still can control the speed with your speed variable.
let progressBar = document.querySelector(".progressbar");
let progressBarValue = document.querySelector(".progressbar__value");
const body = document.querySelector("body");
let progressBarStartValue = 0;
let progressBarEndValue = 100;
let speed = 50;
let progress = 0;
body.addEventListener("click", function(e) {
if (progressBarStartValue === progressBarEndValue) {
alert("you have completed all the tasks");
} else {
progress += 10;
setInterval(() => {
if (progressBarStartValue < progress) {
progressBarStartValue += 1;
clearInterval();
}
progressBarValue.textContent = `${progressBarStartValue}%`;
progressBar.style.background = `conic-gradient(
#FFF ${progressBarStartValue * 3.6}deg,
#262623 ${progressBarStartValue * 3.6}deg
)`;
}, speed);
}
});
.progressbar {
position: relative;
height: 150px;
width: 150px;
background-color: #262623;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid red;
}
.progressbar::before {
content: "";
position: absolute;
height: 80%;
width: 80%;
background-color: #0f0f0f;
border-radius: 50%;
border: 3px solid blue;
}
.progressbar__value {
color: #fff;
z-index: 9;
font-size: 25px;
font-weight: 600;
}
<main class="main">
<section class="statistic">
<div class="container">
<div class="statistic__inner">
<div class="statistic__text">
<h2 class="statistic__title">You're almost there!</h2>
<p class="statistic__subtitle">keep up the good work</p>
</div>
<div class="progressbar"><span class="progressbar__value">0%</span></div>
</div>
</div>
</section>
</main>
I have a slider in my page and slider's indicators are dynamic, It bases on slider's elements' number and width of body.
My code block is:
function setIndicators(){
const indicator = document.createElement("div");
indicator.className = "indicator active";
indicatorContainer.innerHTML = "";
for(let i = 0;i <= maxIndex; i++){
indicatorContainer.appendChild(indicator.cloneNode(true));
}
updateIndicators();
}
which is working fine. But I want to show active indicator but I cannot manipulate elements' classes.
I tried this:
function updateIndicators(index) {
indicators.forEach((indicator) => {
indicator.classList.remove("active");
});
let newActiveIndicator = indicators[index];
newActiveIndicator.classList.add("active");
}
And I am not able to reach every indicators using index or anything I know/find. Also, it seems like NodeList not a HTML element.
Other things you may need:
const indicatorContainer = document.querySelector(".container-indicators");
const indicators = document.querySelectorAll(".indicator");
let maxScrollX = slider.scrollWidth - body.offsetWidth;
let baseSliderWidth = slider.offsetWidth;
let maxIndex = Math.ceil(maxScrollX / baseSliderWidth);
A better one I would suggest using the indicators in a different way. Since your HTML isn't shared, I have to assume a few things:
function clearAll() {
const activeOnes = document.querySelectorAll(".active");
activeOnes.forEach(function(activeOne) {
activeOne.classList.remove("active");
});
}
function chooseOne(index) {
clearAll();
const indicators = document.querySelectorAll(".indicator");
indicators[index].classList.add("active");
}
* {
font-family: 'Operator Mono', consolas, monospace;
}
.indicators {
border: 2px solid #ccc;
display: inline-block;
width: auto;
margin: 15px;
}
.indicators .indicator {
padding: 15px;
line-height: 1;
background-color: #fff;
flex-grow: 1;
text-align: center;
display: inline-block;
}
.indicator.active {
background-color: #f90;
}
<div class="indicators"><div class="indicator">I1</div><div class="indicator">I2</div><div class="indicator">I3</div><div class="indicator">I4</div><div class="indicator">I5</div></div>
<button onclick="chooseOne(2); return false">Select I3</button>
<button onclick="chooseOne(3); return false">Select I4</button>
I would have done this differently this way.
Preview
So this is day 2 of making an HTML game. I am honestly convinced I'm making a lot of progress and I am, except, yet again, I run into another styling problem.
So there is a grid in the game that updates every time the game is loaded. Basically, the grid's length and width to the word with the most letters, as shown below:
As you can see, the word everyday is 8 letters long, so the game puts 8 spaces available.
Now here's two problems with this in general:
I want the word bank to be directly UNDER the grid, no matter the length of the grids.
I want the grids to have NO space under them, so you see the little space between every new row? Basically that needs to go poof, and not be there.
What have you tried so far?
Placing the word bank div under the game area div didn't work, so I started to look up some solutions on Google, and it told me to try to add position: absolute; and position: relative; to div 1 and 2, but that just created a mess when it came to the word bank (spacing it out WAY too much) and did nothing to the grids. Also, display: block; can't help, because the code is already using flex for a different reason.
I also tried using margin-bottom for the grid space problem, but did nothing.
Code:
// definitely didn't get the grid part from Stack Overflow
var score = 0;
var scoreDisplay = document.getElementById("score");
scoreDisplay.innerHTML = "<p>Score: " + score;
var wordBank = document.getElementById("wordBank")
var gameArea = document.getElementById("gameArea")
let rows = document.getElementsByClassName("gridRow");
let cells = document.getElementsByClassName("cell");
// sparing you word array, nobody wants to read that list to the very last bits
var selectedWords = []
for (let i = 0; i < 5; i++) {
const selectedWord = words[Math.floor(Math.random() * words.length)]
if (selectedWord.length <= 9) {
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
selectedWords.push(selectedWord)
}
}
var longestWord = selectedWords.reduce((a, b) => a.length < b.length ? b : a, "")
var charCount = longestWord.length
function makeRows(rowNum) {
for (r = 0; r < rowNum; r++) {
let row = document.createElement("div");
gameArea.appendChild(row).className = "gridRow";
};
};
function makeColumns(cellNum) {
for (i = 0; i < rows.length; i++) {
for (j = 0; j < cellNum; j++) {
let newCell = document.createElement("div");
rows[j].appendChild(newCell).className = "cell";
};
};
};
function defaultGrid() {
makeRows(charCount);
makeColumns(charCount);
}
defaultGrid();
body {
margin: 0px;
}
.content {
width: 512px;
height: 512px;
margin-left: auto;
margin-right: auto;
font-family: Arial;
}
.score {
font-size: 24px;
text-align: right;
}
.wordBank {
border: 2.5px solid black;
border-radius: 5px;
font-size: 24px;
display: flex;
width: 100%;
justify-content: space-between;
height: 13%;
}
.wordBank> :nth-of-type(even) {
align-self: flex-end;
}
.gameArea {
width: 100%;
height: 70%;
}
.cell {
border: 1px solid black;
width: 50px;
height: 50px;
display: inline-block;
}
<div class="content" id="content">
<div class="gameArea" id="gameArea">
</div>
<div class="wordBank" id="wordBank">
</div>
<div class="score" id="score">
</div>
</div>
How can I fix this issue? Any help is appreciated!
(Example for David):
One approach is below, with explanatory comments in the code:
// replaced all uses of 'var' with either let (if I anticipated the value would change), or const
// (if the value was likely to be unchanging):
let score = 0;
const scoreDisplay = document.getElementById("score");
const wordBank = document.getElementById("wordBank")
const gameArea = document.getElementById("gameArea")
const rows = document.getElementsByClassName("gridRow");
const cells = document.getElementsByClassName("cell");
// created an Array of words (though ideally a minimal, demonstrative Array would have been in the
// posted MCVE demo code); obviously: replace with your own Array:
const words = ['hello', 'thrifty', 'gaol', 'maester', 'mandible', 'osteoarthritic', 'venerable', 'the', 'cursive'];
let selectedWords = []
// moved this line out of the variable assignments/initialisation, in order that it's easier to
// maintain the code, because related things/actions are in the same/similar place(s):
scoreDisplay.innerHTML = "<p>Score: " + score;
// the rest of the JavaScript I left alone, with the exception of adding a 'let' declaration in the
// for loops after this first one:
for (let i = 0; i < 5; i++) {
const selectedWord = words[Math.floor(Math.random() * words.length)]
if (selectedWord.length <= 9) {
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
selectedWords.push(selectedWord)
}
}
let longestWord = selectedWords.reduce((a, b) => a.length < b.length ? b : a, "")
let charCount = longestWord.length
function makeRows(rowNum) {
for (let r = 0; r < rowNum; r++) {
let row = document.createElement("div");
gameArea.appendChild(row).className = "gridRow";
}
}
function makeColumns(cellNum) {
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < cellNum; j++) {
let newCell = document.createElement("div");
rows[j].appendChild(newCell).className = "cell";
}
}
}
function defaultGrid() {
makeRows(charCount);
makeColumns(charCount);
}
defaultGrid();
/* added a simple, minimal CSS reset to normalise all element defaults
to a similar layout-sizing method, and font-family: */
*,
::before,
::after {
box-sizing: border-box;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
/* added this, to help lay out the various elements more clearly: */
.content {
display: grid;
/* defining three rows, each of which is sized to the maximum size
needed to clearly display the content within: */
grid-template-rows: repeat(3, max-content);
/* setting a margin around the element on the block-axis, which is
perpendicular to the inline-axis, the inline-axis being the
direction of writing in the local language; so in left-to-right
languages this results in a top, and bottom, margin of 1em: */
margin-block: 1em;
/* setting a margin of auto on the inline-axis, the left and right
margins of the element in a left-to-right language: */
margin-inline: auto;
/* I retained the width, but removed the height constraint: */
width: 512px;
}
.score {
font-size: 24px;
text-align: right;
}
.wordBank {
border: 2.5px solid #000;
border-radius: 5px;
display: flex;
/* I left this part more ore less alone, other than adjusting
the font-size to an 'em' based sizing for responsive purposes: */
font-size: 1.6em;
/* added a minimum height, in order to allow room for the words
to move to the end within the space: */
min-height: 3em;
justify-content: space-between;
padding: 0.25em;
}
.wordBank span:nth-child(even) {
align-self: end;
}
.gameArea {
/* removing the spaces below/between each .gridRow element, which are caused by
the newline and whitespace characters between the .gridRow elements: */
font-size: 0;
/* placing the game area 'board' horizontally centered in the layout */
justify-self: center;
max-width: 100%;
}
.cell {
border: 1px solid black;
width: 50px;
/* resetting the font-size, so that text is visible once more (despite the parent
having a font-size of 0): */
font-size: 1rem;
height: 50px;
display: inline-block;
}
<div class="content" id="content">
<div class="gameArea" id="gameArea">
</div>
<div class="wordBank" id="wordBank">
</div>
<div class="score" id="score">
</div>
</div>
JS Fiddle demo.
Further to the question in the comments (below):
[...]one problem, why do only four words appear [in] certain instances?
This is a result of your loop, and its check:
// here, i is initialised to 0 (first iteration),
// the assessment is then executed; if it evaluates
// to true the loop runs an iteration, otherwise
// if the assessment returns false the loop stops;
// after the assessment i is incremented:
for (let i = 0; i < 5; i++) {
// selecting a random word:
const selectedWord = words[Math.floor(Math.random() * words.length)]
// testing the length of that random word:
if (selectedWord.length <= 9) {
// if the 'if' statement evaluates to true:
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
// adding the selectedWord to the selectedWords Array
selectedWords.push(selectedWord)
// if the 'if' statement evaluates to false nothing
// happens, the loop runs another iteration; this
// 'consumes' a loop but no word was added hence
// a smaller selectedWords Array
}
}
To guard against this, you could modify your loop:
let score = 0;
const scoreDisplay = document.getElementById("score");
const wordBank = document.getElementById("wordBank")
const gameArea = document.getElementById("gameArea")
const rows = document.getElementsByClassName("gridRow");
const cells = document.getElementsByClassName("cell");
const words = ['hello', 'thrifty', 'gaol', 'maester', 'mandible', 'osteoarthritic', 'venerable', 'the', 'cursive'];
let selectedWords = [];
scoreDisplay.innerHTML = "<p>Score: " + score;
// using a while() loop, and testing the length of the selectedWords Array, so that
// while the condition is true (and the Array-length is less than 5) the loop will
// continue running:
while (selectedWords.length < 5) {
// select random word:
const selectedWord = words[Math.floor(Math.random() * words.length)];
// test the length of that word is less than 9 characters:
if (selectedWord.length <= 9) {
// adding content to the wordBank element:
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
// pushing the word to the Array:
selectedWords.push(selectedWord);
// if no word is added to the Array, the length of the Array doesn't change
// and so the while loop will run again.
}
}
let longestWord = selectedWords.reduce((a, b) => a.length < b.length ? b : a, "")
let charCount = longestWord.length
function makeRows(rowNum) {
for (let r = 0; r < rowNum; r++) {
let row = document.createElement("div");
gameArea.appendChild(row).className = "gridRow";
}
}
function makeColumns(cellNum) {
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < cellNum; j++) {
let newCell = document.createElement("div");
rows[j].appendChild(newCell).className = "cell";
}
}
}
function defaultGrid() {
makeRows(charCount);
makeColumns(charCount);
}
defaultGrid();
*,
::before,
::after {
box-sizing: border-box;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.content {
display: grid;
grid-template-rows: repeat(3, max-content);
margin-block: 1em;
margin-inline: auto;
width: 512px;
}
.score {
font-size: 24px;
text-align: right;
}
.wordBank {
border: 2.5px solid #000;
border-radius: 5px;
display: flex;
font-size: 1.6em;
min-height: 3em;
justify-content: space-between;
padding: 0.25em;
}
.wordBank span:nth-child(even) {
align-self: end;
}
.gameArea {
font-size: 0;
justify-self: center;
max-width: 100%;
}
.cell {
border: 1px solid black;
width: 50px;
font-size: 1rem;
height: 50px;
display: inline-block;
}
<div class="content" id="content">
<div class="gameArea" id="gameArea">
</div>
<div class="wordBank" id="wordBank">
</div>
<div class="score" id="score">
</div>
</div>
JS Fiddle demo.
Note that there is an infinitesimally small chance that this may lead to an infinite loop – though to do so would require that every iteration of the while loop selects a random word longer than 9 characters in length – so it may be worth modifying further, to filter the Array and first remove all words with more than 9 characters:
// you didn't include your own Array, so I'm not sure how
// it's assigned; but you should be able to use
// Array.prototype.filter():
const words = ['hello', 'thrifty', 'gaol', 'maester', 'mandible', 'osteoarthritic', 'venerable', 'the', 'cursive']
// here we use an Arrow function to filter the words
// of the words Array:
.filter(
// passing in a reference to the current Array-element
// ('word') of the Array over which we're iterating;
// here we're testing that the length of the current
// word is less than 9; if so this assessment returns
// Boolean true, and the word is retained in the Array,
// otherwise it returns false and the word is discarded:
(word) => word.length < 9
);
// ...code omitted for brevity...
// again, using a while loop, to ensure that we
// have five Array-elements in the selectedWords
// Array:
while (selectedWords.length < 5) {
// no 'if' to check the length, as it's now
// unnecessary to do so:
const selectedWord = words[Math.floor(Math.random() * words.length)];
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
selectedWords.push(selectedWord);
}
let score = 0;
const scoreDisplay = document.getElementById("score");
const wordBank = document.getElementById("wordBank")
const gameArea = document.getElementById("gameArea")
const rows = document.getElementsByClassName("gridRow");
const cells = document.getElementsByClassName("cell");
const words = ['hello', 'thrifty', 'gaol', 'maester', 'mandible', 'osteoarthritic', 'venerable', 'the', 'cursive'];
let selectedWords = [];
scoreDisplay.innerHTML = "<p>Score: " + score;
// using a while() loop, and testing the length of the selectedWords Array, so that
// while the condition is true (and the Array-length is less than 5) the loop will
// continue running:
while (selectedWords.length < 5) {
// select random word:
const selectedWord = words[Math.floor(Math.random() * words.length)];
// test the length of that word is less than 9 characters:
if (selectedWord.length <= 9) {
// adding content to the wordBank element:
wordBank.innerHTML += "<span>" + selectedWord + "</span>"
// pushing the word to the Array:
selectedWords.push(selectedWord);
// if no word is added to the Array, the length of the Array doesn't change
// and so the while loop will run again.
}
}
let longestWord = selectedWords.reduce((a, b) => a.length < b.length ? b : a, "")
let charCount = longestWord.length
function makeRows(rowNum) {
for (let r = 0; r < rowNum; r++) {
let row = document.createElement("div");
gameArea.appendChild(row).className = "gridRow";
}
}
function makeColumns(cellNum) {
for (let i = 0; i < rows.length; i++) {
for (let j = 0; j < cellNum; j++) {
let newCell = document.createElement("div");
rows[j].appendChild(newCell).className = "cell";
}
}
}
function defaultGrid() {
makeRows(charCount);
makeColumns(charCount);
}
defaultGrid();
*,
::before,
::after {
box-sizing: border-box;
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
}
.content {
display: grid;
grid-template-rows: repeat(3, max-content);
margin-block: 1em;
margin-inline: auto;
width: 512px;
}
.score {
font-size: 24px;
text-align: right;
}
.wordBank {
border: 2.5px solid #000;
border-radius: 5px;
display: flex;
font-size: 1.6em;
min-height: 3em;
justify-content: space-between;
padding: 0.25em;
}
.wordBank span:nth-child(even) {
align-self: end;
}
.gameArea {
font-size: 0;
justify-self: center;
max-width: 100%;
}
.cell {
border: 1px solid black;
width: 50px;
font-size: 1rem;
height: 50px;
display: inline-block;
}
<div class="content" id="content">
<div class="gameArea" id="gameArea">
</div>
<div class="wordBank" id="wordBank">
</div>
<div class="score" id="score">
</div>
</div>
JS Fiddle demo.
You probably want to do something like this:
So, what I did was to place the .game-grid(the n by n grid) and .words-wrapper (the zig zag word cloud) in a .container. This .container is a flex that flows in a column. This shows the 2 items inside the .container one by one from top to bottom.
.game-grid itself is a grid. This lets you easily create a grid.
grid-template-colums: repeat(8, 1fr) tells the browser that this grid is going to have 8 columns (this you will have to control by the length of the longest word). I set the grid to have a fixed size and all the items inside have place-items: stretch which means they take all the available space, so they will all be equal size.
Hope this helps.
.container {
display: flex;
flex-direction: column;
width: 100%;
height: 100vh;
align-items: center;
}
.game-grid {
width: 50vh;
height: 50vh;
display: grid;
grid-template-columns: repeat(8, 1fr);
place-items: stretch;
place-content: stretch;
}
.game-grid-item {
border-width: 1px;
border-style: solid;
border-color: chocolate;
width: 100%;
height: 100%;
display: grid;
place-items: center;
}
.words-wrapper {
display: flex;
width: 100%;
justify-content: space-between;
height: 10vh;
border-width: 1px;
border-style: solid;
border-color: blueviolet;
}
.even {
align-self: flex-end;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div class="container">
<div class="game-grid">
<div class="game-grid-item">1</div>
<div class="game-grid-item">2</div>
<div class="game-grid-item">3</div>
<div class="game-grid-item">4</div>
<div class="game-grid-item">5</div>
<div class="game-grid-item">6</div>
<div class="game-grid-item">7</div>
<div class="game-grid-item">8</div>
<div class="game-grid-item">9</div>
<div class="game-grid-item">10</div>
<div class="game-grid-item">11</div>
<div class="game-grid-item">12</div>
<div class="game-grid-item">13</div>
<div class="game-grid-item">14</div>
<div class="game-grid-item">15</div>
<div class="game-grid-item">16</div>
<div class="game-grid-item">1</div>
<div class="game-grid-item">2</div>
<div class="game-grid-item">3</div>
<div class="game-grid-item">4</div>
<div class="game-grid-item">5</div>
<div class="game-grid-item">6</div>
<div class="game-grid-item">7</div>
<div class="game-grid-item">8</div>
<div class="game-grid-item">9</div>
<div class="game-grid-item">10</div>
<div class="game-grid-item">11</div>
<div class="game-grid-item">12</div>
<div class="game-grid-item">13</div>
<div class="game-grid-item">14</div>
<div class="game-grid-item">15</div>
<div class="game-grid-item">16</div>
<div class="game-grid-item">1</div>
<div class="game-grid-item">2</div>
<div class="game-grid-item">3</div>
<div class="game-grid-item">4</div>
<div class="game-grid-item">5</div>
<div class="game-grid-item">6</div>
<div class="game-grid-item">7</div>
<div class="game-grid-item">8</div>
<div class="game-grid-item">9</div>
<div class="game-grid-item">10</div>
<div class="game-grid-item">11</div>
<div class="game-grid-item">12</div>
<div class="game-grid-item">13</div>
<div class="game-grid-item">14</div>
<div class="game-grid-item">15</div>
<div class="game-grid-item">16</div>
<div class="game-grid-item">1</div>
<div class="game-grid-item">2</div>
<div class="game-grid-item">3</div>
<div class="game-grid-item">4</div>
<div class="game-grid-item">5</div>
<div class="game-grid-item">6</div>
<div class="game-grid-item">7</div>
<div class="game-grid-item">8</div>
<div class="game-grid-item">9</div>
<div class="game-grid-item">10</div>
<div class="game-grid-item">11</div>
<div class="game-grid-item">12</div>
<div class="game-grid-item">13</div>
<div class="game-grid-item">14</div>
<div class="game-grid-item">15</div>
<div class="game-grid-item">16</div>
</div>
<div class="words-wrapper">
<span class="item">multiply</span>
<span class="item even">step</span>
<span class="item">kiss</span>
<span class="item even">force</span>
<span class="item">ago</span>
</div>
</div>
</body>
</html>
I'm creating a card game where the user chooses 2 out of 3 cards. I then store those cards into an array and want to be able to print out the cards that were stored in the array with the actual image of the cards that the user chose.
I've tried looping through the array and then using innerHTML to push the results to a specific div but I keep getting "[object HTMLDivElement]". It also prints that out 3 times instead of 2 (since we are choosing 2 cards there should only be two elements to print out, I suspect the loop is running an extra time).
The below is the loop I have tried but I also am including a codepen for further clarity.
https://codepen.io/cramos2/pen/pMVjez
var holder = document.getElementById("cardResults");
for(var i=0; i < chosenCards.length; i++){
holder.innerHTML += "<p>" + chosenCards[i] + "</p><br>";
}
let chosenCards = new Array();
class tarot {
//constructor
constructor(cards) {
this.cardsArray = cards;
}
startReading() {
this.shuffleCards(this.cardsArray);
//call shuffle method
}
//Adds class "flipped" to the cards
flipCard(card, cards) {
if (this.canFlipCard(card)) {
if (chosenCards.length >= 2) {
console.log("removing1");
//from here
for (let card0 in cards) {
let list = card0.classList;
if (list) {
if (!list.contains('visible')) {
card0.removeEventListener('click', card0.fn);
}
}
}
} //to here
else if (!card.classList.contains('visible')) {
debugger;
card.classList.add('visible');
chosenCards.push(card);
console.log(chosenCards);
//this is where print out
var holder = document.getElementById("cardResults");
for (var i = 0; i < chosenCards.length; i++) {
holder.innerHTML += "<p>" + chosenCards[i] + "</p><br>";
}
card.removeEventListener('click', card.fn);
}
}
}
//Need a Shuffle method in here
shuffleCards(cardsArray) {
for (let i = cardsArray.length - 1; i > 0; i--) {
const randIndex = Math.floor(Math.random() * (i + 1));
[cardsArray[i], cardsArray[randIndex]] = [cardsArray[randIndex], cardsArray[i]];
}
cardsArray = cardsArray.map((card, index) => {
card.style.order = index;
});
}
//gets the card
getCardType(card) {
return card.getElementsByClassName('card-value')[0].src;
}
//returns card
canFlipCard(card) {
return card
}
}
//this will call the reading to start when page is loaded
if (document.readyState == 'loading') {
document.addEventListener('DOMContentLoaded', ready)
} else {
ready()
}
function ready() {
//declares card's' & sets it to the card class in HTML
let cards = Array.from(document.getElementsByClassName('card'));
//creates new instance of tarot class
let tarotReading = new tarot(cards);
let over = Array.from(document.getElementsByClassName('over'));
over.forEach(overlay => {
overlay.addEventListener('click', () => {
overlay.classList.remove('visible');
tarotReading.startReading();
});
});
//flips the cards
cards.forEach(card => {
card.addEventListener('click', card.fn = function clicked() {
tarotReading.flipCard(card, cards);
//remove cards that dont have visible tag
});
})
console.log(chosenCards[0]);
}
h1 {
color: #7B68EE;
padding left: 50px;
padding right: 50px;
padding-top: 5px;
text-align: center;
}
.container {
display: grid;
grid-template-columns: repeat(6, auto);
grid-gap: 10px;
margin: 50px;
justify-content: center;
perspective: 500px;
}
.card {
position: relative;
height: 175px;
width: 125px;
}
.card-face {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
backface-visibility: hidden;
border-radius: 12px;
border-width: 1px;
border-style: solid;
transition: transform 500ms ease-in-out;
}
.card.visible .card-back {
transform: rotateY(-180deg);
}
.card.visible .card-front {
transform: rotateY(0)
}
.card-back {
background-color: black;
border-color: white;
color: white;
}
.card-front {
background-color: black;
border-color: white;
color: white;
transform: rotateY(180deg);
}
<body>
<h1>Tarot</h1>
<div class="container">
<div class="card">
<div class="card-back card-face card1" id="card1">
<p> 1
<p>
</div>
<div class="card-front card-face">
<p> The Hermit
<p>
</div>
</div>
<div class="card">
<div class="card-back card-face card2">
2
</div>
<div class="card-front card-face">
The Fool
</div>
</div>
<div class="card">
<div class="card-back card-face card3">
3
</div>
<div class="card-front card-face">
The Empress
</div>
</div>
</div>
<button type="button" class="over container">Shuffle</button>
</div>
<hr>
<div id="cardResults">
</div>
</body>
The expected result would be the flipped over card with the text (not the number of the card) that the user has chosen.
You could try something like this:
for(var i=0; i < chosenCards.length; i++){
holder.appendChild(chosenCards[i].cloneNode(true));
}
I'm trying to make a Single Page Application with pure JavaScript (no additional frameworks or libraries). The problem is that the values I add to the TODO list are not storing in the localStorage (and are not showing).
I would appreciate any help with that task.
How can I simplify the code? (without using any additional libraries and frameworks (ex.jquery etc.))
Here is my code:
let inputTask = document.getElementById('toDoEl');
let editTask = document.getElementById('editTask');
let checkTask = document.getElementById('list');
let emptyList = document.getElementById('emptyList');
let items = [];
let id = [];
let labelToEdit = null;
const empty = 0;
let pages = ['index', 'add', 'modify'];
load();
function load() {
items = loadFromLocalStorage();
id = getNextId();
items.forEach(item => renderItem(item));
}
function show(shown) {
location.href = '#' + shown;
pages.forEach(function(page) {
document.getElementById(page).style.display = 'none';
});
document.getElementById(shown).style.display = 'block';
return false;
}
function getNextId() {
for (let i = 0; i<items.length; i++) {
let item = items[i];
if (item.id > id) {
id = item.id;
}
}
id++;
return id;
}
function loadFromLocalStorage() {
let localStorageItems = localStorage.getItem('items');
if (localStorageItems === null) {
return [];
}
return JSON.parse(localStorageItems);
}
function saveToLocalStorage() {
localStorage.setItem('items', JSON.stringify(items));
}
function setChecked(checkbox, isDone) {
if (isDone) {
checkbox.classList.add('checked');
checkbox.src = 'https://image.ibb.co/b1WeN9/done_s.png';
let newPosition = checkTask.childElementCount - 1;
let listItem = checkbox.parentNode;
listItem.classList.add('checked');
checkTask.removeChild(listItem);
checkTask.appendChild(listItem);
} else {
checkbox.classList.remove('checked');
checkbox.src = 'https://image.ibb.co/nqRqUp/todo_s.png';
let listItem = checkbox.parentNode;
listItem.classList.remove('checked');
}
}
function renderItem(item) {
let listItem = document.getElementById('item_template').cloneNode(true);
listItem.style.display = 'block';
listItem.setAttribute('data-id', item.id);
let label = listItem.querySelector('label');
label.innerText = item.description;
let checkbox = listItem.querySelector('input');
checkTask.appendChild(listItem);
setChecked(checkbox, item.isDone);
emptyList.style.display = 'none';
return listItem;
}
function createNewElement(task, isDone) {
let item = { isDone, id: id++, description: task };
items.push(item);
saveToLocalStorage();
renderItem(item);
}
function addTask() {
if (inputTask.value) {
createNewElement(inputTask.value, false);
inputTask.value = '';
show('index');
}
}
function modifyTask() {
if (editTask.value) {
let item = findItem(labelToEdit);
item.description = editTask.value;
labelToEdit.innerText = editTask.value;
saveToLocalStorage();
show('index');
}
}
function findItem(child) {
let listItem = child.parentNode;
let id = listItem.getAttribute('data-id');
id = parseInt(id);
let item = items.find(item => item.id === id);
return item;
}
// Chanhe img to checked
function modifyItem(label) {
labelToEdit = label;
editTask.value = label.innerText;
show('modify');
editTask.focus();
editTask.select();
}
function checkItem(checkbox) {
let item = findItem(checkbox);
if (item === null) {
return;
}
item.isDone = !item.isDone;
saveToLocalStorage();
setChecked(checkbox, item.isDone);
}
function deleteItem(input) {
let listItem = input.parentNode;
let id = listItem.getAttribute('data-id');
id= parseInt(id);
for (let i in items) {
if (items[i].id === id) {
items.splice(i, 1);
break;
}
}
if (items.length === empty) {
emptyList.style.display = 'block';
}
saveToLocalStorage();
listItem.parentNode.removeChild(listItem);
}
* {
box-sizing: border-box;
}
body {
font-family: sans-serif;
}
h2, li, #notification {
text-align: center;
}
h2 {
font-weight: normal;
margin: 0 auto;
padding-top: 20px;
padding-bottom: 20px;
}
#root {
width: 400px;
height: 550px;
margin: 0 auto;
position: relative;
}
#root>ul {
display: block;
}
#addButton {
display: block;
margin: 0 auto;
}
.checkbox, .delete {
height: 24px;
bottom: 0;
}
.checkbox {
float: left;
}
.delete {
float: right;
}
ul {
margin: 20px 30px 0 30px;
padding-top: 20px;
padding-left: 20px;
text-align: center;
}
#toDoEl {
width: 50%;
}
li {
width: 100%;
list-style: none;
box-sizing: border-box;
display: flex;
justify-content: space-between;
align-items: center;
margin: 15px auto;
}
label {
margin: 0 auto;
text-align: justify;
text-justify: inter-word;
}
label:hover {
cursor: auto;
}
li.checked {
background-color: gray;
}
span.button {
cursor: pointer;
}
#add, #modify {
display: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Homework 12 - Simple TODO List</title>
<link rel="stylesheet" href="./assets/styles.css">
</head>
<body>
<div id="root">
<!--Main page-->
<div id="index">
<h2>Simple TODO Application</h2>
<button class="button" id="addButton" onclick="show('add')">Add New Task</button>
<p id="emptyList">TODO is empty</p>
<ul id="list">
<li id="item_template" style="display: none">
<input class="checkbox" type="image" alt="checkbox" src="https://image.ibb.co/nqRqUp/todo_s.png" onclick="checkItem(this)">
<label onclick="modifyItem(this)"></label>
<input id="delete" class="delete" type="image" alt="remove" src="https://image.ibb.co/dpmqUp/remove_s.jpg" onclick="deleteItem(this)">
</li>
</ul>
</div>
<!--Add page-->
<div id="add">
<h2>Add Task</h2>
<input type="text" id="toDoEl">
<button class="button cancel" onclick="show('index')">Cancel</button>
<button class="button save" onclick="addTask()">Save changes</button>
</div>
<!--Modify page-->
<div id="modify">
<h2>Modify item</h2>
<input type="text" id="editTask">
<button class="button cancel" onclick="show('index')">Cancel</button>
<button class="button save" onclick="modifyTask()">Save changes</button>
</div>
</div>
<script src="./src/app.js"></script>
</body>
</html>
Your code does appear to work. If you console.log(JSON.parse(localStorageItems)) right above line 49 in the loadFromLocalStorage function, it shows as expected in the console. Also, upon refreshing the items persist.
If what you mean is that you're checking localStorage and you don't see the items, it might be that you're looking at the preview version of localStorage. (I'm assuming you're using Chrome.) Hover over the top of the empty section and pull down, this should reveal the values stored. If you click on one, it should show in the preview section. I think this was a Chrome dev tools UI change recently implemented.
I checked your code in Codepen and it works.