I am currently working on a web project that has some incrementation animation and I've created a counter function like this :
const counters = document.querySelectorAll('.counter');
counters.forEach(counter => {
const updateCount = () => {
const target = +counter.getAttribute('data-target');
const count = +counter.innerText;
const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up
if (count < target) {
counter.innerText = count + speed;
setTimeout(updateCount,1);
} else {
counter.innerText = target;
}
}
updateCount();
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html , body {
width: 100%;height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
/* HEADER */
.counter {
font-size: 20px;
font-weight: bold;
padding: 5px;
}
<span data-target="20" class="counter">0</span>
<span data-target="100" class="counter">0</span>
<span data-target="50" class="counter">0</span>
<span data-target="250000" class="counter">0</span>
The problem is the number display in the web is decimal. I've tried math.ceil,math.floor, ... But they seem to mess up the speed of the counter . Is there a way of display only the integer part WITHOUT
messing with the speed ?
You need to pass the incremented value to the function itself instead of reading the printed value.
See the comments on the code.
const counters = document.querySelectorAll('.counter');
counters.forEach(counter => {
/* expecting a parameter */
const updateCount = (count) => {
const target = +counter.getAttribute('data-target');
/* at the first function call without argument
* read the innerText value, otherwise read it from
* the argument (change const with var)
*/
var count = count || +counter.innerText;
const speed = target / 1000;
if (count < target) {
/* print the value with parseInt() so you keep
* the integer part only
*/
counter.innerText = parseInt(count + speed, 10);
/* call the function passing the original sum of
* count + speed
*/
setTimeout(function() {
updateCount(count+speed)
}, 1);
}
else {
/* use parseInt() */
counter.innerText = parseInt(target, 10);
}
}
updateCount();
});
.counter {
font-size: 20px;
font-weight: bold;
padding: 5px;
display: block;
}
<span data-target="20" class="counter">0</span>
<span data-target="100" class="counter">0</span>
<span data-target="50" class="counter">0</span>
<span data-target="250000" class="counter">0</span>
As a side note, you can also pass a second target argument to the function, instead of continuously access the DOM to read it, exactly as I've done for count, since it doesn't change.
The same value is being used for the calculation and displaying the value so if you round it off you lose the precision. If on the first iteration it does 1.3+3=4.3 on the next iteration the 4.3 will be rounded up and the sum would be 5+3. That's what's making the speed seem off.
Adding another hidden attribute to the span (count-value in the code below) gives you a way to store the whole number with all the decimals to use in the calculations and then you can display the number without decimals to the users without it throwing off the sum.
const counters = document.querySelectorAll('.counter');
counters.forEach(counter => {
const updateCount = () => {
const target =+counter.getAttribute('data-target');
//update count with the full value
const count =+counter.getAttribute('count-value');
const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up
if (count < target) {
//set the innertext to the rounded up value
counter.innerText = Math.ceil(count + speed);
//update the hidden attribute with the full value
counter.setAttribute('count-value', count + speed);
setTimeout(updateCount,1);
} else {
counter.innerText = target;
}
}
updateCount();
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html , body {
width: 100%;height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
/* HEADER */
.counter {
font-size: 20px;
font-weight: bold;
padding: 5px;
}
<span data-target="20" count-value="0" class="counter">0</span>
<span data-target="100" count-value="0" class="counter">0</span>
<span data-target="50" count-value="0" class="counter">0</span>
<span data-target="250000" count-value="0" class="counter">0</span>
This might help you : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed
example with an extra data-attribute:
const counters = document.querySelectorAll('.counter');
counters.forEach(counter => {
const updateCount = () => {
const target = +counter.getAttribute('data-target');
const count = +counter.getAttribute('data-count');
const speed = target / 1000; //this will not work accurate if using Math.ceil or floor to round number up
if (count < target) {
counter.setAttribute('data-count', count + speed);
let stripped = (count + speed).toFixed(0);
counter.innerText = stripped
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
}
updateCount();
});
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
}
/* HEADER */
.counter {
font-size: 20px;
font-weight: bold;
padding: 5px;
}
<span data-target="20" class="counter">0</span>
<span data-target="100" data-count="0" class="counter">0</span>
<span data-target="50" data-count="0" class="counter">0</span>
<span data-target="250000" data-count="0" class="counter">0</span>
Related
I'm going through The Odin Project and I'm working on completing Tic Tac Toe. I have almost everything completed but I'm stuck with coming up with the logic that checks what symbol (X or O) is being stored in each board and how to check for a winner. The directions say to store everything inside an array so I was doing that but I can't figure out how to update the array to store whichever symbol and check for a winner.
You’re going to store the gameboard as an array inside of a Gameboard object, so start there! Your players are also going to be stored in objects… and you’re probably going to want an object to control the flow of the game itself.
This is what I have completed so far.
I have a player function that stores each players name and the symbol that represents them.
const player = (name, symbol) => {
return { name, symbol };
};
const Player1 = player("Player X", "X");
const Player2 = player("Player O", "O");
I have a for loop that loops through my main div and creates 9 divs that represent the 9 squares for the game.
const createGameBoard = function () {
for (i = 0; i < 9; i++) {
gameBoardElements(); // Function that appends div to main board class
}
};
createGameBoard();
This is the function I'm using when I click on one of the divs. At the top I have the variable turn = 0 and after every click, it goes up by one. Using the modulus operator and my player function I'm making the text inside the board div either X or O and I'm changing the class so the div has a different background color. I know I could use the style feature but I don't like changing my html too much.
const board = document.querySelectorAll(".game-board");
board.forEach((el) => el.addEventListener("click", selectBoard));
// Decides which symbol to add to board: X or O
function selectBoard(e) {
e.target.appendChild(
createPElement(`${turn % 2 === 0 ? Player1.symbol : Player2.symbol}`)
);
turn++;
e.target.className = "complete-board";
e.target.removeEventListener("click", selectBoard); // prevent additional clicks
}
What I've tried
I've tried creating the array myGameBoard and appending each div to it inside the createGameBoard function
let myGameBoard = [
{board: 1, value: ""}
{board: 2, value: ""}
etc...
];
But I couldn't find a way to update the value of each div after I click to make it either an X or an O.
const currentTurn = document.getElementById("current-turn");
const getGameBoard = document.getElementById("board");
const btn = document.getElementById("btn");
let turn = 0;
// Main Player function. Gives default name and symbol
const player = (name, symbol) => {
return { name, symbol };
};
const Player1 = player("Player X", "X");
const Player2 = player("Player O", "O");
console.log(Player1);
// Creates a div and appends to board div
const gameBoardElements = function () {
const getBoard = document.getElementById("board");
const createBoard = document.createElement("div");
getBoard.appendChild(createBoard);
createBoard.className = "game-board";
};
// For loop that creates 9 divs and adds to board div
const createGameBoard = function () {
for (i = 0; i < 9; i++) {
gameBoardElements(); // Function that appends div to main board class
}
};
createGameBoard();
// Creates the [p] element that displays either X or O inside the gameboard div
function createPElement(symbol) {
let p = document.createElement("p");
p.textContent = symbol;
p.className = "text";
return p;
}
// Selects all game boards and assigns two event listener functions
const board = document.querySelectorAll(".game-board");
board.forEach((el) => el.addEventListener("click", selectBoard));
board.forEach((el) => el.addEventListener("click", updateBoard));
// Decides which symbol to add to board: X or O
function selectBoard(e) {
e.target.appendChild(
createPElement(`${turn % 2 === 0 ? Player1.symbol : Player2.symbol}`)
);
turn++;
e.target.className = "complete-board";
e.target.removeEventListener("click", selectBoard); // prevent additional clicks
}
// Updates the [p] tag that shows whose turn it is at the top
function updateBoard() {
currentTurn.textContent = `${
turn % 2 === 0 ? `${Player1.name} turn` : `${Player2.name} turn`
}`;
}
// Reset button text display
btn.addEventListener("mouseover", function () {
this.textContent = "Reset >>";
});
btn.addEventListener("mouseout", function () {
this.textContent = "Reset >";
});
body {
background-color: #fffdfa;
}
.title {
font-size: 42px;
font-family: "Dancing Script", cursive;
}
.current-turn {
font-size: 24px;
}
.content {
background-color: #fffdfa;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 700px;
width: 800px;
margin: 0 auto;
border-radius: 8px;
}
.board {
display: flex;
flex-flow: row wrap;
align-content: center;
justify-content: space-evenly;
gap: 10px;
background-color: #fff;
height: 500px;
width: 600px;
}
.board div,
.complete-board {
display: flex;
justify-content: center;
align-items: center;
border: 2px solid #000;
background-color: #3c4048;
width: 175px;
height: 150px;
}
.board div:hover {
background-color: #f4e06d;
transform: scale(1.1);
transition: 0.8s;
box-shadow: 5px 5px 0 rgba(0, 0, 0, 0.5);
}
.complete-board {
background-color: #f4e06d !important;
}
.text {
font-size: 64px;
}
.btn {
display: inline-block;
background-color: #4649ff;
padding: 0.5em 2em;
margin-top: 20px;
cursor: pointer;
font-family: "Poor Story", cursive;
font-size: 2rem;
letter-spacing: 0.4rem;
transition: all 0.3s;
text-decoration: none;
}
.btn:hover {
box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.5);
}
.parallelogram {
transform: skew(-20deg);
}
.skew-fix {
display: inline-block;
transform: skew(20deg);
}
<div class="content">
<p class="title">Tic Tac Toe</p>
<p class="current-turn" id="current-turn">Player X Turn</p>
<div id="board" class="board"></div>
<a id="btn" class="btn parallelogram" onclick="reset()">
<span class="skew-fix">Reset ></span>
</a>
</div>
The winning logic will include checking the element if
it is any one of the corner elements or if it is the element at the center.
it is any of the other elements.
If it is case 1, you will check all horizontal/vertical/diagonal elements if they are the same.
If it is case 2, you will check for all horizontal/vertical elements if they are the same.
If any of the above two statements are true, then you have won the game.
I hope I gave you some hint or logic.
I had to animate the rotation using vanila JS and encountered the problem with Firefox. I used setInterval() to animate each animation step and then clearInteval() to stop the animation. In this example I animated the rotation of an element. It works fine in Chrome, but doesn't quite finish the animation in Firefox, as if Firefox takes longer to process each step. I created an example, demonstrating this behaviour.
const circle = document.getElementById('circle')
const angle = document.getElementById('angle')
const degToMove = 90 //rotate 90 degrees
const animStepLength = 10 // 10ms is one animation step
const animLength = 300 //whole animation length -> 30 animation steps -> 3deg rotation per step
const rotateCircle = () => {
rotInterval = setInterval(()=>{
//what rotation value currently is
let currentVal = circle.style.transform.match(/[-+]?\d+/);
//add 3 deg rotation per step to it
circle.style.transform = `rotate(${+currentVal[0] + (degToMove*animStepLength) / animLength}deg)`
//text output
angle.innerHTML = `${+currentVal[0] + (degToMove*animStepLength) / animLength} deg`
}, animStepLength)
setTimeout(() => {
//after all steps are done clear the interval
clearInterval(rotInterval)
}, animLength);
}
circle.addEventListener('click', rotateCircle)
body{
display: flex;
align-items: center;
justify-content: center;
margin: 0;
height: 100vh;
font-family: sans-serif;
}
#circle{
border-radius: 50%;
background-color: skyblue;
width: 100px;
height: 100px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
<div id="circle" style='transform: rotate(0deg)'>
<span>
Click to rotate
</span>
<span id="angle">0 deg</span>
</div>
Also available as jsfiddle
While Chrome rotates to 90 -> 180 -> 270 -> 360 ...
Firefox goes to 57 -> 114 -> 171 -> 228 -> ... in this particular example.
Well, this is basically +1 rad increase, but it has to do something with the selected values for animLength and animStepLength. If I select them differently, Firefox shows different values.
The simple CSS animation would work here but there are reasons for me to use JS here.
You can never guarantee that a setTimeout or setInterval handler will be called when you ask it to. You should always compare the current time against the initial time to figure out what kind of progress your animation should show, usually by figuring out what percentage of the animation has elapsed. Look for the elapsedPercentage variable in the example below.
Using setInterval is considered bad practice because of that reason. The suggested way to animate is to use nested requestAnimationFrame;
The script below can use lots of improvements but it does show you how to properly update your animation based on how long it's been since the animation started.
const circle = document.getElementById('circle');
const angle = document.getElementById('angle');
const degToMove = 90; //rotate 90 degrees
let rotationStartTime = null;
let targetRotation = 0;
let rotationStart = 0
let animationTime = 3000;
function startRotating() {
rotationStartTime = new Date().getTime();
rotationStart = parseInt(circle.style.transform.match(/[-+]?\d+/)[0]);
targetRotation += degToMove;
rotateCircle();
}
function rotateCircle() {
const currentVal = parseInt(circle.style.transform.match(/[-+]?\d+/)[0]);
const currentTime = new Date().getTime();
const elapsedPercentage = (currentTime - rotationStartTime) / animationTime;
let newVal = Math.min(targetRotation, Math.round(rotationStart + (elapsedPercentage * degToMove)));
circle.style.transform = `rotate(${newVal}deg)`;
//text output
angle.innerHTML = `${newVal} deg`;
if (newVal < targetRotation) {
window.requestAnimationFrame(rotateCircle);
}
}
circle.addEventListener('click', startRotating)
body{
display: flex;
align-items: center;
justify-content: center;
margin: 0;
height: 100vh;
font-family: sans-serif;
}
#circle{
border-radius: 50%;
background-color: skyblue;
width: 100px;
height: 100px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
}
<div id="circle" style='transform: rotate(0deg)'>
<span>
Click to rotate
</span>
<span id="angle">0 deg</span>
</div>
I just wanna run a function when my height of my div reaches the bottom of the page on scroll? Is their any way to do that using JavaScript?
The most contemporary JS solution is to use an intersection observer. (MDN). It is less resource intensive than older solutions that listen to the scroll event. With the intersection observer you can decide exactly where you want the function to trigger and you can reuse it like this example shows:
class CountUp {
constructor(triggerEl, counterEl) {
const counter = document.querySelector(counterEl)
const trigger = document.querySelector(triggerEl)
let num = 0
const countUp = () => {
if (num <= counter.dataset.stop)
++num
counter.textContent = num
}
const observer = new IntersectionObserver((el) => {
if (el[0].isIntersecting) {
const interval = setInterval(() => {
(num < counter.dataset.stop) ? countUp() : clearInterval(interval)
}, counter.dataset.speed)
}
}, { threshold: [0] })
observer.observe(trigger)
}
}
// Initialize any number of counters:
new CountUp('#start1', '#counter1')
new CountUp('#start2', '#counter2')
p {
font-weight: bold;
text-align: center;
margin-top: 5%;
font-size: 2rem;
color: green;
}
.start-counter {
text-align: center;
padding: 0 0 5rem;
}
.counter {
text-align: center;
/* transition: .1s ease; */
font-size: 14rem;
color: red;
font-weight: bold;
font-family: sans-serif;
padding: 0 0 30rem;
}
<p>Scroll down ↓</p>
<div style="height:100vh;"></div>
<div id="start1" class="start-counter">Counter starts counting here!</div>
<div id="counter1" class="counter" data-stop="199" data-speed="20">0</div>
<p>Scroll down ↓</p>
<div style="height:100vh;"></div>
<div id="start2" class="start-counter">Counter starts counting here!</div>
<div id="counter2" class="counter" data-stop="300" data-speed="20">0</div>
Trying to get all four counters to finish at the same time.
e.g.
Counter four should stay at zero until counter three reaches value of 14, then switch to 1.
Counter one should increment by following counter two and three.
https://jsfiddle.net/IhaveVoicesinMyhead/0j9q1b85/2/#&togetherjs=f3xchiJM9f
const counter = document.querySelectorAll('.counter');
const speed = 200;
counter.forEach(counter => {
const updateCount = () => {
const target = +counter.getAttribute('data-target');
const count = +counter.innerText;
const increment = target / speed;
if (count < target) {
counter.innerText = Math.ceil(count + increment);
setTimeout(updateCount, 80);
} else {
count.innerText = target;
}
}
updateCount();
});
.counters {
margin-top: 2em;
}
.counters .container-max-width {
display: flex;
position: relative;
flex-flow: row nowrap;
justify-content: center;
align-items: stretch;
background-color: #fff;
font-size: 1rem;
text-transform: uppercase;
}
.counters .container-max-width .counter {
font-size: 3.25em;
}
.counters .container-max-width span {
color: #848484;
padding: 0;
}
.counters .container-inner-width {
display: grid;
grid-gap: 200px;
grid-template-columns: repeat(4, 1fr);
padding: 85px 0 85px 0;
text-align: center;
}
<section class="counters">
<div class="container-max-width">
<div class="container-inner-width">
<div>
<div class="counter counter-one" data-target="4">0</div>
<span>counter one</span>
</div>
<div>
<div class="counter counter-two" data-target="10">0</div>
<span>counter two</span>
</div>
<div>
<div class="counter counter-three" data-target="15">0</div>
<span>counter three</span>
</div>
<div>
<div class="counter counter-four" data-target="1">0</div>
<span>counter four</span>
</div>
</div>
</div>
</section>
Here's a simple example of how you can create multiple timers that all end at the same time. I separated the logic behind it (countUp()) from the view (createTimer()), so you should be able to adapt the countUp() function to whatever needs you have. Just give it your desired final value, how long you want it to execute (in ms), and what you want it to do at each step.
const counter1 = document.getElementById('counter1')
const counter2 = document.getElementById('counter1')
function countUp({ to, onStep, duration, _currentValue = 0 }) {
onStep(_currentValue)
if (_currentValue === to) return
setTimeout(() => {
countUp({ to, onStep,duration, _currentValue: _currentValue + 1 })
}, duration / to)
}
function createTimer(to) {
const timerEl = document.createElement('div')
document.body.appendChild(timerEl)
const onStep = currentValue => {
timerEl.innerText = currentValue
}
countUp({ to, duration: 3000, onStep })
}
createTimer(1)
createTimer(10)
createTimer(20)
Note that this doesn't account for setTimeout() drift (setTimeout doesn't always execute the callback as soon as the you asked it to), so you shouldn't use this solution for longer-lasting or very precise timers. You can add some more math to it if you want to account for drift.
I want to dynamically append a child to its parent multiple times when I click the button.
let btn = document.querySelector('.btn');
let starContainer = document.querySelector('.star__container');
let starWin = document.createElement('div');
starWin.classList.add('star__win');
starWin.innerText = 'Test';
btn.addEventListener('click',addItem);
function addItem(){
starContainer.appendChild(starWin);
}
<div class="star__container"></div>
<button class='btn'>Click</button>
You need to create your starWin element each time the addItem method is called. Now, you append the same element several times. It won't be cloned.
let btn = document.querySelector('.btn');
let starContainer = document.querySelector('.star__container');
btn.addEventListener('click', addItem);
function addItem() {
starContainer.appendChild(createElement());
}
function createElement() {
let starWin = document.createElement('div');
starWin.classList.add('star__win');
starWin.innerText = 'Test';
return starWin;
}
<div class="star__container"></div>
<button class='btn'>Click</button>
<div class="star__container"></div>
<button class='btn'>Click</button>
let btn = document.querySelector('.btn');
let starContainer = document.querySelector('.star__container');
btn.addEventListener('click',addItem);
function addItem(){
let starWin = document.createElement('div');
starWin.className = 'star__win';
starContainer.appendChild(starWin);
}
Update
Issue
Expectation: A <div> should to be appended to DOM for each click of a button.
Result: The first click of the button appends a <div> to DOM, but thereafter any further clicking of said button elicits nothing.
Diagnosis: All code concerning the creation of <div> is not within a function, the only time it will run is at page load. When the handler function is triggered by a button click, it finds that <div> that was made at page load and successfully appends the <div> to the DOM. When user clicks the button again, nothing happens because the <div> was made only once.
Solution: Place all of the aforementioned code in the handler function addItem()
Demo 1
let btn = document.querySelector('.starBtn');
btn.addEventListener('click', addItem);
function addItem(event) {
const box = document.querySelector('.starBox');
let star = document.createElement('b');
star.classList.add('stellar');
star.innerText = '⭐';
box.appendChild(star);
}
body {
font-size: 3rem
}
.starBox {
word-wrap: break-word;
}
.starBtn {
position: fixed;
top: 0;
left: 0;
font: inherit;
}
<article class="starBox"></article>
<button class='starBtn'>🤩</button>
Not sure what the problem is, don't care really. If you take a look at this demo it'll help with whatever issue you may have. Details are commented line-by-line in the demo. Apologies in advance -- I'm bored ...
🥱
I'll come back and post a decent answer later. Review the demo in Full Page mode.
Demo 2
// Reference the <form>
const STARS = document.forms.starFactory;
// Define the counter
let s = 0;
// Register the form to the click event
STARS.onclick = makeStar;
// Handler function passes event object
function makeStar(event) {
// Define an array of characters✱
let galaxy = ['★', '☆', '✨', '✩', '✪', '⚝', '✫', '✭', '✯', '✰', '✴', '⭐', '🌟', '🌠', '💫', '🟊', '🤩'];
/*
- "this" is the form
- Collect all <input>, <button>, <output>, etc into a
NodeList
*/
const field = this.elements;
/*
- event.target is always the tag the user interacted with
- In this case it's the <button> because this handler
will not accept but that <button>
*/
const clicked = event.target;
/*
- The NodeList `field` can reference form tags by
suffixing the tag's #id or [name]
- The <fieldset> and <output> are referenced
*/
const jar = field.starJar;
const cnt = field.count;
/*
- By using a simple `if` condition strategically we can
control what and how tags behave when a registered
event.
- The remainder of the handler is explained at the very
end.
*/
if (clicked.id === 'STARt') {
s++;
const star = document.createElement('S');
let index = Math.floor(Math.random() * galaxy.length);
let ico = galaxy[index];
star.textContent = ico;
star.className = 'star';
star.style.zIndex = s;
star.style.left = Math.floor(Math.random() * 85) + 1 +'%';
star.style.bottom = Math.floor(Math.random() * 90) + 1 + '%';
jar.appendChild(star);
cnt.value = s;
}
}
/*
- increment `s` by one
- create a <s>trikethrough tag (aka <s>tar tag JK)
- generate a random number in the range of 0 to 15
- get a character from galaxy Array at the index number
determined from the previous step.
- render the text character in the <s>tar
- assign the class .star to <s>tar
- assign `z-index` to <s>tar (Note: it increases every
click which insures that tags won't collide)
- randomly assign `left` in the range of 1 to 85 to <s>tar
- randomly assign `bottom` in the range of 1 to 90 to
<s>tar
- append <s>tar to #starJar
- increment #count value
*/
:root,
body {
font: 400 5vw/1 Verdana;
background: #123;
}
#starFactory {
display: flex;
justify-content: center;
align-items: center;
}
#starJar {
position: relative;
display: flex;
flex-flow: row wrap;
justify-content: end;
width: 50vw;
height: 50vw;
border: 5px inset rgba(255,255,0,0.3);
border-bottom-left-radius: 12vw;
border-bottom-right-radius: 12vw;
color: gold;
}
legend {
position: relative;
z-index: 5150;
width: max-content;
font-size: 1.5rem;
color: goldenrod;
}
#STARt {
position: relative;
z-index: 5150;
font-size: 1.5rem;
background: none;
padding: 0;
border: 0;
cursor: pointer;
}
#count {
position: relative;
z-index: 9999;
font-size: 1.25rem;
width: 5vw;
overflow-x: visible;
color: cyan;
}
s.star {
position: absolute;
text-decoration: none;
font-size: 1.5rem;
background: none;
padding: 0;
margin: 0;
border: 0;
}
<form id='starFactory'>
<fieldset id="starJar">
<legend>starJar
<button id='STARt' type='button'>
✴️ <output id='count'></output>
</button>
</legend>
</fieldset>
</form>