<html>
<head>
<title>Random manga</title>
<script src="./js/my.js"></script>
<link type="text/css" rel="stylesheet" href="./css/style.css">
</head>
<body>
<div id="al">
<div id="pic">
<img src="" id="img" style="visibility: hidden;">
</div>
<button id="btng" onclick="my()">Suggest me manga</button>
</div>
</body>
</html>
Here is the html for button div and other things
Below is the css
body
{
background : white;
color : black;
}
#pic
{
border-radius : 25px;
background-color : #f2f2f2;
-webkit-border-radius : 35px;
-moz-border-radius : 75px;
box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px;
background-size : cover;
text-align : center;
height: 400px;
width: 400px;
background-size : cover;
background-repeat: no-repeat;
background-position: center;
}
#btng
{
backface-visibility: hidden;
background-color: #405cf5;
border-radius: 6px;
border-width: 0;
box-shadow: rgba(50, 50, 93, .1) 0 0 0 1px inset,rgba(50, 50, 93, .1) 0 2px 5px 0,rgba(0, 0, 0, .07) 0 1px 1px 0;
color: #fff;
font-size: 100%;
height: 44px;
line-height: 1.15;
margin: 12px 0 0;
outline: none;
overflow: hidden;
padding: 0 25px;
position: relative;
text-align: center;
text-transform: none;
transform: translateZ(0);
transition: all .2s,box-shadow .08s ease-in;
width: 50%;
}
#btng:focus {
box-shadow: rgba(50, 50, 93, .1) 0 0 0 1px inset, rgba(50, 50, 93, .2) 0 6px 15px 0, rgba(0, 0, 0, .1) 0 2px 2px 0, rgba(50, 151, 211, .3) 0 0 0 4px;
}
#al
{
text-align : center;
position : absolute;
top : 50%;
left : 50%;
transform : translate(-50%, -50%);
}
#img{
height: 100%;
width: 100%;
}
And below the function for onclick on button
function my(){
var bt=document.getElementById("btng");
bt.textContent = "Suggest me another";
var my = new Array("im/R.jpg","im/S.jpg","im/E.jpg");
var randomNum = Math.floor(Math.random() * my.length);
var backgroundImage = my[randomNum];
document.getElementById("img").src = my[randomNum];
document.getElementById("pic").style.backgroundImage=`url(${backgroundImage})`;
}
The onclick event only runs after the button is clicked twice or thrice sometimes I tried doing something to slove it but I was unsuccessful, is there any way to do it? Here is the live preview:- https://mangasuggestions.000webhostapp.com/index.html
Please click on buttons 4/5 times to get the problem I am facing.
The onclick event and the function is working properly. The image didn't update is because the randomNum is the same as the previous one.
Our goal is to prevent using the same number that is generated previously.
One way to do this is to keep track of the last generated random number, and regenerate until it is different from the previous one.
Main logic
let randomNum;
do {
randomNum = Math.floor(Math.random() * images.length);
} while (randomNum === prevRandomNum);
prevRandomNum = randomNum
Full code (With some modification of your original code)
// Keep track of the last random number
let prevRandomNum = -1
function my(){
const btn = document.getElementById("btng");
btn.innerText = "Suggest me another";
const images = ["im/R.jpg","im/S.jpg","im/E.jpg"];
let randomNum;
do {
randomNum = Math.floor(Math.random() * images.length);
} while (randomNum === prevRandomNum);
prevRandomNum = randomNum
const backgroundImage = images[randomNum];
document.getElementById("pic").style.backgroundImage=`url(${backgroundImage})`;
}
Please make the button type="button". it seems working.
<button id="btng" type="button" onclick="my()">Suggest me another</button>
As #AndrewLi already answered, there is possibilty that generated random number is the same as the last one.
Instead of generating new number multiple times (with while loop).
You can filter out currently displayed image from array.
function my(){
var bt=document.getElementById("btng");
bt.textContent = "Suggest me another";
var img = document.getElementById("img");
var my = new Array("/im/R.jpg","/im/S.jpg","/im/E.jpg").filter((src) => src !== new URL(img.src).pathname);
var randomNum = Math.floor(Math.random() * my.length);
var backgroundImage = my[randomNum];
document.getElementById("img").src = my[randomNum];
document.getElementById("pic").style.backgroundImage=`url(${backgroundImage})`;
}
But to make it work, you need provide absolute path to your images in array.
So /im/R.jpg instead of im/R.jpg
Here is the probably most efficient way to do this without filtering the values causing a loop or re-generating values in case we get a duplicate using some index manipulation.
Explanation
For the first image we can select any image within the set of valid indices for the array i.e. 0 to array.length - 1 which we can do using Math.floor(Math.random() * array.length). Then we store the selected index in a variable currentImageIdx.
For any following image we generate a step value which determines the amount of steps we want to move on from the current index to the next value. We need to choose this in the range of 1 to array.length - 1 which we can do by using Math.floor(1 + Math.random() * (arrayLength - 1)) as this will make sure we will never move on so far that we are back at the same index as we currently are. We then just need to add that step value to the current index and take the remainder using modulo to make sure we're not getting out of bounds.
Examples for moving on to next index
For the following examples an array with 3 values is assumed:
We're currently at index 0 so we should either move on 1 or 2 steps. If we were to move on 3 steps we would be at 0 again because (start index + step) % array.length would translate to (0 + 3) % 3 = 0 and we'd be at the start index again.
If we're at index 1 we could again move on 1 or 2 steps but not 3 because (start index + step) % array.length would translate to (1 + 3) % 3 = 1 and we'd be at the start index again.
The same applies for index 2
This will work for an array of any size. Except that for the case of just having one element in the array that one element will of course be selected every time.
let currentImageIdx = undefined;
function my() {
var bt = document.getElementById("btng");
bt.textContent = "Suggest me another";
var my = new Array("https://dummyimage.com/20x20/000/fff", "https://dummyimage.com/20x20/00FF00/000", "https://dummyimage.com/20x20/FF0000/000");
let backgroundImageIdx;
// it is imporant to check for undefined here as currentImageIdx != 0 would also trigger this case if it just was if(!currentImageIdx) and may produce the same picture twice
if (currentImageIdx !== undefined) backgroundImageIdx = getNextImage(currentImageIdx, my.length)
else backgroundImageIdx = Math.floor(Math.random() * my.length);
document.getElementById("img").src = my[backgroundImageIdx];
currentImageIdx = backgroundImageIdx;
console.log(`selected image ${currentImageIdx}`);
document.getElementById(
"pic"
).style.backgroundImage = `url(${my[backgroundImageIdx]})`;
}
function getNextImage(currentIndex, arrayLength) {
// generate random step value between 1 and array.length - 1
// with three elements: either 1 and 2
var step = Math.floor(1 + Math.random() * (arrayLength - 1));
// add the step value to current index and make sure we're not out of bounds
console.log(`(${currentIndex} + ${step}) % ${arrayLength} = ${(currentIndex + step) % arrayLength}`)
return (currentIndex + step) % arrayLength;
}
<div id="al">
<div id="pic">
<img src="" id="img" style="visibility: hidden;">
</div>
<button id="btng" onclick="my()">Suggest me manga</button>
</div>
Related
I created this color guessing game from scratch using HTML, CSS and JS. You're given a random RGB value and you guess which out of three given colors is the correct one. Now I want to take it a step higher by adding a scoreboard and score (Which I'm not sure how to do at all) and a full-screen congratulation message when you guess the color right. I've tried doing that but the animation doesn't seem to work.
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
<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>
<h1 class="text-centered" id="main-h1">Guess The Color!</h1>
<h2 class="text-centered" id="colorguess"></h2>
<p id="score">
</p>
<div class="box-container">
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
</div>
<div id="bravo">
<p id="bravo-text">
BRAVO
</p>
</div>
</body>
<script src="script.js"></script>
</html>
CSS
*{
box-sizing: border-box;
}
body{
font-family: arial;
background-color: #1c1874;
color: rgb(105, 105, 139);
color: #fff;
}
.text-centered{
text-align: center;
text-shadow: 0px 2px 2px rgba(150, 150, 150, 1);
}
.box-container{
padding: 200px;
align-self: center;
display: flex;
flex-direction: row;
justify-content:space-evenly;
}
.box{
width: 200px;
height: 200px;
border-radius: 20px;
cursor: pointer;
border:5px solid #ffffff;
}
#score{
font-weight: 500;
font-size: 2em;
position: fixed;
top: 5%;
left: 5%;
}
#score::before {
content: "Score: ";
}
#bravo{
position: fixed;
display: none;
opacity: 0;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
text-align: center;
font-size: 5em;
background-color: rgba(4, 8, 97, 0.69);
}
#bravo-text{
flex: 0 0 120px;
text-shadow: 0px 2px 2px rgba(150, 150, 150, 1);
}
JS
function getrandomcolor(){
var r = Math.floor(Math.random() * 256);
var g = Math.floor(Math.random() * 256);
var b = Math.floor(Math.random() * 256);
rgb = "rgb("+r+", "+g+", "+b+")";
return rgb;
}
function gamestart(){
var mainh1 = document.getElementById('main-h1');
mainh1.innerHTML = "Guess the color!";
rgbvaluetrue = getrandomcolor();
var rgb = document.getElementById('colorguess');
rgb.innerHTML = rgbvaluetrue;
var body = document.getElementsByTagName('body');
var box = document.getElementsByClassName('box');
var scoreboard= document.getElementById('score');
var score = 0;
function displaybravo(element){
var op= 0;
var timer = setInterval(function () {
if (op >= 1){
clearInterval(timer);
}
if(op>=0.1){
element.style.display = "flex";
element.style.flexDirection = "column";
element.style.justifyContent = "center";
element.style.alignItems="center";
}
element.style.opacity = op;
element.style.filter = 'alpha(opacity=' + op * 100 + ")";
op += op * 0.1;
}, 10);
}
for(var i=0;i<box.length;i++){
box[i].style.backgroundColor = getrandomcolor();
}
//here I am trying to assign the initial rgb value to one of the divs randomly
var rand = Math.floor(Math.random() * box.length);
box[rand].style.backgroundColor = rgbvaluetrue;
//here I check if the div you clicked on is correct
function markSelection() {
if(this.style.backgroundColor == rgbvaluetrue) {
mainh1.innerHTML="BRAVO";
setTimeout(function() {
var brv = document.getElementById('bravo');
displaybravo(brv);
gamestart(); }, 2000);
} else {
gamestart();
}
}
for(var i=0;i<box.length;i++){
box[i].onclick = markSelection;
}
scoreboard.innerHTML=score;
}
gamestart();
I reviewed your game and made some changes to the code based on what stood out to me. There were a few bugs that were important to correct, and many things I changed purely for readability / preference.
I tend to avoid directly using timeouts. Promises are a good way to control the order of things in javascript. This game could likely benefit from OOP to make the logic more organized, but I didn't want to return you something that was completely unrecognizable. You are welcome to take this to code review for a more complete refactoring.
The first issue that I noticed is that your setInterval never resolves with the logic implemented. Because it never resolves, every time you attempted to show the bravo message, it would start an interval at 10 ms, and they would stack on top of eachother. This quickly would become an issue, potentially even breaking your page. Instead of attempting to redesign your interval logic, I decided to use the css transition attribute to handle the Bravo message, and I threw in a keyframe animation so you could see how to add other effects without javascript. Instead of having to write logic in js and preform resource expensive operations, I can leave the animation to css. Now js just needs to add and remove the hidden class.
I also wrote a helper function wait at the top of the js, and made functions with waiting async so I could use the await keyword instead of nesting my code inside timeouts. This is more readable in my own opinion.
To add a score, I moved the score variable above the gameStart function. This way each time the game restarts, the score is not affected. Then I just had to increment the score variable whenever a correct answer is given, and change the text in the dom.
One bug I noticed was that the correct answer could have been clicked several times, and the points would have increased, because the event listener was still active after the answer was submitted. To handle this, I changed the way the event listener was applied. Instead of using element.onclick =, I opted for el.addEventListener("click", . This allowed me to use .removeEventListener once the answer was given, and only reapply the event listener once the next round started. I also added a wait for incorrect answers, and some text when the wrong answer was given.
Other more cosmetic changes include using a helper function $ to make grabbing elements easier, and changing .innerHTML to .innerText to more clearly show intent / prevent potential bugs.
I didn't correct it in this code, but it's worth mentioning variable names in javascript are usually camel case. i.e. instead of getrandomcolor it would be getRandomColor
If you have any questions about my code, please ask 👍
const wait = t => new Promise(r => setTimeout(r, t));
const $ = (str, dom = document) => [...dom.querySelectorAll(str)];
function getrandomcolor(){
const random255 = () => Math.floor(Math.random() * 256);
const [r, g, b] = Array.from({length: 3}, random255);
return "rgb(" + r + ", " + g + ", " + b + ")";
}
let scoreboard = $('#score')[0];
let score = 0;
function gamestart(){
var mainh1 = $('#main-h1')[0];
mainh1.innerText = "Guess the color!";
rgbvaluetrue = getrandomcolor();
var rgb = $('#colorguess')[0];
rgb.innerText = rgbvaluetrue;
var body = document.body;
var allBoxes = $('.box');
async function displaybravo(element){
element.classList.remove("hidden");
await wait(2000);
element.classList.add("hidden");
await wait(1000);
}
for (var i=0; i<allBoxes.length; i++) {
allBoxes[i].style.backgroundColor = getrandomcolor();
}
var randomBoxIndex = Math.floor(Math.random() * allBoxes.length);
allBoxes[randomBoxIndex].style.backgroundColor = rgbvaluetrue;
async function markSelection() {
allBoxes.forEach(box => box.removeEventListener("click", markSelection));
if (this.style.backgroundColor == rgbvaluetrue) {
score++;
scoreboard.innerText = score;
mainh1.innerText = "BRAVO";
var brv = $('#bravo')[0];
await displaybravo(brv);
gamestart();
} else {
mainh1.innerText = "NOPE!";
await wait(1000);
gamestart();
}
}
allBoxes.forEach(box => box.addEventListener("click", markSelection));
scoreboard.innerHTML = score;
}
gamestart();
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: arial;
background-color: #1c1874;
color: rgb(105, 105, 139);
color: #fff;
}
.text-centered{
text-align: center;
text-shadow: 0px 2px 2px rgba(150, 150, 150, 1);
}
.box-container{
padding: 200px;
align-self: center;
display: flex;
flex-direction: row;
justify-content:space-evenly;
}
.box{
width: 200px;
height: 200px;
border-radius: 20px;
cursor: pointer;
border:5px solid #ffffff;
}
#score{
font-weight: 500;
font-size: 2em;
position: fixed;
top: 5%;
left: 5%;
}
#score::before {
content: "Score: ";
}
#bravo {
overflow: hidden;
position: fixed;
inset: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100%; /*needed to transition to / from*/
transition: height 2s;
}
#bravo.hidden {
height: 0;
}
#bravo-text {
font-size: 5rem;
font-weight: bold;
color: black;
animation: glow 1s ease-in-out infinite alternate;
}
#keyframes glow {
from {
text-shadow:
0 0 5px #fff,
0 0 10px #fff,
0 0 15px #e60073,
0 0 20px #e60073,
0 0 25px #e60073,
0 0 30px #e60073,
0 0 35px #e60073;
}
to {
text-shadow:
0 0 10px #fff,
0 0 20px #ff4da6,
0 0 30px #ff4da6,
0 0 40px #ff4da6,
0 0 50px #ff4da6,
0 0 60px #ff4da6,
0 0 70px #ff4da6;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="main.css">
<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>
<script defer src="main.js"></script>
</head>
<body>
<h1 class="text-centered" id="main-h1">Guess The Color!</h1>
<h2 class="text-centered" id="colorguess"></h2>
<p id="score">
</p>
<div class="box-container">
<div class="box">
</div>
<div class="box">
</div>
<div class="box">
</div>
</div>
<div id="bravo" class="hidden flexCenter">
<p id="bravo-text" class="flexCenter">
BRAVO
</p>
</div>
</body>
</html>
I'm attempting to mimic the following widget with HTML/CSS/JavaScript:
https://gyazo.com/76bee875d35b571bd08edbe73ead12cb
The way that I have it set up is the following:
I have a bar with a background color that has a gradient from red to green which is static.
I then have two blinders that is supposed to represent the negative space to give the illusion that the colored bars are animating (in reality, the blinders are simply sliding away)
I did it this way because I figured it might be easier instead of trying to animate the bar going in both directions, but now I'm not so sure lol. One requirement that I'm trying to keep is that the animation only deals with transform or opacity to take advantage of optimizations the browser can do (as described here: https://hacks.mozilla.org/2016/08/animating-like-you-just-dont-care-with-element-animate/)
The example has a few buttons to help test various things. The "Random positive" works great, and is exactly what I want. I haven't quite hooked up the negative yet tho because I'm not sure how to approach the problem of transitioning from positive to negative and vice-versa.
Ideally, when going from a positive to a negative, the right blinder will finish at the middle, and the left blinder will pick up the animation and finish off where it needs to go.
So for example, if the values is initially set to 40%, and the then set to -30%, the right blinder should animate transform: translateX(40%) -> transform: translateX(0%) and then the left blinder should animate from transform: translateX(0%) -> transform: translateX(-30%) to expose the red.
Also, the easing should be seamless.
I'm not sure if this is possible with the setup (specifically keeping the easing seamless, since the easing would be per-element, I think, and can't "carry over" to another element?)
Looking for guidance on how I can salvage this to produce the expected results, or if there's a better way to deal with this.
Note: I'm using jquery simply for ease with click events and whatnot, but this will eventually be in an application that's not jquery aware.
Here's my current attempt: https://codepen.io/blitzmann/pen/vYLrqEW
let currentPercentageState = 0;
function animate(percentage) {
var animation = [{
transform: `translateX(${currentPercentageState}%)`,
easing: "ease-out"
},
{
transform: `translateX(${percentage}%)`
}
];
var timing = {
fill: "forwards",
duration: 1000
};
$(".blind.right")[0].animate(animation, timing);
// save the new value so that the next iteration has a proper from keyframe
currentPercentageState = percentage;
}
$(document).ready(function() {
$(".apply").click(function() {
animate($("#amount").val());
});
$(".reset").click(function() {
animate(0);
});
$(".random").click(function() {
var val = (Math.random() * 2 - 1) * 100;
$("#amount").val(val);
animate(val);
});
$(".randomPos").click(function() {
var val = Math.random() * 100;
$("#amount").val(val);
animate(val);
});
$(".randomNeg").click(function() {
var val = Math.random() * -100;
$("#amount").val(val);
animate(val);
});
$(".toggleBlinds").click(function() {
$(".blind").toggle();
});
$(".toggleLeft").click(function() {
$(".blind.left").toggle();
});
$(".toggleRight").click(function() {
$(".blind.right").toggle();
});
});
$(document).ready(function() {});
.wrapper {
margin: 10px;
height: 10px;
width: 800px;
background: linear-gradient(to right, red 50%, green 50%);
border: 1px solid black;
box-sizing: border-box;
position: relative;
overflow: hidden;
}
.blind {
height: 100%;
position: absolute;
top: 0;
background-color: rgb(51, 51, 51);
min-width: 50%;
}
.blind.right {
left: 50%;
border-left: 1px solid white;
transform-origin: left top;
}
.blind.left {
border-right: 1px solid white;
transform-origin: left top;
}
<div class="wrapper">
<div class='blind right'></div>
<div class='blind left'></div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js" type="text/javascript"></script>
<input id="amount" type="number" placeholder="Enter percentage..." value='40' />
<button class="apply">Apply</button>
<button class="random">Random</button>
<button class="randomPos">Random Positive</button>
<button class="randomNeg">Random Negative</button>
<button class="toggleBlinds">Toggle Blinds</button>
<button class="toggleLeft">Toggle L Blind</button>
<button class="toggleRight">Toggle R Blind</button>
<button class="reset" href="#">Reset</button>
I've modified your code. Have a look at the code.
let currentPercentageState = 0;
function animate(percentage) {
var animation = [{
transform: `translateX(${currentPercentageState}%)`,
easing: "ease-out"
},
{
transform: `translateX(${percentage}%)`
}
];
var timing = {
fill: "forwards",
duration: 1000
};
if (percentage < 0) {
$(".blind.right")[0].animate(
[{
transform: `translateX(0%)`,
easing: "ease-out"
},
{
transform: `translateX(0%)`
}
], timing);
$(".blind.left")[0].animate(animation, timing);
} else {
$(".blind.left")[0].animate(
[{
transform: `translateX(0%)`,
easing: "ease-out"
},
{
transform: `translateX(0%)`
}
], timing);
$(".blind.right")[0].animate(animation, timing);
}
// save the new value so that the next iteration has a proper from keyframe
//currentPercentageState = percentage;
}
$(document).ready(function() {
$(".apply").click(function() {
animate($("#amount").val());
});
$(".reset").click(function() {
animate(0);
});
$(".random").click(function() {
var val = (Math.random() * 2 - 1) * 100;
$("#amount").val(val);
animate(val);
});
$(".randomPos").click(function() {
var val = Math.random() * 100;
$("#amount").val(val);
animate(val);
});
$(".randomNeg").click(function() {
var val = Math.random() * -100;
$("#amount").val(val);
animate(val);
});
$(".toggleBlinds").click(function() {
$(".blind").toggle();
});
$(".toggleLeft").click(function() {
$(".blind.left").toggle();
});
$(".toggleRight").click(function() {
$(".blind.right").toggle();
});
});
$(document).ready(function() {});
.wrapper {
margin: 10px;
height: 10px;
width: 800px;
background: linear-gradient(to right, red 50%, green 50%);
border: 1px solid black;
box-sizing: border-box;
position: relative;
overflow: hidden;
}
.blind {
height: 100%;
position: absolute;
top: 0;
background-color: rgb(51, 51, 51);
min-width: 50%;
}
.blind.right {
left: 50%;
border-left: 1px solid white;
transform-origin: left top;
}
.blind.left {
border-right: 1px solid white;
transform-origin: left top;
}
<div class="wrapper">
<div class='blind right'></div>
<div class='blind left'></div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js" type="text/javascript"></script>
<input id="amount" type="number" placeholder="Enter percentage..." value='40' />
<button class="apply">Apply</button>
<button class="random">Random</button>
<button class="randomPos">Random Positive</button>
<button class="randomNeg">Random Negative</button>
<button class="toggleBlinds">Toggle Blinds</button>
<button class="toggleLeft">Toggle L Blind</button>
<button class="toggleRight">Toggle R Blind</button>
<button class="reset" href="#">Reset</button>
You need to animate the things in two steps. The first step is to reset the previous state to initial state(which should be set to 0) and in the second step, you need to run the other animation which will actually move it to the destination state.
In order to achive this you can do,
let currentPercentageState = 0;
const animationTiming = 300;
function animate(percentage) {
let defaultTranformVal = [{
transform: `translateX(${currentPercentageState}%)`,
easing: "ease-out"
}, {transform: `translateX(0%)`}];
var animation = [{
transform: `translateX(0%)`,
easing: "ease-out"
},{
transform: `translateX(${percentage}%)`,
easing: "ease-out"
}];
var timing = {
fill: "forwards",
duration: animationTiming
};
if (percentage < 0) {
if(currentPercentageState > 0) {
$(".blind.right")[0].animate(defaultTranformVal, timing);
setTimeout(() => {
$(".blind.left")[0].animate(animation, timing);
}, animationTiming);
} else {
$(".blind.left")[0].animate(animation, timing);
}
}
if(percentage > 0) {
if(currentPercentageState < 0) {
$(".blind.left")[0].animate(defaultTranformVal, timing);
setTimeout(() => {
$(".blind.right")[0].animate(animation, timing);
}, animationTiming);
} else {
$(".blind.right")[0].animate(animation, timing);
}
}
// save the new value so that the next iteration has a proper from keyframe
currentPercentageState = percentage;
}
Here, you will see we have two transformations. The first one defaultTranformVal will move the currentPercentageState to zero and then the other one which will move from 0 to percentage.
You need to handle a couple of conditions here. The first one is if you are running it the first time(means there is no currentPercentageState), you don't need to run defaultTranformVal. If you have currentPercentageState then you need to run defaultTranformVal and then run the second animation.
Note:- You also need to clear the timeout in order to prevent the memory leak. This can be handle by storing the setTimout return value and then when next time it's running clear the previous one with the help of clearTimeout.
Here is the updated codepen example:-
https://codepen.io/gauravsoni119/pen/yLeZBmb?editors=0011
EDIT: I actually did manage to solve this!
let easing = "cubic-bezier(0.5, 1, 0.89, 1)";
let duration = 1000;
let easeReversal = y => 1 - Math.sqrt((y-1)/-1)
https://codepen.io/blitzmann/pen/WNrBWpG
I gave it my own cubic-bezier function of which I know the reversal for. The post below and my explanation was based on an easing function using sin() which isn't easily reversible. Not only that, but the built in easing function for ease-out doesn't match the sin() one that I had a reference for (I'm not really sure what the build in one is based on). But I realized I could give it my own function that I knew the reversal for, and boom, works like a charm!
This has been a very informative experience for me, I'm glad that I've got a solution that works. I still think I'll dip my toes in the other ideas that I had to see which pans out better in the long term.
Historical post:
So, after a few nights of banging my head around on this, I've come to the conclusion that this either isn't possible the way I was thinking about doing it, or if it is possible then the solution is so contrived that it's probably not worth it and I'd be better off developing a new solution (of which I've thought of one or tow things that I'd like to try).
Please see this jsfiddle for my final "solution" and a post-mortem
https://jsfiddle.net/blitzmann/zc80p1n4/
let currentPercentageState = 0;
let easing = "linear";
let duration = 1000;
function animate(percentage) {
percentage = parseFloat(percentage);
// determine if we've crossed the 0 threshold, which would force us to do something else here
let threshold = currentPercentageState / percentage < 0;
console.log("Crosses 0: " + threshold);
if (!threshold && percentage != 0) {
// determine which blind we're animating
let blind = percentage < 0 ? "left" : "right";
$(`.blind.${blind}`)[0].animate(
[
{
transform: `translateX(${currentPercentageState}%)`,
easing: easing
},
{
transform: `translateX(${percentage}%)`
}
],
{
fill: "forwards",
duration: duration
}
);
} else {
// this happens when we cross the 0 boundry
// we'll have to create two animations - one for moving the currently offset blind back to 0, and then another to move the second blind
let firstBlind = percentage < 0 ? "right" : "left";
let secondBlind = percentage < 0 ? "left" : "right";
// get total travel distance
let delta = currentPercentageState - percentage;
// find the percentage of that travel that the first blind is responsible for
let firstTravel = currentPercentageState / delta;
let secondTravel = 1 - firstTravel;
console.log("delta; total values to travel: ", delta);
console.log(
"firstTravel; percentage of the total travel that should be done by the first blind: ",
firstTravel
);
console.log(
"secondTravel; percentage of the total travel that should be done by the second blind: ",
secondTravel
);
// animate the first blind.
$(`.blind.${firstBlind}`)[0].animate(
[
{
transform: `translateX(${currentPercentageState}%)`,
easing: easing
},
{
// we go towards the target value instead of 0 since we'll cut the animation short
transform: `translateX(${percentage}%)`
}
],
{
fill: "forwards",
duration: duration,
// cut the animation short, this should run the animation to this x value of the easing function
iterations: firstTravel
}
);
// animate the second blind
$(`.blind.${secondBlind}`)[0].animate(
[
{
transform: `translateX(${currentPercentageState}%)`,
easing: easing
},
{
transform: `translateX(${percentage}%)`
}
],
{
fill: "forwards",
duration: duration,
// start the iteration where the first should have left off. This should put up where the easing function left off
iterationStart: firstTravel,
// we only need to carry this aniamtion the rest of the way
iterations: 1-firstTravel,
// delay this animation until the first "meets" it
delay: duration * firstTravel
}
);
}
// save the new value so that the next iteration has a proper from keyframe
currentPercentageState = percentage;
}
// the following are just binding set ups for the buttons
$(document).ready(function () {
$(".apply").click(function () {
animate($("#amount").val());
});
$(".reset").click(function () {
animate(0);
});
$(".random").click(function () {
var val = (Math.random() * 2 - 1) * 100;
$("#amount").val(val);
animate(val);
});
$(".randomPos").click(function () {
var val = Math.random() * 100;
$("#amount").val(val);
animate(val);
});
$(".randomNeg").click(function () {
var val = Math.random() * -100;
$("#amount").val(val);
animate(val);
});
$(".flipSign").click(function () {
animate(currentPercentageState * -1);
});
$(".toggleBlinds").click(function () {
$(".blind").toggle();
});
$(".toggleLeft").click(function () {
$(".blind.left").toggle();
});
$(".toggleRight").click(function () {
$(".blind.right").toggle();
});
});
animate(50);
//setTimeout(()=>animate(-100), 1050)
$(function () {
// Build "dynamic" rulers by adding items
$(".ruler[data-items]").each(function () {
var ruler = $(this).empty(),
len = Number(ruler.attr("data-items")) || 0,
item = $(document.createElement("li")),
i;
for (i = -11; i < len - 11; i++) {
ruler.append(item.clone().text(i + 1));
}
});
// Change the spacing programatically
function changeRulerSpacing(spacing) {
$(".ruler")
.css("padding-right", spacing)
.find("li")
.css("padding-left", spacing);
}
changeRulerSpacing("30px");
});
.wrapper {
margin: 10px auto 2px;
height: 10px;
width: 600px;
background: linear-gradient(to right, red 50%, green 50%);
border: 1px solid black;
box-sizing: border-box;
position: relative;
overflow: hidden;
}
.blind {
height: 100%;
position: absolute;
top: 0;
background-color: rgb(51, 51, 51);
min-width: 50%;
}
.blind.right {
left: 50%;
border-left: 1px solid white;
transform-origin: left top;
}
.blind.left {
border-right: 1px solid white;
transform-origin: left top;
}
#buttons {
text-align: center;
}
/* Ruler crap */
.ruler-container {
text-align: center;
}
.ruler, .ruler li {
margin: 0;
padding: 0;
list-style: none;
display: inline-block;
}
/* IE6-7 Fix */
.ruler, .ruler li {
*display: inline;
}
.ruler {
display:inline-block;
margin: 0 auto;https://jsfiddle.net/user/login/
background: lightYellow;
box-shadow: 0 -1px 1em hsl(60, 60%, 84%) inset;
border-radius: 2px;
border: 1px solid #ccc;
color: #ccc;
height: 3em;
padding-right: 1cm;
white-space: nowrap;
margin-left: 1px;
}
.ruler li {
padding-left: 1cm;
width: 2em;
margin: .64em -1em -.64em;
text-align: center;
position: relative;
text-shadow: 1px 1px hsl(60, 60%, 84%);
}
.ruler li:before {
content: '';
position: absolute;
border-left: 1px solid #ccc;
height: .64em;
top: -.64em;
right: 1em;
}
<div class="wrapper">
<div class='blind right'></div>
<div class='blind left'></div>
</div>
<div class="ruler-container">
<ul class="ruler" data-items="21"></ul>
</div>
<div id="buttons">
<input id="amount" type="number" placeholder="Enter percentage..." value='-80' />
<button class="apply">Apply</button>
<button class="random">Random</button>
<button class="randomPos">Random Positive</button>
<button class="randomNeg">Random Negative</button>
<button class="flipSign">Flip Sign</button>
<button class="toggleBlinds">Toggle Blinds</button>
<button class="toggleLeft">Toggle L Blind</button>
<button class="toggleRight">Toggle R Blind</button>
<button class="reset" href="#">Reset</button>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.0/jquery.min.js" type="text/javascript"></script>
<hr />
<p><strong>A note</strong> on the attempt made here:</p>
<p>
I was trying to animate a percentage bar that has both positive and negative values. But I set a challenge as well: I wanted to achieve this via animations utilizing only the compositor - which means animating opacity or transform <strong>only</strong> (no color, width, height, position, etc). The ideas presented here were based on the concept of blinds. I have a static element with a background gradient of red to green, then I have two elements that "blind" the user to the background. These blinds, being a simple element, simply slide into and out of place.
</p>
<p>The problem that I ran into was timing the two animations correctly when they switched signage. It's currently working (very well) for linear animation, but as soon as you introduce an easing function it gets wonky. The reason for this is due to the value that I'm using to set the first animation length (iteration, not duration), as well as the second animations start to pick up where the first left off. The value that I was using is the percentage of the total travel distance that each of the blinds will have to do.</p>
<p>So, for example, if you have a value of 50, and go to -80, that's a total travel distance of 130. The first blind travels <code>50 / 130 = ~0.3846</code> of the total distance, and the second blind will travel <code>1 - ~0.3846 = ~0.6154</code> of the total distance.</p>
<p>But, these are not the correct values for the <em>duration</em> of the animation. Instead, these are the percentages of the easing values (the y-axis). To get the duration for these, I would have to find the x value (given the known y value). eg, for an ease-out animation for a value going from 50 to -80, the animation crosses our 0 at ~0.03846, and we would have to solve for x given <code>0.03846 = sin((x * PI) / 2)</code>.</p>
<p>With the help of Wolfram Alpha, I was able to find a few test values this got me much closer to the actual animation, but the blinds always stopped slightly off the mark. I eventually chalked this up to one of two reasons: the fact that the valuess are always going to be approximate and the browser is never going to be 100% accurate, or / and 2) the browser is using a slightly different easing function than I was using for reference. Regardless, being so constrained by the fact that this "animation" relies on two different aniamtions lining up perfectly, I decided to leave this version be and go in a different direction.</p>
<p>
If anyone finds an actual solution to this, please post an answer here: https://stackoverflow.com/questions/62866844/how-to-animate-a-progress-bar-with-negatives-using-element-animate
</p>
Thanks to those that attempted this admittedly tricky problem
I have two elements, and one is moving towards the other. I am trying to get the new distance as it moves closer. Consider the code below:
<html>
<center>
<body onload = 'start()'>
<div class='field'>
<div id='bull'></div>
<div id='mount'></div>
</div>
<button id='one'>DO</button>
</body>
</center>
<style>
.field{
width: 440px;
height: 260px;
background: rgba(0, 0, 0, .2);
margin-top: 30px;
border: 1px solid #222;
border-radius: 10px;
}
#bull{
width: 15px;
height: 10px;
background: #000;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
position: absolute;
}
#mount{
width: 60px;
height: 30px;
background: rgba(20, 10, 45);
color: #fff;
border-radius: 4px;
margin-top: 210px;
}
</style>
<script type='text/javascript'>
function start() {
var ball = document.getElementById('bull');
var button = document.getElementById('one');
var mount = document.getElementById('mount');
button.addEventListener('click', go);
var x_pos = 0;
var y_pos = 0;
var bounce_point = 200;
var ball_dim = ball.getBoundingClientRect();
var ball_h_half = ball_dim.width / 2;
var ball_w_half = ball_dim.height / 2;
var mount_dim = mount.getBoundingClientRect();
var mount_h_half = mount_dim.width / 2;
var mount_w_half = mount_dim.height / 2;
function go() {
for(x_pos = 0; bounce_point > x_pos; x_pos++) {
ball.style.margin = x_pos + "px";
ball.style.transition = x_pos/2 + "s";
var dist = ((ball_h_half - mount_h_half)*(ball_w_half - mount_w_half)) + ((mount_h_half - ball_h_half)*(mount_w_half - ball_w_half));
console.log(dist);
if(dist < 3) {
console.log('One');
}
}
}
}
</script>
When bull reaches comes within 3px of mount, nothing happens... I've pretty much explained the issue as best as I can.
**
When bull reaches comes within 3px of mount, nothing happens... I've pretty much explained the issue as best as I can.**
Well i tried a little bit more, and your logic doens't seem to fit to what you want to do. First of all, you probably noticed your value in the log doesn't change.
It could have been because the values are retreived outside the loop, but not only (they actually have to be in the loop to be updated). You have 2 other problems: first, you are measuring the elements width and height, which don't take account of margin or other positioning. Your elements don't change size, so the value also won't. The other problem is actually the transition itself on the movement. Because of the delay, all your loop iterations are most probably done, and the margin already set to its final value when your "bull" effectively starts to move. It means that in the loop, you can't detect the position change, the element having not started to move yet. Using the value that was just set (margin) instead of detecting the real position of the element should show a progression for the value, but it makes harder to detect the collision because your 2 elements don't have the same positioning rules and you can't just compare the margins.
Here is a quick example that gets updated values (because the transition has been disabled, if you enable back, the problem comes again). You'll notice your calculation for the collision is wrong too. You can't just compare a distance between 2 corners for that, for a rectangle it's rather "has gone beyond left vertical edge AND has gone beyond top horizontal edge" (this of course takes only in account the top left corner, to be complete, it should also be added that it must not have reached the right or bottom edge yet).
Well, I can't propose you an all ready solution, but this addresses your code main issues:
<html>
<center>
<body onload = 'start()'>
<div class='field'>
<div id='bull'></div>
<div id='mount'></div>
</div>
<button id='one'>DO</button>
</body>
</center>
<style>
.field{
width: 440px;
height: 260px;
background: rgba(0, 0, 0, .2);
margin-top: 30px;
border: 1px solid #222;
border-radius: 10px;
}
#bull{
width: 15px;
height: 10px;
background: #000;
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
position: absolute;
}
#mount{
width: 60px;
height: 30px;
background: rgba(20, 10, 45);
color: #fff;
border-radius: 4px;
margin-top: 210px;
}
</style>
<script type='text/javascript'>
function start() {
var ball = document.getElementById('bull');
var button = document.getElementById('one');
var mount = document.getElementById('mount');
button.addEventListener('click', go);
var x_pos = 0;
var y_pos = 0;
var bounce_point = 200;
var ball_dim, ball_x, ball_y, mount_dim, mount_x, mount_y, diff_x, diff_y;
var stayInLoop = true;
//ball.style.transition = "0.4s"; //i don't know why you updated the transition time based on position, changed to a fixed value outside the loop because it's quicker for the example
function go() {
for(x_pos = 0; bounce_point > x_pos && stayInLoop; x_pos++) {
ball_dim = ball.getBoundingClientRect();
ball_y = ball_dim.top + 10; // +10 because we're considering the bottom edge of bull
ball_x = ball_dim.left + 15; // +15 because we're considering the right edge of bull
mount_dim = mount.getBoundingClientRect();
mount_y = mount_dim.top;
mount_x = mount_dim.left;
diff_x = mount_x - ball_x;
diff_y = mount_y - ball_y;
console.log(diff_x, diff_y);
ball.style.margin = x_pos + "px";
if(diff_x < 3 && diff_y < 3) {
console.log('One');
stayInLoop = false;
}
}
}
}
</script>
EDIT/SUGGESTION: i suggest looking at window.requestAnimationFrame() MDN doc here for a better control on animations
I have set a method to randomised an array of images. These randomised images will then be appended to the id tag within the html body. I have set 2 image id and have tried to append the randomised image array to the image id.
However, no images are being displayed.
Hence, what has been done wrong? please help.
Code:
var BrandNameArray = ["lib/img/Brands/A.png", "lib/img/Brands/C.png", "lib/img/Brands/B.png"];
function Game_Congrats() {
//Randomised Brand Offer
//Auto populate into brand container once randomised
for (i = 0; i < $('#list').find('img').length; i++) {
random_BrandIndex = Math.floor(Math.random() * BrandNameArray.length);
//Assign Variable to generate random Brands
var Brand = BrandNameArray[random_BrandIndex];
BrandNameArray.splice(random_BrandIndex, 1);
$('#Brand_' + (i + 1)).attr('src', Brand);
$('#Brand_' + (i + 1)).show();
}
}
}
.GameWinBrand_Container {
position: absolute;
top: 950px;
left: 286px;
height: 250px;
width: 580px;
overflow: hidden;
}
.GameWinBrand_innerScroll {
position: relative;
width: 550px;
font-size: 30px;
text-align: justify;
color: #ffffff !important;
overflow: hidden;
}
.GameWinBrand_Container::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
border-radius: 12px;
background-color: #ffffff;
}
.GameWinBrand_Container::-webkit-scrollbar {
width: 12px;
background-color: #5e5767;
}
.GameWinBrand_Container::-webkit-scrollbar-thumb {
border-radius: 20px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
background-color: #5e5767;
}
<div class="GameWinBrand_Container">
<div id="BrandWinlist" class="GameWinBrand_innerScroll">
<img id="GameBrand_1" style="width:250px; height:230px; top:0px; left:0px; border:0px; outline:0px" onclick="selectBrand('1');">
<img id="GameBrand_2" style="width:250px; height:230px; top:0px; left:330px; border:0px;" onclick="selectBrand('2');">
</div>
</div>
You have a bunch of wrong id references:
First of all your loop does not execute, because you do not have an element with id list.
Secondly you don't have image elements with id Brand_1, ... etc.
So change your code to this:
for (i = 0; i < $('#BrandWinlist').find('img').length; i++) {
random_BrandIndex = Math.floor(Math.random() * BrandNameArray.length);
//Assign Variable to generate random Brands
var Brand = BrandNameArray[random_BrandIndex];
BrandNameArray.splice(random_BrandIndex, 1);
$('#GameBrand_' + (i + 1)).attr('src', Brand).show();
}
Note that you can chain the call to show.
Also the selector and loop can be written more concisely as follows:
$('#BrandWinlist > img').each(function (i, img) {
random_BrandIndex = Math.floor(Math.random() * BrandNameArray.length);
//Assign Variable to generate random Brands
var Brand = BrandNameArray[random_BrandIndex];
BrandNameArray.splice(random_BrandIndex, 1);
$(img).attr('src', Brand).show();
});
And as a bonus, you can splice and get the removed element in one go:
var Brand = BrandNameArray.splice(random_BrandIndex, 1)[0];
The id of img tags starts like GameBrand.
Change this:
$('#Brand_' + (i + 1)).attr('src', Brand);
to:
$('#GameBrand_' + (i + 1)).attr('src', Brand);
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
My requirement is to change the div gradient background with an animation according to a increasing counter value.
For instance, suppose if a div background gradient is blue-white at counter=== 0.
When counter value is in range of [80, 100], then the div goes to danger range hence the background gradient is suppose to turn in red-white with animation.
I tried doing several tries, however cant do it gracefully.
Can some one tell me how can i acheive it?
I think you could made an adaptative solution. In this example, we are moving :
FROM : background-image: linear-gradient(rgba(220, 20, 20, 1) 0, rgba(255, 255, 255, 1));
TO : background-image: linear-gradient(rgba(20, 120, 220, 1) 0, rgba(255, 255, 255, 1));
Here is the code:
Stylesheet
*{
padding: 0;
margin: 0;
background-color: transparent;
background-image: none;
}
div{
display: block;
margin: 5px;
width: 500px;
height: 300px;
border-radius: 5px;
border: 1px rgba(220, 220, 220, 1) solid;
box-shadow: 1px 3px 9px rgba(220, 220, 220, 1);
background-image: linear-gradient(rgba(20, 120, 220, 1) 0, rgba(255, 255, 255, 1));
transition-duration: .7s;
-o-transition-duration: .7s;
-moz-transition-duration: .7s;
-webkit-transition-duration: .7s;
}
HTML BODY CONTENT
<div data-index="0"></div>
<b>Counter : </b><output></output>
<script type="text/javascript" src="js/JQuery/jquery-2.1.1.js"></script>
Javascript content
$(function() {
var linearDefinition = 'linear-gradient(rgba(RED, BLUE, GREEN, 1) 0, rgba(255, 255, 255, 1))';
setInterval(function() {
var count = parseInt($('div').data('index'));
count = (count === 100) ? 0 : count;
var red = 20 + ((1 + count) * 2);
var blue = 120 - count;
var green = 220 - (2 * (1 + count));
// Asume this is your couter instruction
$('div').data('index', 1 + count);
$('output').text($('div').data(('index')));
$('div').css('background-image', linearDefinition.replace(/RED/, red).replace(/BLUE/, blue).replace(/GREEN/, green))
}, 50);
});