Good Morning,
I have a problem with for loop, I tried to make multiply progressbars on page and I try to triger them in one action:
function startProgressbaring(){
var progressArray = [
progress1 = document.querySelector('.progressbar-1'),
progress2 = document.querySelector('.progressbar-2'),
progress3 = document.querySelector('.progressbar-3'),
progress4 = document.querySelector('.progressbar-4'),
progress5 = document.querySelector('.progressbar-5'),
progress6 = document.querySelector('.progressbar-6')
];
var durationArray = [
progress1_Duration = 90,
progress2_Duration = 65,
progress3_Duration = 70,
progress4_Duration = 55,
progress5_Duration = 95,
progress6_Duration = 90
];
var id = setInterval(frame,30);
var width = 1;
function frame(){
for(var z=0; z<durationArray.length; z++){
if(width>durationArray[z]){
clearInterval(id);
console.log(durationArray[z]);
}else {
width++;
progressArray[z].style.width = width + '%';
}
}
};
Console Log with durationArray returns only 55, for example
six progressbars have values between 56 and 60 not the values from durationArray,
no idea whats going wrong, thanks for any advices
The problem is, that you do
clearInterval(id);
if one of the frames reaches its width. That automatically stops the others too. May just stop if all have ended:
function frame(){
var running=false;
for(var z=0; z<durationArray.length; z++){
if(width>durationArray[z]){
console.log(durationArray[z]);
}else {
running=true;
width++;
progressArray[z].style.width = width + '%';
}
}
if(!running) clearInterval(id);
};
I don't understand what you are trying to accomplish, but the code can be explained. You are not setting the width of your progress bars to any values in durationArray, you are setting your progress bars to the width value: progressArray[z].style.width = width + '%';. width will eventually be larger than the smallest value in the durationArray. When this happens, you stop the interval with clearInterval. width will increase a couple more times while for loop iterates for the last time.
add a single line of logging to your code and observer the output (see below).
Basicall what happens is:
your width starts with value 1, and increases with each iteration
after iterating over the 6 item array 9 times, the value of width is
54
your iteration reaches progress4_Duration with width = 58 (54 + 4)
width > durationArray[z] condition will be true, interval gets cleared, processing stops.
Assuming you only need to stop the iteration for a particular progressbar, probably you need an array of process ids and only stop the one that has sufficent width.
function startProgressbaring(){
var progressArray = [
progress1 = document.querySelector('.progressbar-1'),
progress2 = document.querySelector('.progressbar-2'),
progress3 = document.querySelector('.progressbar-3'),
progress4 = document.querySelector('.progressbar-4'),
progress5 = document.querySelector('.progressbar-5'),
progress6 = document.querySelector('.progressbar-6')
];
var durationArray = [
progress1_Duration = 90,
progress2_Duration = 65,
progress3_Duration = 70,
progress4_Duration = 55,
progress5_Duration = 95,
progress6_Duration = 90
];
var intervalArray = [];
durationArray.forEach(function(d) {
intervalArray.push(setInterval(frame,1000))
});
//var id = setInterval(frame,1000);
var width = 1;
function frame(){
for(var z=0; z<durationArray.length; z++){
// debug output
//console.log(width, durationArray[z]);
if(width>durationArray[z]){
//clearInterval(id);
clearInterval(intervalArray[z]);
console.log(durationArray[z]);
} else {
width++;
progressArray[z].style.width = width + '%';
}
}
}
}
Related
I'm creating a small game in javascript and I'm using svg for the graphics. Right now I'm having a problem with updating the game in the middle of a game tick. If I exit my loop directly after I update the fill attribute with "setAttributeNS", it's redrawn, but if I don't do that, it isn't updated until after "game_tick" is over. Even worse, if I call "game_tick" multiple times in a row, the svg objects aren't updated until after I've run all of the "game_tick"s instead of being updated after each one.
function game_tick(){
num_grid_copy = num_grid.slice();
for (var x = 0; x < num_squares_x; x += 1) {
for (var y = 0; y < num_squares_x; y += 1) {
var n = get_neighbors(x,y);
var isAliveInNextGen = next_gen(n, num_grid[x*num_squares_x+y]);
num_grid_copy[x*num_squares_x+y] = isAliveInNextGen;
if (isAliveInNextGen == 1){
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#0099ff');
}
else {
rect_grid[x*num_squares_x+y].setAttributeNS(null, 'fill', '#fff');
}
}
}
num_grid = num_grid_copy;
}
Thanks to valuable input from Robert I realized that javascript execution and page rendering are done in the same thread. I changed the function to the following:
function start() {
var inc = 0,
max = 25;
delay = 100; // 100 milliseconds
var repeat = setInterval(function() {
game_tick();
if (++inc >= max)
clearInterval(repeat);
},
delay);
}
This works fine. I can set the delay and the number of times it repeats.
I want to put like 30 of <img class="anger"> elements with random size and random position inside the <div> container, but none of the .anger hitting one another.
Is it possible?
This is my code so far:
function loadAngers() {
var wrp = '#angerContainer'; //the container
var rectAvatar = $('#picAvatar')[0].getBoundingClientRect(); //rect of user avatar
var rectWrapper = $(wrp)[0].getBoundingClientRect(); //rect of container
listCoorditaes = [[
rectAvatar.width,
(rectAvatar.left+rectAvatar.right)/2,
(rectAvatar.top+rectAvatar.bottom)/2
]];
$(wrp).find('.anger').remove();
for (var i=0; i<listAnger.length; i++) {
var verb = listAnger[i].replace('assets/img/verb/','').replace('.png','').replace('-',' ');
var anger = $('<img src="'+listAnger[i]+'" class="anger hvr-'+getRandom(listAnim)+'" data-verb="'+verb+'" style="position:absolute">');
var paddingX = 100;
var paddingY = 200;
var wideX = rectWrapper.width - paddingX;
var wideY = rectWrapper.height - paddingY - rectAvatar.top;
var width = Math.round(30 + Math.random() * 70);
var left;
var top;
var x;
var y;
var tubrukan;
var coba = 0;
do { //find the best coordinate
tubrukan = false;
coba++;
x = Math.round(Math.random() * wideX) + paddingX/2;
y = Math.round(Math.random() * wideY) + paddingY/2 + rectAvatar.top;
left = x - width/2;
top = y - width/2;
for (var j=0; j<=i; j++) {
var cekW = listCoorditaes[j][0];
var cekX = listCoorditaes[j][1];
var cekY = listCoorditaes[j][2];
var difX = Math.abs( x - cekX );
var difY = Math.abs( y - cekY );
if (difX < cekW && difY < cekW) {
tubrukan = true;
break;
}
}
}
while(tubrukan && coba<3); //as I give up for eternal loop, I limit the loop with 3 tries.
listCoorditaes.push([width,x,y]);
anger.css('width',width+'px');
anger.css('left',left);
anger.css('top',top);
anger.appendTo(wrp);
}
}
This is the current result:
As we can see, the elements still overlap the other because I limit the loop. If I remove the limit, browser will endure endless loop and become not responding.
Do you have another better way to achieve it?
UPDATE:
My bad, I set the container height only 800px, that's why it can't contain all those <img> without overlapping, thus eternal loop happen. I made it to 2000px to see if it works. But the problem is still takes uncertain number of loops to find the best coordinate, so I still put limit to the loop thus overlap still happen several times.
I have been working on an issue for a few days now and have been unable to solve it. Please note that I am relatively new to Javascript so not sure if what I have below is the best way to accomplish this but going through this over the last few days has definitely helped me learn some new things.
Also, please note I know I could achieve this very easily using CSS but I wanted to know if there was a Javascript/JQuery solution.
The Issue:
I am attempting to simulate a fadeIn animation on a canvas for some text.
var introText =
{
"Welcome To A New Day...": "75",
"Full Service Web Design": "50",
"by": "50",
"J. David Hock": "50"
};
The numbers represent font size.
Here is the full code:
$(document).ready(function ()
{
var canvas = $('#myCanvas')[0];
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var ctx = canvas.getContext('2d');
var introText =
{
"Welcome To A New Day...": "75",
"Full Service Web Design": "50",
"by": "50",
"J. David Hock": "50"
};
function fadeText(timeStamp, t, x, y)
{
var opacity = timeStamp / 1000;
console.log('Timestamp: ' + timeStamp + ' Opacity: ' + opacity);
console.log('t, x, y |' + t +' | ' + x + ' | ' + y)
//ctx.clearRect(0, 0, canvas.width, canvas.height);
//ctx.fillStyle = 'rgba(178, 34, 34, ' + opacity + ')';
//ctx.fillText(t, x, y);
if (opacity < 1)
{
requestAnimationFrame(function (timestamp)
{
fadeText(timestamp, t, x, y)
});
}
}
function MessageObject(x, y, f, fs, t)
{
this.x = x;
this.y = y;
this.f = f;
this.fs = fs;
this.t = t;
}
var msgArray = [];
function CreateMessageArray(myArray, callback)
{
var i = 0;
var v = 75;
var x = 0;
var y = 0;
var f = '';
var fs = '';
var t = '';
for (t in myArray)
{
fs = myArray[t]; //font size
f = 'italic ' + fs + 'px Bradley Hand'; //font type
x = (canvas.width / 2) //x pos of text
msgArray.push(new MessageObject(x, y, f, fs, t))
y = Number(fs);
//alert('x, y, f, t | ' + x + ' | ' + y + ' | ' + f + ' | ' + t);
}
return callback(msgArray);
}
let ProcessMessageArray = function (myArray)
{
var xPrime = 0;
var yPrime = 0;
for (i = 0; i < myArray.length; i++)
{
var msgObject = myArray[i];
var x = msgObject.x;
var y = msgObject.y;
var f = msgObject.f;
var fs = msgObject.fs;
var t = msgObject.t;
ctx.textBaseline = 'top';
ctx.font = f;
var txtWidth = ctx.measureText(t).width
xPrime = x - (txtWidth / 2);
requestAnimationFrame(function(timestamp)
{
fadeText(timestamp, t, x, y)
});
//ctx.fillStyle = 'rgba(178, 34, 34, 1)';
//ctx.fillText(t, xPrime, yPrime);
if (i === 0)
{
yPrime = Number(yPrime) + (2 * Number(fs) - 35);
}
else
{
yPrime = Number(yPrime) + Number(fs);
}
}
}
CreateMessageArray(introText, ProcessMessageArray)
});
The way it is supposed to work is that the CreateMessageArray function creates an array of objects that contain the x-pos, y-pos, etc. for each of the lines of text in the introTextArray.
The ProcessMessageArray is then responsible for outputting each line of text in the introTextArray into it proper position on the screen.
In the ProcessMessageArray there is a call to a requestAnimationFrame function where I was hoping it would "fade in" each line of the text but what is actually occurring is that I am only getting the last line of text in the IntroTextArray.
I am sure it has to do with the fact that I am calling a requestAnimationFrame within a loop but I am not sure how to accomplish what I want to otherwise. Any advice would be appreciated.
Animating with requestAnimationFrame
RAF requestAnimationFrame
There are many ways that you can use RAF to animate. You can have many RAF functions that will all present the content to the display at the same time if they are all called before the next refresh. But this does incur extra overhead (especially if the rendering load per item is small) and extra head pain. If you have a slow render (for whatever reason, browsers can hang for a 1/60th second without warning) it can mess up the presentation order and you will only get part of the frame rendered.
The easiest way to manage any type of animation is to have a single animation loop that is called via RAF. From that function you call your animations. This is safe from presentation order problems, and can easily switch state. (eg after text fade you may want something else fading in)
Fix
Your code was not able to be saved, and sorry, was DOA, so I rewrote the whole thing. I have added some notes in the comments as to why and what for but if you have questions please do ask in the comments.
The example displays the fading text as two sets (to illustrate changing display state, a state is an abstract and reference to related elements or render (eg intro, middle, end))
The function has one main loop that handles the rendering calls and the timing.
Text is displayed by a function that updates and then displays. it will return true when text has faded in so main loop can set up the next render state.
// Removed jQuery and I dont see the need to use it
// The convention in JavaScript is NOT to capitalize the first character
// unless you function / object is created using new ObjectName
const ctx = canvas.getContext("2d"); // canvas is named in HTML via its id
canvas.width = innerWidth; // window is the global scope you do not need
canvas.height = innerHeight; //to prefix when accesing its properties
// helper
//const log = (...data) => console.log(data.join(","));
// use an array it is better suited to the data you are storing
const introText = [
["Testing one two.", 75],
["Testing..." , 50],
["One, one two.", 50],
["Is this thing on?", 50],
["",1], // to delay next state
["",1], // to delay next state
];
const introText1 = [
["Second stage, state 2", 20],
["The End" , 40],
[":)", 30],
["",10],
["Thanks for watching..",12],
["",1],
["",1],
["Dont forget to vote for any answers you find helpfull..",10],
["",1],
["",10],
["This intro was brought to you by",12],
["",10],
["requestAnimationFrame",12],
["HTML5",12],
["canvas",12],
["JavaScript (AKA) ECMAScript6",12],
["",10],
["Staring...",14],
["Stackoverflow.com",18],
];
const TEXT_FADE_TIME = 1000; // in ms
// create the array of display arrays
const displayLists = [
createDisplayList(8, introText), // converts simple text array to display list
createDisplayList(8, introText1),
];
var curretDisplayListIdx = 0;
requestAnimationFrame(mainLoop); // will start when all code has been parsed
var startTime;
function mainLoop(time){ // keep it simple use one animation frame per frame
if(startTime === undefined) { startTime = time }
const timeSinceStart = time - startTime;
ctx.clearRect(0,0, canvas.width, canvas.height);
if (textFadeIn(timeSinceStart, curretDisplayListIdx )) {
if (curretDisplayListIdx < displayLists.length - 1) {
curretDisplayListIdx += 1;
startTime = time;
}
}
requestAnimationFrame(mainLoop);
}
// creates a display list from text array. top is the start y pos
function createDisplayList(top, array) {
const result = [];
var y = top;
var fontSize;
for (const item of array) {
const fontSize = item[1];
result.push({
font : 'italic ' + fontSize + 'px Bradley Hand',
text : item[0],
x : canvas.width / 2,
y , fontSize,
startTime : null,
fadeTime : TEXT_FADE_TIME,
alpha : 0, // starting alpha
});
y += fontSize;
}
return result;
}
// displays a text display list from the displayLists array.
// time is time since starting to display the list
// displayListIdx is the index
// returns true when text has faded in
function textFadeIn(time, displayListIdx) {
// complete will be true when all have faded in
const complete = updateDisplayList(displayLists[displayListIdx], time);
renderDisplayList(displayLists[displayListIdx]);
return complete;
}
// separate logic from rendering
function updateDisplayList(array, time) {
var fadeNext = true; // used to indicate that the next item should fade in
// for each item
for (const text of array) {
if (fadeNext) { // has the previous items done its thing?
if (text.startTime === null) { text.startTime = time }
}
if(text.startTime !== null){ // if start time is set then fade it in
text.alpha = Math.min(1, (time - text.startTime) / text.fadeTime);
if (text.alpha < 1) { fadeNext = false } // if not complete flag fadeNext to stop next text fading in
}
}
// if last item is fully faded in return true
return array[array.length - 1].alpha === 1;
}
// seperate rendering from logic
function renderDisplayList(array) {
ctx.textBaseline = "top";
ctx.textAlign = "center";
ctx.fillStyle = "black";
for (const text of array) {
ctx.font = text.font;
ctx.globalAlpha = text.alpha;
ctx.fillText(text.text,text.x,text.y);
}
}
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
I'm making a basic javascript 'game', where objects (enemies) travel down from the top of the container, and a 'ship' shoots 'bullets' at the objects. There are many bullets and many enemies. I'm pushing every enemy object into an array, and the Y (top) value changes and is pushed into the array and updated with every change.
This is an interval that pushes the enemy down on the screen, and updates its _top value in the array.
enemyCoord = {
_left : parseInt(enemy.style.left, 10),
_top : enemyTop
}
enemyArray.push(enemyCoord);
var enemyInterval = setInterval(enemyMovement, 7 );
function enemyMovement(){
enemyTop += 1;
enemyArray[enemyCount]._top = enemyTop; // enemyCount is unique number given to each enemy object
enemy.style.top = enemyTop + 'px';
if (enemyTop >= document.getElementById('gc_1').offsetHeight -20 ) {
clearInterval(enemyInterval);
container.removeChild(enemy);
}
}
This is the bullet movement and where I attempt to make the values meet
function shoot(bullet, topValue) {
var topValue = parseInt(topValue, 10);
var bulletTop = topValue;
var bulletInterval = setInterval(bulletMovement, 7);
var arrayLength = enemyArray.length;
function bulletMovement (){
bulletTop = bulletTop - 1 ;
bullet.style.top = bulletTop + 'px';
if (bulletTop <= 20) {
var container = document.getElementById('gc_1');
container.removeChild(bullet);
clearInterval(bulletInterval);
}
// This is the part I really need help with! Where the values meet
for (var i=0;i<arrayLength;i++) {
if (bulletTop === enemyArray[i]._top) {
alert('we met :)');
}
}
}
}
How do I compare the parsed top style value of the bullets and the enemies? each bullet is checking against the array of enemy objects top value to see if it equals the bullets top value, but it doesn't work. How would I do this in a more efficient way?
The behaviour I want is this: The background color changes to say, gold, and remains that color for say X length of time. Then, background color changes to say, red, and remains that color for say Y length of time. The background color then changes back to gold and remains that color for X length of time. Then the background color changes back to red and stays that way for Y length of time. This whole kit and caboodle executes in a loop-style fashion for Z number of times and then ends.
I've tried putting setInterval'd functions into a for loop (in order to count the number of times we make the change) but have found that all of the functions that have been set to setInterval themselves all start running the interval timers at the same time (not in sequence).
I hope this is clear. Here is a JSFiddle of my efforts: http://jsfiddle.net/6WE6s/3/ I've managed to get the background color to change in a even pattern, but I want the pattern described above and I'm confused as to what to do next.
Thanks in advance for the help! :)
var colors = [
['gold', 2000], // X = 2000 miliseconds
['red', 1000] // Y = 1000
],
repeat = 3, // Z = 3,
index = 0, // current position in colors array
changeColor = function( ) {
// if index == colors.length then mod = 0
var mod = index % colors.length;
if(!index || mod || --repeat ) {
index = mod;
var data = colors[ index++ ]; // data = [ currentColor, currentColorTimeout ]
document.body.style.background = data[0];
setTimeout( changeColor, data[1] ); // and so on
}
//if index >0 && index == last
//then decrement `repeat` and check if is == 0
//nothing to do :)
};
changeColor(); // run
This is a simple example. You can make function with arguments(colors,repeats) and its body as above.
Note:
setInterval isn't suitable for this purpose because in setInterval you pass timeout once
If repeat initially is 0 will be an infinite number of repetitions
Don't use setInterval(). With setTimeout() you can do something like this:
function changeColors(colors, repeats) {
var i = 0;
if (typeof repeats === "undefined")
repeats = 1;
function doNext() {
if (i >= colors.length){
if (--repeats > 0)
i = 0;
else
return;
}
$('body').css('background-color', colors[i].color);
setTimeout(doNext, colors[i++].delay);
}
doNext();
}
changeColors([{color : "gold", delay : 2000},
{color : "red", delay : 4000}],
3);
You can add as many colours as you like, each with their own delay, by adding more elements to the array you pass to changeColors(). The function will go through the colours in turn, and do the whole sequence the number of times specified in the repeats parameter.
Demo: http://jsfiddle.net/nnnnnn/6WE6s/10/
Here's my effort - no jQuery required:
function colorCycle(el, count, cols) {
var i = 0,
n = cols.length;
// allow this to work on any element given its ID
el = (typeof el === "string") ? document.getElementById(el) : el;
if (n === 0) {
return; // no colours?
} else if (n === 1) {
count = 1; // don't trigger any timers if there's only one colour
}
// all of the hard work is done here
(function repeat() {
var interval = cols[i][1];
el.style.backgroundColor = cols[i][0];
// only do the whole cycle "count" times - 0 = forever
if (++i === n) {
if (count && !--count) {
return;
}
i = 0;
}
setTimeout(repeat, interval); // call myself
})(); // IIFE starts the cycle straight away
};
colorCycle(document.body, 5, [
['red', 1000],
['gold', 500]]);
See http://jsfiddle.net/alnitak/42PeT/
Abstain from using setInterval. Reference here.
EDIT: I've missed the different delay in calls.
var colors = ["#FF0000", "#00FF00", "#0000FF"];
var times = [1000, 2000, 3000];
var backgroundColor = "";
var counter = 0;
var changeBackground = function () {
// if we ran out of colors — do nothing: this simply goes out
// of the function, without continually calling setTimeout.
if (counter >= colors.length)
return;
// you fetch your new color here and increase the counter
// The counter keeps count of how many animations you've done.
backgroundColor = colors[counter];
// increase the counter to point to the next index of colors
// array you'll use in a subsequent call
counter++;
// do your magic voodoo change background animation here.
// I'm just doing a console.log() to be sure this works.
// Your question was framework agnostic, the answer should be too.
console.log(backgroundColor);
// setInterval to repeat
window.setTimeout(changeBackground, times[counter]);
}
window.setTimeout(changeBackground, times[counter]);
try this
var colors = [];
colors.push({color:"gold", time:4000}); //4000 X length of time
colors.push({color:"red", time:2000}); //2000 Y length of time
var numberofTimes = 50; //50 Z number of times
var $body;
var times = 0; // counter for tracking
var currentColor = {}; //currentColor info can be used to get the current
$(function(){
$body = $('body');
changeBG();
});
function changeBG()
{
currentColor = colors[times % colors.length];
$body.css('background-color',currentColor.color);
times++;
if(times<numberofTimes)
setTimeout(changeBG, currentColor.time);
}
check this quick DEMO
A basic example iterating an array of color and time arrays with setTimeout.
(function() {
var i = 0,
colorsTimes = [['gold', 'red', 'gold', 'red', 'gold'],
[2000, 4000, 2000, 4000, 2000]];
function switchColors() {
setTimeout(function() {
$('body').css('background-color', colorsTimes[0][i]);
if (++i < colorsTimes[0].length) switchColors();
}, colorsTimes[1][i]);
}
switchColors();
}());
Fiddle
Using setTimeout:
var doCount = (function() {
var count = 0;
var interval;
var limit = 5; // default
return function(num) {
limit = num || limit;
if (count < limit) {
count++;
console.log('running number ' + count);
interval = setTimeout(arguments.callee, 1000);
} else {
interval && clearTimeout(interval);
}
}
}())
Using setInterval:
var doCount = (function() {
var count = 0;
var interval;
var limit = 5; // default
return function(num) {
limit = num || limit;
if (interval) {
if (++count >= limit) {
interval && clearInterval(interval);
}
console.log('running number ' + count);
} else {
interval = setInterval(arguments.callee, 1000);
}
}
}())
The advantage of setTimeout is that you can adjust the time between runs to make it more regular, setInterval just tries to run as regularly as it can.