How to keep button disabled even if user refreshes the page? Javascript - javascript

I have this little app that generates numbers between 1 and 90. I do it as I'm practicing with my early JS skills.
It's written in italian so for better understanding, well the title doesn't matter that much, it just says "wanna try your luck?" bla-bla.
Then I set a timeout that disables the "Genera Numeri" button for 1 hour after it's clicked (I set it to 3 seconds for the post), and a message pops up ("You can use it once every hour") but if you refresh the page, of course, you can click it again.
I was wondering, how can I make it that even if I refresh, the countdown would be still going?
I read another similar question where they were talking about Mongo (which I have no clue what it is, I suppose back end (?) ), and they mentioned the use of cookies to do that, which in my inexperienced eyes, could make sense.
Anyways, HERE the Javascript
let button = document.querySelector('.button')
let clear = document.querySelector('.clear')
let message = document.querySelector('.message')
let first = document.querySelector('.first')
let second = document.querySelector('.second')
let third = document.querySelector('.third')
let fourth = document.querySelector('.fourth')
let fifth = document.querySelector('.fifth')
let sixth = document.querySelector('.sixth')
button.addEventListener('click', ()=> {
first.innerText = Math.floor(Math.random() * (90 - 1) + 1)
second.innerText = Math.floor(Math.random() * (90 - 1) + 1)
third.innerText = Math.floor(Math.random() * (90 - 1) + 1)
fourth.innerText = Math.floor(Math.random() * (90 - 1) + 1)
fifth.innerText = Math.floor(Math.random() * (90 - 1) + 1)
sixth.innerText = Math.floor(Math.random() * (90 - 1) + 1)
message.classList.add('show')
button.disabled = true
setTimeout(function(){
button.disabled = false
message.classList.remove('show')
}, 1000 * 3)
})
clear.addEventListener('click', ()=> {
first.innerText = ''
second.innerText = ''
third.innerText = ''
fourth.innerText = ''
fifth.innerText = ''
sixth.innerText = ''
})
And HERE the snippet
let button = document.querySelector('.button')
let clear = document.querySelector('.clear')
let message = document.querySelector('.message')
let first = document.querySelector('.first')
let second = document.querySelector('.second')
let third = document.querySelector('.third')
let fourth = document.querySelector('.fourth')
let fifth = document.querySelector('.fifth')
let sixth = document.querySelector('.sixth')
button.addEventListener('click', ()=> {
first.innerText = Math.floor(Math.random() * (90 - 1) + 1)
second.innerText = Math.floor(Math.random() * (90 - 1) + 1)
third.innerText = Math.floor(Math.random() * (90 - 1) + 1)
fourth.innerText = Math.floor(Math.random() * (90 - 1) + 1)
fifth.innerText = Math.floor(Math.random() * (90 - 1) + 1)
sixth.innerText = Math.floor(Math.random() * (90 - 1) + 1)
message.classList.add('show')
button.disabled = true
setTimeout(function(){
button.disabled = false
message.classList.remove('show')
}, 1000 * 3)
})
clear.addEventListener('click', ()=> {
first.innerText = ''
second.innerText = ''
third.innerText = ''
fourth.innerText = ''
fifth.innerText = ''
sixth.innerText = ''
})
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-image: url('conf.jpg');
background-size: cover;
background-repeat: no-repeat;
height: 100vh;
background-position-x: right;
}
.overlay {
width: 100vw;
height: 100vh;
background: rgba(0,0,0,.3);
}
.container {
width: 95vw;
height: 420px;
margin: auto;
display: flex;
flex-direction: column;
border: 1px solid black;
justify-content: space-around;
align-items: center;
text-align: center;
font-family: helvetica;
position: absolute;
top: 40vh;
left: 50vw;
transform: translate(-50%, -50%);
background: rgba(0,0,0,.75);
color: white;
border-radius: 15px;
box-shadow: 0 0 15px black;
padding-bottom: 1em;
}
.title {
height: 50%;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
text-align: center;
letter-spacing: .5px;
}
h1 {
font-family: verdana;
border-bottom: 1px solid white;
}
.tenta {
font-size: 1.25em;
}
.numbers {
display: flex;
width: 100%;
justify-content: space-around;
}
.number {
width: 50px;
height: 50px;
border-radius: 50%;
border: 1px solid white;
text-align: center;
vertical-align: center;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-family: tahoma;
font-size: 1.2em;
background: black;
}
.button {
padding: .5em 1.5em;
font-size: 1.1em;
border: none;
background: white;
border-radius: 5px;
box-shadow: 0 0 10px black;
transition: .2s linear;
outline: none;
background: rgb(0,255,117)
}
.button:active {
transform: scale(.95);
}
.clear {
padding: .2em .5em;
position: absolute;
bottom: 20px;
right: 20px;
border: none;
background: white;
border-radius: 5px;
box-shadow: 0 0 10px black;
transition: .2s linear;
outline: none;
}
.clear:active {
transform: scale(.95);
}
p {
color: white;
text-align: center;
position: relative;
top: calc(450px + 20vw);
font-family: helvetica;
font-size: 1.1em;
margin: auto;
background: rgba(0,0,0,.45);
padding: .5em 1em;
display: none;
}
p.show {
display: block;
}
<div class="overlay">
<div class="container">
<div class="title">
<h1>Number Generator</h1>
<h3 class="nonsai">Non sai quali numeri giocare?</h3>
<h3 class="tenta">Tenta la fortuna con l'algoritmo di<br> Number Generator!</h3>
</div>
<div class="numbers">
<div class="first number"></div>
<div class="second number"></div>
<div class="third number"></div>
<div class="fourth number"></div>
<div class="fifth number"></div>
<div class="sixth number"></div>
</div>
<button class="button">Genera Numeri</button>
<button class="clear">Refresh</button>
</div>
<p class="message">Puoi usare Number Generator una volta ogni ora</p>
</div>
<script src="main.js"></script>

I didn't read all your code but from the explanation it seems like you have wrong approach in general if you want to restrict the user from clicking the button. I think you are aware of the client and server side computation.
So, first of all. Surely you can restrict button clicks on client side and even in a consistent way (saving it after refresh) but you should keep in mind that an advanced internet user will be able to hack it easily because you have just some standart options to achieve it. One of them is using cookies: When user clicks a button save a cookie with name butttonClicked or smth with value true and a lifetime of 1 hour. Its easy googlable how to do it. On page load always check if the cookies is set and attach attribute disabled to button. but cookies are visible and user can delete it himself.
To really restrict it you should make some checks on the server side so that even if user was able to send a request somehow, no matter the button was disabled or no on the front end, you should stop performing an action and send back an error message.

One way to do this would be using the browser's built-in capability to store data for a particular domain by utilizing the local storage. In general it can be used to store any kind of data but in your case it should hold the time your button gets 'reactivated'.
Let me outline what needs to be done:
at page load, start a timer which continuously checks the time stored in the local storage in a short interval of e.g. 1 second.
if there is no time stored yet, store the current time
if there is a time stored and it's in the future, disable the button
if there is a time stored and it's in the past, enable the button
If the user presses the button, update the value in the local storage and disable the button.
Here's an example. If you click on the button, it will disable it for 3 seconds:
<html>
<head>
</head>
<body>
<button id="button">click me</button>
</body>
<script type="text/javascript">
var myButton=document.getElementById("button");
function storeTime(theTime)
{
localStorage.setItem("time", theTime);
}
function check()
{
var now=new Date().getTime();
if(!localStorage.getItem("time"))
{
storeTime(now);
}
var storedTime=parseInt(localStorage.getItem("time"));
if(storedTime>now)
{
myButton.disabled=true;
}
else
{
myButton.disabled=false;
}
}
function buttonClicked()
{
var now=new Date().getTime() + 3000;
storeTime(now);
myButton.disabled=true;
}
setInterval(check, 1000);
check();
myButton.addEventListener("click", buttonClicked);
</script>
</html>

Related

How to rotate and scale cards while swiping

As I'm trying to make a carousel that rotates I wonder how to move the cards rotated and it scales from a minimum of 0.8(left and right cards) and a maximum scale of 1(center card) when the user keeps swiping
Scale:
left = 0.8
center = 1
right = 0.8
I'm trying to solve on how to rotate them using transform and z-index properties. The cards will also rotate however I'm still trying to make a formula on how to make a function that makes the cards rotate
Any alternative solutions are accepted The animation is
similar to this carousel from codepen however it doesn't swipe Carousel Rotate
const CONTAINER_FLEX = document.querySelector('.container-flex');
const items = document.querySelectorAll('.item');
let touchStartX = 0;
let touchMoveX = 0;
let count = 0;
let current_translate = 0;
let previous_translate = 0;
CONTAINER_FLEX.addEventListener('touchstart', (event) => {
touchStartX = event.touches[0].pageX;
});
CONTAINER_FLEX.addEventListener('touchmove', (event) => {
touchMoveX = event.touches[0].pageX;
current_translate = previous_translate + (touchMoveX - touchStartX);
console.log(current_translate);
items[1].style.transform = `translateX(${current_translate}px)`;
});
CONTAINER_FLEX.addEventListener('touchend', () => {
current_translate = touchMoveX - touchStartX;
previous_translate = current_translate;
});
*,
::before,
::after {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100vh;
font-family: Arial, Helvetica, sans-serif;
line-height: 1.5;
background-color: #131b24;
}
.main-container {
padding: 30px 0;
height: 300px;
width: 900px;
border-top: 1px solid #444;
border-bottom: 1px solid #444;
overflow: hidden;
background-color: white;
}
.container-flex {
height: 100%;
display: flex;
transition: transform 400ms cubic-bezier(0.165, 0.84, 0.44, 1);
}
.item {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
min-width: 300px;
max-width: 300px;
}
.item h1 {
font-size: 40px;
color: white;
}
/* ITEMS */
.item-1 {
background-color: #2c3e50;
transform: translateX(100px);
z-index: 1;
}
.item-2 {
background-color: #3498db;
z-index: 2;
}
.item-3 {
background-color: #1abc9c;
transform: translateX(-100px);
z-index: 1;
}
<div class="main-container">
<div class="container-flex" id="container-flex">
<div class="item item-1">
<h1>1</h1>
</div>
<div class="item item-2">
<h1>2</h1>
</div>
<div class="item item-3">
<h1>3</h1>
</div>
</div>
</div>
Here is a working example https://jsfiddle.net/4ue5sgm9/3/
I wonder how to move the cards when it goes from 0 to 200 however
Have all your cards in an array.
var cards = [ thing1, thing2, thing3];
Use % - modulus operator, it's the secret for cycling back to the beginning
cardIndex = (cardIndex + 1) % cards.length
Copied from the chat. I made it verbose for clarity
scrollLeft() {
nextIndex = items.indexOf(displayed[2])
nextIndex = ++nextIndex % items.length-1
displayed = items[nextIndex]
nextIndex = ++nextIndex % items.length-1;
displayed.push( items[nextIndex] );
nextIndex = ++nextIndex % items.length-1;
displayed.push( items[nextIndex] )
}
P.S. Write and iterator for items array. An iterator stops after the last item. An iterator is why "for (x in thisArray)" works. BUT write the next() function to return "% this.length-1" instead => now you have a never-ending looper. All that code just above goes away.

Hiding/Showing CSS elements does not work?

THE WHOLE CODE IN JSFIDDLE
I have been struggling to effectively remove the code and css created in the function Seifenblasen_blasen()
function Seifenblasen_blasen(){
btn1.style.display = 'none';
document.getElementById("text").innerHTML="Bubble"
const section = document.querySelector('section')
const createElement = document.createElement('spawn')
var size = Math.random() * 60;
createElement.style.width = 30 + size + 'px';
createElement.style.height = 30 + size + 'px';
createElement.style.left = Math.random() * innerWidth + "px";
section.appendChild(createElement);
setTimeout(() => {
createElement.remove()
},8000)
}
const Blaseninterval = setInterval(Seifenblasen_blasen, 100)
created CSS:
section {
width: 100%;
height: 100vh;
overflow: hidden;
background: #1F69FA;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
section.text{
font-size: 10em;
color: #333;
margin: 0 auto;
text-align: center;
font-family: consolas;
background-color:#1F69FA;
pointer-events: none;
border: none;
}
section spawn {
position: absolute;
bottom: -80px;
background: transparent;
border-radius: 50%;
pointer-events: none;
box-shadow: inset 0 0 10px rgba(255, 255, 255, 0.5);
animation: animate 4s linear infinite;
}
section spawn:before {
content: '';
position: absolute;
width: 100%;
height: 100%;
transform: scale(0.25) translate(-70%, -70%);
background: radial-gradient(#fff, transparent);
opacity: 0.6;
border-radius: 50%;
}
#keyframes animate {
0% {
transform: translateY(0%);
opacity: 1;
}
99% {
opacity: 1;
}
100% {
transform: translateY(-2000%);
opacity: 0;
}
section span {
margin-top: 700px;
font-size: 1em;
color: #333;
margin: 0 auto;
font-family: consolas;
background-color: #1F69FA;
border: none;
position: absolute;
}
HTML:
<section id="section">
<div class="content">
<button id="btn"></button>
<button id="btn1"></button>
</div>
</section>
to then execute the next function function next(). This removal is needed because when I don't remove the elements from the first function the second wont work. I could just do document.head.innerHTML = "" but that would then also remove the css needed for the button appearing in the next function. So then I tried to make variables with const
const btn = document.getElementById('text');
const btn1 = document.getElementById('text1');
const section = document.querySelector('section')
// in function Seifenblasen_blasen()
btn1.style.display = 'none';
// in function next()
section.style.display = 'none';
btn.style.display = 'none';
btn1.style.display = 'block';
to hide and show only parts of the css without removing the css entirely to keep the styling intact, but now nothing works anymore.(the button on the next Screen doesn't show up at all and the first button does not contain any styling) My endgoal is that I can essentially switch between two screens one showing the bubbles and one the bouncy balls and when I click on the button it goes on. (for example start is bubbles. I click -> Bounce, click again -> back to Bubbles and so on)

Restrict creation of new divs to a specific area

I have a small page. Divas in the form of circles are created here every certain time.
They spawn in random places.
As can be seen even on the buttons and slightly outside the page.
The question is. Is it possible to make a box that does not touch the buttons, and that the circles are created within this box?
This should be done as a border with a certain extension, but specifying everything in pixels is not an option, it will be bad for different screens.
I created such a frame, replaced document.body.appendChild(div);
on the document.getElementById("spawnRadius").appendChild(div);
It seems that they should appear within this frame, but no, all the same throughout the page.
I also tried instead of whole page height and width document.documentElement.clientWidth use the width and height of the desired border spawnRadius.width
But now all my circles do not appear randomly, but at the beginning of this block in one place.
I tried to see these values ​​through console.log
console.log(documentHeight);
console.log(documentWidth);
But getting there undefined
PS. Demo watch in full page
//timer
var minutesLabel = document.getElementById("minutes");
var secondsLabel = document.getElementById("seconds");
var totalSeconds = 0;
setInterval(setTime, 1000);
function setTime() {
++totalSeconds;
secondsLabel.innerHTML = pad(totalSeconds % 60);
minutesLabel.innerHTML = pad(parseInt(totalSeconds / 60));
}
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
//create circle
var widthHeight = 65;
var margin = 25;
var delta = widthHeight + margin;
var spawnRadius = document.getElementById("spawnRadius");
let clicks = 0;
function createDiv(id, color) {
let div = document.createElement('div');
var currentTop = 0;
var documentHeight = spawnRadius.height;
var documentWidth = spawnRadius.width;
div.setAttribute('class', id);
if (color === undefined) {
let colors = ['#35def2', '#35f242', '#b2f235', '#f2ad35', '#f24735', '#3554f2', '#8535f2', '#eb35f2', '#f2359b', '#f23547'];
div.style.borderColor = colors[Math.floor(Math.random() * colors.length)];
}
else {
div.style.borderColor = color;
}
div.classList.add("circle");
div.classList.add("animation");
currentTop = Math.floor(Math.random() * documentHeight) - delta;
currentLeft = Math.floor(Math.random() * documentWidth) - delta;
var limitedTop = Math.max(margin * -1, currentTop);
var limitedLeft = Math.max(margin * -1, currentLeft);
div.style.top = limitedTop + "px";
div.style.left = limitedLeft + "px";
const nodes = document.querySelectorAll('.animation');
for(let i = 0; i < nodes.length; i++) {
nodes[i].addEventListener('click', (event) => {
event.target.style.animation = 'Animation 200ms linear';
setTimeout(() => {
event.target.style.animation = '';
}, 220); });
}
$(div).click(function() {
$('#clicks').text(++clicks);
$(this).fadeOut();
});
document.getElementById("spawnRadius").appendChild(div);
}
let i = 0;
const oneSecond = 600;
setInterval(() => {
i += 1;
createDiv(`circle${i}`);
}, oneSecond);
html, body {
width: 100%;
height: 100%;
margin: 0;
background: #0f0f0f;
}
.back {
font-family: "Comic Sans MS", cursive, sans-serif;
font-size: 25px;
letter-spacing: 2px;
word-spacing: 2px;
color: #ffffff;
text-shadow: 0 0 5px #ffffff, 0 0 10px #ffffff, 0 0 20px #ffffff, 0 0 40px #ff00de, 0 0 80px #ff00de, 0 0 90px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
font-weight: 700;
text-decoration: none;
font-style: italic;
font-variant: normal;
text-transform: lowercase;
position: absolute;
top: 25%;
left: 2%;
user-select: none;
z-index: 999;
}
.panel {
color: #0f0f0f;
font-size: 40px;
z-index: 999;
position: absolute;
cursor: default;
user-select: none;
color: #0f0f0f;
}
.score {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.time {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.circle {
width: 60px;
height: 60px;
border-radius: 60px;
background-color: #0f0f0f;
border: 3px solid #000;
margin: 20px;
position: absolute;
}
#keyframes Animation {
0% {
transform: scale(1);
}
50% {
transform: scale(.8);
}
100% {
transform: scale(1);
}
}
#spawnRadius {
top: 55%;
height: 650px;
width: 1000px;
left: 50%;
white-space: nowrap;
position: absolute;
transform: translate(-50%, -50%);
background: #0f0f0f;
border: 2px solid #ebc6df;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="panel">
<span class="score">Score: <a id="clicks">0</a></span>
<span class="time">Time: <label id="minutes">00</label>:<label id="seconds">00</label></span>
</span>
back
<div id="spawnRadius"></div>
To answer your main question, the getBoundingClientRect method can be used to retrieve the current bounding rectangle of an element, using which you can determine where the valid spawn areas are.
When choosing a valid placement, only consider the width and height of the container element, since the coordinates of child elements are relative to its parent. You also need to take into account the size of the elements being spawned, so the valid range of the x position for example is 0 to containerWidth - circleWidth.
The circles also had a CSS margin associated with them, which would offset them past their absolute coordinates.
There are a few other issues with the code though which you may run into later on:
There was an odd mix of jQuery and standard JavaScript calls, so if you're familiar with native JavaScript methods then it's likely simpler to stick with those and remove the dependency on jQuery.
For example, there were two click event handlers on each circle, one to add the CSS animation and another to increment the score. These can be combined into a single function.
The bounce out animation and the jQuery fade out can also be combined by adding opacity values into the animation start and end keyframes.
There was a loop in the createDiv function which added another click event handler to every circle element rather than just to the newly created element. This may have originally necessitated the jQuery click handler outside of that loop, since otherwise the score counter would have been incremented multiple times.
It was also possible to click the circles multiple times before the animation was complete (hence adding multiple points), which was likely not intended. Adding a simple Boolean clicked flag can avoid this.
Once the fade animation completed, the circle element itself was still on the page, it just had a display of none so wouldn't be visible. Over time, this would cause slowdowns on lower end hardware since there would be many DOM elements still sitting in memory that were no longer required. As such, it's best to remove elements from the DOM once they're no longer needed using removeChild. You had the right idea by removing the animation after the animation completed.
Here's the amended code:
var minutesLabel = document.getElementById("minutes");
var secondsLabel = document.getElementById("seconds");
var clickEl = document.getElementById("clicks");
var totalSeconds = 0;
let clicks = 0;
setInterval(setTime, 1000);
function setTime() {
++totalSeconds;
secondsLabel.innerText = pad(totalSeconds % 60);
minutesLabel.innerText = pad(parseInt(totalSeconds / 60));
}
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
var spawnRadius = document.getElementById("spawnRadius");
var spawnArea = spawnRadius.getBoundingClientRect();
const circleSize = 66; // Including borders
//create circle
function createDiv(id, color) {
let div = document.createElement('div');
div.setAttribute('class', id);
if (color === undefined) {
let colors = ['#35def2', '#35f242', '#b2f235', '#f2ad35', '#f24735', '#3554f2', '#8535f2', '#eb35f2', '#f2359b', '#f23547'];
div.style.borderColor = colors[Math.floor(Math.random() * colors.length)];
}
else {
div.style.borderColor = color;
}
// Randomly position circle within spawn area
div.style.top = `${Math.floor(Math.random() * (spawnArea.height - circleSize))}px`;
div.style.left = `${Math.floor(Math.random() * (spawnArea.width - circleSize))}px`;
div.classList.add("circle", "animation");
// Add click handler
let clicked = false;
div.addEventListener('click', (event) => {
if (clicked) { return; } // Only allow one click per circle
clicked = true;
div.style.animation = 'Animation 200ms linear forwards';
setTimeout(() => { spawnRadius.removeChild(div); }, 220);
clickEl.innerText = ++clicks
});
spawnRadius.appendChild(div);
}
let i = 0;
const oneSecond = 1000;
setInterval(() => {
i += 1;
createDiv(`circle${i}`);
}, oneSecond);
html, body {
width: 100%;
height: 100%;
margin: 0;
background: #0f0f0f;
}
.back {
font-family: "Comic Sans MS", cursive, sans-serif;
font-size: 25px;
letter-spacing: 2px;
word-spacing: 2px;
color: #ffffff;
text-shadow: 0 0 5px #ffffff, 0 0 10px #ffffff, 0 0 20px #ffffff, 0 0 40px #ff00de, 0 0 80px #ff00de, 0 0 90px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
font-weight: 700;
text-decoration: none;
font-style: italic;
font-variant: normal;
text-transform: lowercase;
position: absolute;
top: 25%;
left: 2%;
user-select: none;
z-index: 999;
}
.panel {
color: #0f0f0f;
font-size: 40px;
z-index: 999;
position: absolute;
cursor: default;
user-select: none;
color: #0f0f0f;
}
.score {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.time {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.circle {
width: 60px;
height: 60px;
border-radius: 60px;
background-color: #0f0f0f;
border: 3px solid #000;
position: absolute;
}
#keyframes Animation {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(.8);
}
100% {
transform: scale(1);
opacity: 0;
}
}
#spawnRadius {
top: 55%;
height: 650px;
width: 1000px;
left: 50%;
white-space: nowrap;
position: absolute;
transform: translate(-50%, -50%);
background: #0f0f0f;
border: 2px solid #ebc6df;
}
<span class="panel">
<span class="score">Score: <a id="clicks">0</a></span>
<span class="time">Time: <label id="minutes">00</label>:<label id="seconds">00</label></span>
</span>
back
<div id="spawnRadius"></div>

Chaining button clicks with jQuery

I'm trying to build a simple website that let's you click 6 different buttons. Each click of the button is supposed to trigger the display of specific content (as an img file). Clicking each of the six buttons should lead to different content. I have managed to achieve this part via Javascript getElementById.
However, to add a bit more complexity, I want to implement sequential decision making. Meaning that clicking Button "1" and THEN clicking Button "2" (or 3-6 for that matter) should each lead to the display of other specific content. Likewise clicking Button "1", then "2" and then "1" again should also display specific content. My sequential decision making is supposed to be limited to only two buttons interacting until the end of the decision is reached. So essentially, something like 1 -> 2 -> 3 can not happen, but 3 -> 6 -> 3 can happen. I hope it's not too complicated what I'm trying to do.
Anyway, here's some code I wrote trying to achieve this, but I'm fairly sure that my toggle function is not the correct way to go about it as I'm essentially simply placing pictures above each other and there is no sequency to any of the decisions made. I think to achieve this, I would need to chain the clicks, but I'm completely lost as to how to achieve that. Any help is greatly appreciated.
a:link {
color: white;
text-decoration: none;
}
a:visited {
color: white;
text-decoration: none;
}
a:hover {
color: white;
text-decoration: none;
}
a:active {
color: white;
text-decoration: none;
}
a.pos:link {
color: black;
text-decoration: none;
}
a.pos:visited {
color: black;
text-decoration: none;
}
a.pos:hover {
color: white;
text-decoration: none;
}
a.pos:active {
color: black;
text-decoration: none;
}
a.button:link, a.button:visited {
margin: auto;
position: absolute;
top: 0px;
left: 0px;
background-color: yellowgreen;
width: 345px;
line-height: 20px;
height: 185px;
border: 2px solid;
border-color: white;
text-align: center;
border-radius: 100px;
font-family: open sans;
font-size: 9px;
color: black;
font-weight: 650;
color: white;
padding: 14px 25px;
text-align: center;
text-decoration: none;
display: inline-block;
}
a.button:hover, a.button:active {
background-color: yellowgreen;
}
body {margin:0;}
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
position: fixed;
top: 0;
width: 100%;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: center;
padding: 20px 30px;
font-family: open sans;
text-decoration: none;
}
li a:hover:not(.active) {
background-color: #111;
}
.active {
background-color: #4CAF50;
}
h1 {
color: whitesmoke;
font-family: open sans;
font-size: 300%;
}
.table {
margin: auto;
position: relative;
width: 450px;
top: -1350px;
border: 6px solid #333333;
border-radius: 250px;
background: #737373;
padding-top: 150px;
padding-right: 50px;
padding-left: 50px;
padding-bottom: 150px;
}
#quattro {
margin: auto;
position: absolute;
bottom: -25px;
right: 250px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#uno {
margin: auto;
position: absolute;
top: -25px;
right: 250px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#duo {
margin: auto;
position: absolute;
top: 25px;
right: 10px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#tres {
margin: auto;
position: absolute;
bottom: 25px;
right: 10px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#cinqo {
margin: auto;
position: absolute;
bottom: 25px;
left: 10px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
#seis {
margin: auto;
position: absolute;
top: 25px;
left: 10px;
background-color: gold;
width: 50px;
line-height: 50px;
height: 50px;
border: 1px solid black;
text-align: center;
border-radius: 50px;
font-family: open sans;
font-size: 20px;
font-weight: 650;
}
.imgrange1 {
text-align: center;
color: white;
position: absolute;
top: 400px;
left: -400px;
}
.imgrange2 {
text-align: center;
color: white;
position: absolute;
top: 400px;
left: 320px;
}
.centered {
font-family: open sans;
font-size: 150%;
position: absolute;
top: -3%;
left: 50%;
transform: translate(-50%, -50%);
}
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<link rel="stylesheet" href="css/style.css">
<style>
.myimgdivtoggle1 {
display: none;
}
.myimgdivtoggle2 {
display: none;
}
.myimgdivtoggle3 {
display: none;
}
.myimgdivtoggle4 {
display: none;
}
.myimgdivtoggle5 {
display: none;
}
</style>
<script>
$(document).ready(function(){
$('.togglebtn1').click(function(){
$('.myimgdivtoggle1').toggle();
});
});
$(document).ready(function(){
$('.togglebtn2').click(function(){
$('.myimgdivtoggle2').toggle();
});
});
$(document).ready(function(){
$('.togglebtn3').click(function(){
$('.myimgdivtoggle3').toggle();
});
});
$(document).ready(function(){
$('.togglebtn4').click(function(){
$('.myimgdivtoggle4').toggle();
});
});
$(document).ready(function(){
$('.togglebtn5').click(function(){
$('.myimgdivtoggle5').toggle();
});
});
$(document).ready(function(){
$('.togglebtn6').click(function(){
$('.myimgdivtoggle6').toggle();
});
});
</script>
</head>
<body>
<ul>
<li><a class="active" href="index.html">Main</a></li>
<li>News</li>
<li>Contact</li>
<li>About</li>
</ul>
<div style="padding:20px;margin-top:30px;background-color:cadetblue;height:1500px;">
<h1><center>TEST</center></h1>
</div>
<div class="table">
<button type="button" class="togglebtn1" id="uno">1</button>
<div class="myimgdivtoggle1">
<img src="1.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn2" id="duo">2</button>
<div class="myimgdivtoggle2">
<img src="2.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn3" id="tres">3</button>
<div class="myimgdivtoggle3">
<img src="3.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn4" id="quattro">4</button>
<div class="myimgdivtoggle4">
<img src="4.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn5" id="cinqo">5</button>
<div class="myimgdivtoggle5">
<img src="5.JPG" class="imgrange1"/>
</div>
<button type="button" class="togglebtn6" id="seis">6</button>
<div class="myimgdivtoggle6">
<img src="6.JPG" class="imgrange1"/>
</div>
</body>
</html>
Though I can not provide you with a firm solution, I can however offer a small example which illustrates how to incorporate an array which tracks the buttons that have been clicked, as well as a way to get certain content from combinations of buttons.
Run the example and try the combinations 363, 254, 521 and 165 to get some results showing up. I've tried my best to show what the produced output is.
I'd suggest that you take a look at it and ask any questions if you have them. I'll check in to see if you do.
$(document).ready(function() {
/**
* Select the buttons.
* The $display and $clickedButtons are just to output
* the values that are stored.
*/
const $buttons = $('.button');
const $display = $('#display');
const $clickedButtons = $('#clicked-buttons');
const $removeButton = $('#remove-button');
/**
* Array which tracks your clicked buttons.
* If a button is clicked, the value of that button should be added to this array.
* The combination of the values will then later represent the key.
*/
const values = [];
/**
* This is where any know combinations are stored.
* The values in the values array will later be transformed into a single string to
* see if it matches any key in the combinations object below.
* If it does, it will give you a value, otherwise undefined.
*/
const combinations = {
"363": "https://www.fillmurray.com/200/200",
"254": "https://www.fillmurray.com/220/220",
"521": "https://www.fillmurray.com/240/240",
"165": "https://www.fillmurray.com/300/300"
};
/**
* Combines the values to form a single key and check if that key matches a combination.
* If there is a match the content should be anything other than undefined.
*/
function tryCombination() {
// This will output the current values from the array.
$clickedButtons.text(values);
// Transform the array into a single string.
// This will be the key to select content.
// ["1", "2", "3"] becomes "123".
const key = values.join('');
// Check if key has a match in the combinations object.
const url = combinations[key];
if (url !== undefined) {
// It does, show the content.
$display.attr('src', url);
$display.removeClass('hidden');
} else {
// It doesn't, empty the content.
$display.removeAttr('src');
$display.addClass('hidden');
}
}
/**
* Listen for the click event on all the buttons.
* When clicked, get the value of that clicked button and add that to the values array.
* It then calls the tryCombination function to evaluate if the values in the values
* array make a valid combination.
*/
$buttons.on('click', function() {
// This is the currently clicked button.
const $button = $(this);
// Get the value of the button.
const value = $button.val();
// If there already are 3 previously clicked buttons,
// then empty the array, so we can start a new combination.
if (values.length === 3) {
values.length = 0;
}
// Now add the newly clicked value.
values.push(value);
// Render and try the combination.
tryCombination();
});
/**
* Remove the last item in the values array.
* Then retry to create a valid combination.
*/
$removeButton.on('click', function() {
// Remove the last item from the values array
values.pop();
// Render and try the new combination.
tryCombination();
})
});
.container {
display: grid;
grid-template-rows: auto auto;
grid-template-columns: 200px 1fr;
grid-gap: 1em;
border: 1px solid #d0d0d0;
background-color: #f7f7f7;
padding: 1em;
border-radius: 5px;
}
.buttons {
grid-area: 1 / 1 / 2 / 3;
}
#display {
grid-area: 2 / 1 / 3 / 2;
width: 200px;
height: 200px;
background-color: #d0d0d0;
border-radius: 5px;
}
#clicked-buttons {
grid-area: 2 / 2 / 3 / 3;
display: block;
background-color: #d0d0d0;
border-radius: 5px;
padding: 1em;
margin: 0;
}
#remove-button {
grid-area: 1 / 2 / 2 / 3;
}
.hidden {
opacity: 0;
visibility: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="container">
<div class="buttons">
<button class="button" id="1" value="1" >1</button>
<button class="button" id="2" value="2" >2</button>
<button class="button" id="3" value="3" >3</button>
<button class="button" id="4" value="4" >4</button>
<button class="button" id="5" value="5" >5</button>
<button class="button" id="6" value="6" >6</button>
</div>
<img id="display" class="hidden">
<button id="remove-button">Remove last input</button>
<code id="clicked-buttons"></code>
</div>
Edit
In the spirit of showing is better than telling; your last comment was about having a loose combination of numbers. This adds another layer of complexity.
Objects can only have keys that are strings (or Symbols) to get and set values. But in your case you'll want an array of numbers which represent the keys in any order, so plain objects are not suitable anymore.
The solution for this is the Map object. This object can have any type of key and value. So we can make a link between a combination of numbers and the images they represent (hence the name "map").
The example below uses this method. I've written a function that checks if an array of numbers is a match with any combination in the map. And if it does it return an array of images, referring to your previous comment.
Check it out. I believe this one to be more complex, so once more feel free to ask questions.
/**
* Create a Map instance.
*/
const combinations = new Map();
/**
* Values and keys are added with the set() method.
* This could still be improved with a loop setting each
* combination / images pair.
*/
combinations.set([3, 3, 6], ['https://www.fillmurray.com/200/200', 'https://www.fillmurray.com/200/200']);
combinations.set([2, 4, 5], ['https://www.fillmurray.com/220/220', 'https://www.fillmurray.com/220/220']);
combinations.set([1, 2, 5], ['https://www.fillmurray.com/240/240']);
combinations.set([1, 5, 6], ['https://www.fillmurray.com/300/300', 'https://www.fillmurray.com/300/300', 'https://www.fillmurray.com/300/300']);
const tryCombination = (key, combinations) => {
/**
* Loop over every combination.
* [combination, images] exposes the key-value pair,
* it's just a syntax to write fewer lines
*/
for (const [combination, images] of combinations) {
/**
* Create an array for the matches. If a number of the
* combination is in the given key, then that number
* will be pushed to the matches list. In the end,
* if everything matches, we should have just as many
* matches as numbers in the combination. That way
* we know if a key is correct.
*/
const matches = [];
/**
* We'll do some manipulation on the combination array,
* so to keep it intact we make a copy and manipulate that instead.
*/
const combinationCopy = Array.from(combination);
/**
* Count backwards through the combination array.
* Backwards counting is necessary when you remove items
* from the array while looping. I'd suggest you look
* into that subject.
*/
for (let i = combinationCopy.length - 1; i >= 0; i--) {
/**
* Get the current number we're looping over.
*/
const number = combinationCopy[i];
/**
* If that number is in the key array..
*/
if (key.includes(number)) {
/**
* ..then push that number to the matches array..
*/
matches.push(number);
/**
* ..and remove it from the copied combination array.
* We do this to prevent duplicate hits for cases
* where you have multiple occurrences of the same number,
* like [3, 3, 6]. When the first 3 hits, it will be removed.
* Then we have [3, 6] and we know we only need one more
* 3 and a 6.
*/
combinationCopy.splice(i, 1);
}
}
/**
* Now if every number has been matched correctly, then
* the amount of matches should be the same as the length
* of the combination. If that is the case, return the
* images. Otherwise, do nothing.
*/
if (matches.length === combination.length) {
return images;
}
}
/**
* If there are no matches, just return false, notifying the
* user that the combination is incorrect.
*/
return false;
};
console.log(tryCombination([5, 4, 2], combinations)); // Hit!
console.log(tryCombination([5, 1, 6], combinations)); // Hit!
console.log(tryCombination([2], combinations)); // Fail!
console.log(tryCombination([5, 4, 4], combinations)); // Fail!
console.log(tryCombination([3, 6, 3], combinations)); // Hit!

Compounding Value within an object used to slide dot across the page incrementally

I am unable to get a variable to function properly as the translateX value within my object. I am wanting to make the dot scroll across the page each time the next button is clicked. My code is only able to move it back and forth for the first step.
I am new to the animation API, and I have already made this work with CSS transitions but I am trying to get a good handle on the API.
html:
<div class="progress__container">
<div class="progress__bar">
<div id="progress__fill" class="step1"></div>
<div class="circ" id="circ__1"></div>
<div class="circ" id="circ__2"></div>
<div class="circ" id="circ__3"></div>
<div class="circ" id="circ__4"></div>
<div id="progress__dot" class="prog__1"></div>
</div>
<div class="backBar"></div>
<div class="flexrow">
<span class="stepName">Account</span>
<span class="stepName">Frequency</span>
<span class="stepName">Amount</span>
<span class="stepName">Taxes</span>
</div>
<div class="button__container">
<button class="buttonStep" id="back">Back</button>
<button class="buttonStep is-active" id="next">Next</button>
</div>
</div>
js:
// give a starting value for the transformation
var startVal = 0;
// define the keyframes
var moveDot = [
{ transform: `translateX(${startVal}px)`},
{ transform: `translateX(${startVal + 190}px)`}
];
// definte the timing
var dotTiming = {
duration: 400,
fill: "forwards",
easing: 'ease-in',
}
// make the animation happen
var movingDot = document.getElementById("progress__dot").animate(
moveDot,
dotTiming
);
// pause the animation until called
movingDot.pause();
// on click fire the animation
document.getElementById('next').addEventListener('click', function() {
movingDot.playbackRate = 1;
if (startVal <= 380) {
movingDot.play();
startVal += 190;
}
});
document.getElementById('back').addEventListener('click', function() {
movingDot.playbackRate = -1;
if (startVal >= 0) {
movingDot.play();
startVal -= 190;
}
});
css:
#progress__fill {
height:2px;
position: absolute;
top: 7px;
left: 0;
background-color: darkred;
}
#progress__dot {
background-color: darkred;
color: #fff;
border-radius: 50%;
height: 8px;
width: 8px;
position: absolute;
text-align:center;
line-height: 8px;
padding: 6px;
top: 0;
font-size: 12px;
}
/* Static Bar Elements */
.progress__container {
width: 600px;
margin: 20px auto;
position: relative;
}
.backBar {
height:2px;
width:96%;
position: absolute;
top: 7px;
left: 2%;
background-color: lightgrey;
}
.progress__bar {
z-index: 100;
position: relative;
width: 96%;
margin: 0 auto;
}
.circ {
background-color: #fff;
border: 2px solid lightgrey;
border-radius: 50%;
height: 12px;
width: 12px;
display: inline-block;
}
#circ__2, #circ__3 {
margin-left: 30%
}
#circ__4 {
float: right;
}
.passed {
background-color: darkred;
border: 2px solid darkred;
}
.hide {
visibility: hidden
}
.flexrow {
display: flex;
flex-direction: row;
justify-content: space-between;
}
/* Buttons */
.buttonStep {
background: grey;
color: #fff;
padding: 10px 25px;
border-radius: 10px;
font-size: 16px;
}
#back {
float: left;
}
#next {
float: right;
}
.is-active {
background: darkred;
}
The way I have it set up, I expect for the translateX values to increment or decrement depending on the click event listeners which would make the circle slide across the page. What is actually happening is that only the first step works. it will not go past the first stop point. If I log moveDot in the console it gives me the values that I am expecting, but it will only start/stop at 0 and 190. the back button functions the same way. link to fiddle
It is animated from and to the same place every time. Move the definition of moveDot into the event listener:
// give a starting value for the transformation
var startVal = 0;
// definte the timing
var dotTiming = {
duration: 400,
fill: "forwards",
easing: 'ease-in',
}
// on click fire the animation
document.getElementById('next').addEventListener('click', function() {
if (startVal > 380){return;}
// define the keyframes
var moveDot = [{transform: `translateX(${startVal}px)`},
{transform: `translateX(${startVal + 190}px)`}];
// make the animation happen
var movingDot = document.getElementById("progress__dot").animate(
moveDot,
dotTiming
);
movingDot.playbackRate = 1;
movingDot.play();
startVal += 190;
});
document.getElementById('back').addEventListener('click', function() {
movingDot.playbackRate = -1;
if (startVal >= 0) {
movingDot.play();
startVal -= 190;
}
});

Categories

Resources