I am trying to create a JavaScript game. There, I have created a function that whenever we press the right arrow key, the DIV should move to the right. When I press it for the first time, it works fine. However, when I press it after that, it does not work. Here is the code I have tried so far:
<html>
<head>
<style>
.runobj {
width: 80px;
height: 80px;
position: absolute;
background-color: #00ff00;
top: 0;
left: 0;
}
</style>
</head>
<body onkeydown="moveObj(event)">
<div class="runobj" id="runobj"></div>
<script>
function moveObj(event) {
var runobj = document.getElementById("runobj");
if (event.keyCode === 37) {
runobj.style.left -= 10;
} else if (event.keyCode === 39) {
runobj.style.left += 10;
}
}
</script>
</body>
</html>
What am I doing wrong here? Any help would be appreciated. Thankyou!
style.left is a string property with a unit (ie: "10px"). To add or take units of it you first need to parse it to a number or use another property (ie: offsetLeft) and after assign it back with the unit.
function moveObj(element, event){
console.log('keycode', event.keyCode);
//REM: Looking up the same element all the time is not ideal. Maybe pass it instead?
var runobj = element || document.getElementById("runobj");
//REM: You need to turn "style.left" into a number
var tCurrentLeft = parseFloat(runobj.style.left) || 0;
//REM: Just use a switch, since it looks like you are going to implement more key actions
switch(event.keyCode){
case 37:
//REM: Do not forget to add the unit
runobj.style.left = tCurrentLeft - 10 + 'px';
break
case 39:
//REM: Do not forget to add the unit
runobj.style.left = tCurrentLeft + 10 + 'px'
};
console.log('left', runobj.style.left)
};
//REM: It is better to fully split the javascript from the HTML markup. Also you can just pass the element and avoid lookups.
window.addEventListener('keydown', moveObj.bind(this, document.getElementById("runobj")));
.runobj {
width: 80px;
height: 80px;
position: absolute;
background-color: #00ff00;
top: 0;
left: 0;
}
<body>
<div class="runobj" id="runobj"></div>
</body>
Modified your code to handle the postfix px for left attribute:
<html>
<head>
<style>
.runobj {
width: 80px;
height: 80px;
position: absolute;
background-color: #00ff00;
top: 0;
left: 0;
}
</style>
</head>
<body onkeydown="moveObj(event)">
<div class="runobj" id="runobj"></div>
<script>
var _left = 0;
function moveObj(event) {
var runobj = document.getElementById("runobj");
if (event.keyCode === 37) {
_left -= 10;
} else if (event.keyCode === 39) {
_left += 10;
}
runobj.style.left = _left + 'px';
}
</script>
</body>
</html>
Make the function recursive so that on every click event it should moved automatically without refreshing it to the initials .
After the first modification, the value of the left property is the string '10px'. So to continue adding values, you need to extract that value before adding a new values, for example:
runobj.style.left = parseInt(runobj.style.left || 0, 10) + 10;
(The '|| 0' is for the first iteration, because it receives an empty string)
// cache the element
var runobj = document.getElementById("runobj");
function moveObj(event) {
var left = runobj.getBoundingClientRect().left;
if (event.keyCode === 37) {
left -= 10;
} else if (event.keyCode === 39) {
left += 10;
}
runobj.style.left = left + "px";
}
Working example
We need to convert style.left from String datatype to Number datatype inorder to increment or decrement the value.
After the first modification, the value of the left property is the string '10px'.
So when you tried to increment style.left by number 10 after the first modification, it becomes 10px10 which is not a valid value for style.left property.That's why it only works for the first time.
var runobj = document.getElementById("runobj");
function moveObj(event) {
/* Converting the left style value from String type to Number type.
'|| 0' is for the first iteration, because it receives an empty string */
var current = parseFloat(runobj.style.left) || 0;
if (event.keyCode === 37) {
current += 20;
} else if (event.keyCode === 39) {
current -= 20;
}
// Updating the value
runobj.style.left = current;
}
Related
I'm trying to make a game and still in the opening stages, I have the character and the code I thought would work, except when I run it nothing happens. I cant seem to find an error in it.
document.body.onkeydown = function(event){
var k = event.keyCode;
var chr = {
updown: function (){
var y = 0;
if (k==38) {
--y;
} else if (k == 40) {
++y;
}
return y;
},
leftright: function (){
var x = 0;
if (k == 37) {
--x;
} else if (k == 39) {
++x;
}
return x;
}
};
rogue.style.top = (chr.updown()) + "px";
rogue.style.left = (chr.leftright()) + "px";
}
#rogue {
position: relative;
top: 0px;
left: 0px;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="jumpOPP.css">
<script src="jumpOPP.js"></script>
</head>
<body>
<img id="rogue" src="http://piq.codeus.net/static/media/userpics/piq_143310_400x400.png" >
</body>
</html>
I would appreciate any help at all.
Wrap your function like this:
document.body.onkeydown = function(event){
// Your event here
};
Take the handler off the body tag too.
JSFIDDLE
Change your function as below (its just a sample)
Suggested reading:
Retrieve the position (X,Y) of an HTML element
...
updown: function (){
var y = CURRENT_Y; // Get the current Y position of the image HERE
if (k==38) {
--y;
} else if (k == 40) {
++y;
}
return y;
},
leftright: function (){
var x = CURRENT_X; // Get the current X position of the image HERE
if (k == 37) {
--x;
} else if (k == 39) {
++x;
}
return x;
}
The logic you're using to set the top and left values is a little off. Note also that you need to parse the value to an integer so that you can add/remove from its value, and also provide a default.
Try this:
document.body.onkeydown = function(event){
var rogue = document.getElementById('rogue');
var currentY = parseInt(rogue.style.top || 0, 10);
var currentX = parseInt(rogue.style.left || 0, 10);
var speed = 3;
switch (event.keyCode) {
case 38: // up;
rogue.style.top = (currentY - speed) + 'px';
break;
case 40: // down;
rogue.style.top = (currentY + speed) + 'px';
break;
case 37: // left;
rogue.style.left = (currentX - speed) + 'px';
break;
case 39: // right;
rogue.style.left = (currentX + speed) + 'px';
break;
}
}
Example fiddle
Perfectly Working Answer: Run the Code Snippet and check
//bind an event when the user presses any key
window.onkeydown = function (e) {
if (!e) {
e = window.event;
}
//The event object will either be passed
//to the function, or available through
//window.event in some browsers.
var code = e.keyCode;
//that's the code of the key that was pressed.
//http://goo.gl/PsUij might be helpful for these.
//find our rouge image
var rouge = document.getElementById("rouge");
//get the image's current top and left position.
//rouge.style.top will find the top position out of our
//style attribute; parseInt will turn it from for example '10px'
//to '10'.
var top = parseInt (rouge.style.top, 10);
var left = parseInt (rouge.style.left, 10);
//We'll now compare the code that we found above with
//the code of the keys that we want. You can use a chart
//like the one in http://goo.gl/PsUij to find the right codes,
//or just press buttons and console.log it yourself.
if ( code == 37 ) { //LEFT
//time to actually move the image around. We will just modify
//its style.top and style.left accordingly. If the user has pressed the
//left button, we want our player to move closer to the beginning of the page,
//so we'll reduce the 'left' value (which of course is the distance from '0' left)
//by 10. You could use a different amount to make the image move less or more.
//we're also doing some very basic boundary check to prevent
//the image from getting out of the page.
if ( left > 0 ) {
rouge.style.left = left - 10 + 'px';
}
} else if ( code == 38 ) { //UP
//if we pressed the up button, move the image up.
if ( top > 0 ) {
rouge.style.top = top - 10 + 'px';
}
} else if ( code == 39 ) { //RIGHT
//move the image right. This time we're moving further away
//from the screen, so we need to 'increase' the 'left' value.
//the boundary check is also a little different, because we're
//trying to figure out if the rightmost end of the image
//will have gone
//further from our window width if we move it 10 pixels.
if ( left+rouge.width+10 < window.innerWidth ) {
rouge.style.left = left + 10 + 'px';
}
} else if ( code == 40 ) { //DOWN
if ( top+rouge.height+10 < window.innerHeight ) {
rouge.style.top = top + 10 +'px';
}
}
}
<img src="http://piq.codeus.net/static/media/userpics/piq_143310_400x400.png" id="rouge" style="position:absolute;top:0px;left:0px" />
Try this :
Create an array to hold key states : window.keyStates[];
Update the keyStates var with onkeyup / onkeydown events.
document.body.onkeydown = function (event) {
window.keyStates[event.keyCode] = true;
}
document.body.onkeyup = function (event) {
window.keyStates[event.keyCode] = false;
}
Create a loop to loop game :
var mainLoop = function () {
if (window.keyStates[38]) dostuff ();
if (window.keyStates[40]) dootherstuff ();
// etc....
}
window.setInterval(function() {mainLoop()}, 100);
The code is glitchy but you get the idea ? This way you can move your toon 2 directions at the same time too. Or manage virtually any key pressed at the same time.
I'm trying to create an arcade style name input for a highscore list. I'd like it to work using just the arrow keys on the keyboard, so up/down arrow for character selection and left/right for input box selection. I'm using a MakeyMakey for this project, so I want to keep user/keyboard interaction as minimal as possible.
Right now I'm rendering select boxes containing the entire alphabet in option tags, however, a select box always drops down, and your choice has to be confirmed using Enter or a mouse-click.
I'd like to stay at least a little semantically correct on the input, so I'm thinking 3 inputs that each select one letter, then concatenating these in a hidden input field, of which I can pass the value to Rails.
I'm using the following jQuery-plugin (from: http://ole.michelsen.dk/blog/navigate-form-fields-with-arrow-keys.html) to navigate between select boxes using the left/right arrow keys.
(function($) {
$.fn.formNavigation = function() {
$(this).each(function() {
$(this).find('select').on('keyup', function(e) {
switch (e.which) {
case 39:
$(this).closest('td').next().find('select').focus();
break;
case 37:
$(this).closest('td').prev().find('select').focus();
}
});
});
};
})(jQuery);
What I want to implement next is three input boxes that have the entire (infinitely looped) alphabet as choices, navigable by up/down arrow. I'd rather not type out the entire alphabet so I want to generate the options for each box and probably bind a keyhandler somewhere. How would I generate such an input box and integrate the required code into my existing script?
Screenshots
This is how it looks now:
Ideally, it will look like this:
I think Banana had a great answer, but I wanted to try making it look a little more expressive.
KEYCODES = { left: 37, up: 38, right: 39, down: 40 };
$('.cyclic_input').on('keydown',function(ev){
input = $(this);
val = $(this).text();
switch (ev.keyCode) {
case KEYCODES.right:
input.next().focus();
break;
case KEYCODES.left:
input.prev().focus();
break;
case KEYCODES.up:
input.text(advanceCharBy(val, 1));
break;
case KEYCODES.down:
input.text(advanceCharBy(val, -1));
break;
default:
if (ev.keyCode >= 65 && ev.keyCode <= 65 + 26) {
input.text(String.fromCharCode(ev.keyCode));
input.next().focus();
}
};
ev.preventDefault();
});
advanceCharBy = function(char, distance) {
oldCode = char.charCodeAt(0);
newCode = 65 + (oldCode - 65 + 26 + distance) % 26;
return String.fromCharCode(newCode);
};
.cyclic_input {
float: left;
padding: 1rem;
font-size: 5rem;
position: relative;
display: table-cell;
vertical-align: middle;
text-align: center;
}
.cyclic_input:before,
.cyclic_input:after {
display: block;
position: absolute;
left: 50%;
font-size: 1rem;
transform: translateX(-50%);
}
.cyclic_input:before {
content: '▲';
top: 0;
}
.cyclic_input:after {
content: '▼';
bottom: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="cyclic_input" tabindex="0">A</div>
<div class="cyclic_input" tabindex="0">A</div>
<div class="cyclic_input" tabindex="0">A</div>
Forked Codepen but also works fine in the snippet. My thoughts:
I don't know keycodes, I don't want to know keycodes.
We're not interested in keypresses on the whole document, just the initials inputs.
If you must have branching logic, be clear how many paths there are, one.
If these are our form inputs, then they deserve at least tabindex. In reality I would use <input>s but that would have just added a bunch of CSS to the demo.
The whole point is keyboard support. I'm pretty sure we need to handle the case where a user types a letter.
Let's not jerk the page around when someone presses an arrow key.
Its rather simple, all you need is a few calculations:
if you have any questions, feel free to ask.
$(function() {
$(document).on("keydown", function(ev) {
if (ev.keyCode == 39) {
var idx = $('.active').index();
var next_idx = (+idx + 1) % $('.cyclic_input').length;
$('.active').removeClass('active');
$('.cyclic_input').eq(next_idx).addClass('active');
}
if (ev.keyCode == 37) {
var idx = $('.active').index();
var next_idx = ((+idx - 1) + $('.cyclic_input').length) % $('.cyclic_input').length;
$('.active').removeClass('active');
$('.cyclic_input').eq(next_idx).addClass('active');
}
if (ev.keyCode == 38) {
var char = $(".active").text();
var ascii = char.charCodeAt(0);
var nextAscii = 65 + (ascii + 1 - 65) % 26;
$(".active").text(String.fromCharCode(nextAscii));
}
if (ev.keyCode == 40) {
var char = $(".active").text();
var ascii = char.charCodeAt(0);
var nextAscii = 65 + ((ascii - 1 - 65) + 26) % 26;
$(".active").text(String.fromCharCode(nextAscii));
}
});
});
.cyclic_input {
width: 50px;
height: 50px;
display: inline-block;
border: 5px solid red;
font-size: 3em;
}
.active {
border-color: blue;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="cyclic_input active">A</div>
<div class="cyclic_input">A</div>
<div class="cyclic_input">A</div>
and here is a Fiddle
PS: the snippet here doesnt work too well, but if you test the code or check the jsfiddle it will work fine.
I created a sliding puzzle with different formats like: 3x3, 3x4, 4x3 and 4x4. When you run my code you can see on the right side a selection box where you can choose the 4 formats. The slidingpuzzle is almost done. But I need a function which checks after every move if the puzzle is solved and if that is the case it should give out a line like "Congrantulations you solved it!" or "You won!". Any idea how to make that work?
In the javascript code you can see the first function loadFunc() is to replace every piece with the blank one and the functions after that are to select a format and change the format into it. The function Shiftpuzzlepieces makes it so that you can move each piece into the blank space. Function shuffle randomizes every pieces position. If you have any more question or understanding issues just feel free to ask in the comments. Many thanks in advance.
Since I don't have enough reputation I will post a link to the images here: http://imgur.com/a/2nMlt . These images are just placeholders right now.
Here is the jsfiddle:
http://jsfiddle.net/Cuttingtheaces/vkyxgwo6/19/
As always, there is a "hacky", easy way to do this, and then there is more elegant but one that requires significant changes to your code.
Hacky way
To accomplish this as fast and dirty as possible, I would go with parsing id-s of pieces to check if they are in correct order, because they have this handy pattern "position" + it's expected index or "blank":
function isFinished() {
var puzzleEl = document.getElementById('slidingpuzzleContainer').children[0];
// convert a live list of child elements into regular array
var pieces = [].slice.call(puzzleEl.children);
return pieces
.map(function (piece) {
return piece.id.substr(8); // strip "position" prefix
})
.every(function (id, index, arr) {
if (arr.length - 1 == index) {
// last peace, check if it's blank
return id == "blank";
}
// check that every piece has an index that matches its expected position
return index == parseInt(id);
});
}
Now we need to check it somewhere, and naturally the best place would be after each move, so shiftPuzzlepieces() should be updated to call isFinished() function, and show the finishing message if it returns true:
function shiftPuzzlepieces(el) {
// ...
if (isFinished()) {
alert("You won!");
}
}
And voilà: live version.
How would I implement this game
For me, the proper way of implementing this would be to track current positions of pieces in some data structure and check it in similar way, but without traversing DOM or checking node's id-s. Also, it would allow to implement something like React.js application: onclick handler would mutate current game's state and then just render it into the DOM.
Here how I would implement the game:
/**
* Provides an initial state of the game
* with default size 4x4
*/
function initialState() {
return {
x: 4,
y: 4,
started: false,
finished: false
};
}
/**
* Inits a game
*/
function initGame() {
var gameContainer = document.querySelector("#slidingpuzzleContainer");
var gameState = initialState();
initFormatControl(gameContainer, gameState);
initGameControls(gameContainer, gameState);
// kick-off rendering
render(gameContainer, gameState);
}
/**
* Handles clicks on the container element
*/
function initGameControls(gameContainer, gameState) {
gameContainer.addEventListener("click", function hanldeClick(event) {
if (!gameState.started || gameState.finished) {
// game didn't started yet or already finished, ignore clicks
return;
}
if (event.target.className.indexOf("piece") == -1) {
// click somewhere not on the piece (like, margins between them)
return;
}
// try to move piece somewhere
movePiece(gameState, parseInt(event.target.dataset.index));
// check if we're done here
checkFinish(gameState);
// render the state of game
render(gameContainer, gameState);
event.stopPropagation();
return false;
});
}
/**
* Checks whether game is finished
*/
function checkFinish(gameState) {
gameState.finished = gameState.pieces.every(function(id, index, arr) {
if (arr.length - 1 == index) {
// last peace, check if it's blank
return id == "blank";
}
// check that every piece has an index that matches its expected position
return index == id;
});
}
/**
* Moves target piece around if there's blank somewhere near it
*/
function movePiece(gameState, targetIndex) {
if (isBlank(targetIndex)) {
// ignore clicks on the "blank" piece
return;
}
var blankPiece = findBlankAround();
if (blankPiece == null) {
// nowhere to go :(
return;
}
swap(targetIndex, blankPiece);
function findBlankAround() {
var up = targetIndex - gameState.x;
if (targetIndex >= gameState.x && isBlank(up)) {
return up;
}
var down = targetIndex + gameState.x;
if (targetIndex < ((gameState.y - 1) * gameState.x) && isBlank(down)) {
return down;
}
var left = targetIndex - 1;
if ((targetIndex % gameState.x) > 0 && isBlank(left)) {
return left;
}
var right = targetIndex + 1;
if ((targetIndex % gameState.x) < (gameState.x - 1) && isBlank(right)) {
return right;
}
}
function isBlank(index) {
return gameState.pieces[index] == "blank";
}
function swap(i1, i2) {
var t = gameState.pieces[i1];
gameState.pieces[i1] = gameState.pieces[i2];
gameState.pieces[i2] = t;
}
}
/**
* Handles form for selecting and starting the game
*/
function initFormatControl(gameContainer, state) {
var formatContainer = document.querySelector("#formatContainer");
var formatSelect = formatContainer.querySelector("select");
var formatApply = formatContainer.querySelector("button");
formatSelect.addEventListener("change", function(event) {
formatApply.disabled = false;
});
formatContainer.addEventListener("submit", function(event) {
var rawValue = event.target.format.value;
var value = rawValue.split("x");
// update state
state.x = parseInt(value[0], 10);
state.y = parseInt(value[1], 10);
state.started = true;
state.pieces = generatePuzzle(state.x * state.y);
// render game
render(gameContainer, state);
event.preventDefault();
return false;
});
}
/**
* Renders game's state into container element
*/
function render(container, state) {
var numberOfPieces = state.x * state.y;
updateClass(container, state.x, state.y);
clear(container);
var containerHTML = "";
if (!state.started) {
for (var i = 0; i < numberOfPieces; i++) {
containerHTML += renderPiece("", i) + "\n";
}
} else if (state.finished) {
containerHTML = "<div class='congratulation'><h2 >You won!</h2><p>Press 'Play!' to start again.</p></div>";
} else {
containerHTML = state.pieces.map(renderPiece).join("\n");
}
container.innerHTML = containerHTML;
function renderPiece(id, index) {
return "<div class='piece' data-index='" + index + "'>" + id + "</div>";
}
function updateClass(container, x, y) {
container.className = "slidingpuzzleContainer" + x + "x" + y;
}
function clear(container) {
container.innerHTML = "";
}
}
/**
* Generates a shuffled array of id-s ready to be rendered
*/
function generatePuzzle(n) {
var pieces = ["blank"];
for (var i = 0; i < n - 1; i++) {
pieces.push(i);
}
return shuffleArray(pieces);
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
}
body {
font-family: "Lucida Grande", "Lucida Sans Unicode", Verdana, Helvetica, Arial, sans-serif;
font-size: 12px;
color: #000;
}
#formatContainer {
position: absolute;
top: 50px;
left: 500px;
}
#formatContainer label {
display: inline-block;
max-width: 100%;
margin-bottom: 5px;
}
#formatContainer select {
display: block;
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
#formatContainer button {
display: inline-block;
width: 100%;
}
.piece {
width: 96px;
height: 96px;
margin: 1px;
float: left;
border: 1px solid black;
}
.slidingpuzzleContainer3x3,
.slidingpuzzleContainer3x4,
.slidingpuzzleContainer4x3,
.slidingpuzzleContainer4x4 {
position: absolute;
top: 50px;
left: 50px;
border: 10px solid black;
}
.slidingpuzzleContainer3x3 {
width: 300px;
height: 300px;
}
.slidingpuzzleContainer3x4 {
width: 300px;
height: 400px;
}
.slidingpuzzleContainer4x3 {
width: 400px;
height: 300px;
}
.slidingpuzzleContainer4x4 {
width: 400px;
height: 400px;
}
.congratulation {
margin: 10px;
}
}
<body onload="initGame();">
<div id="slidingpuzzleContainer"></div>
<form id="formatContainer">
<label for="format">select format:</label>
<select name="format" id="format" size="1">
<option value="" selected="true" disabled="true"></option>
<option value="3x3">Format 3 x 3</option>
<option value="3x4">Format 3 x 4</option>
<option value="4x3">Format 4 x 3</option>
<option value="4x4">Format 4 x 4</option>
</select>
<button type="submit" disabled="true">Play!</button>
</form>
</body>
Here we have the initGame() function that starts everything. When called it will create an initial state of the game (we have default size and state properties to care about there), add listeners on the controls and call render() function with the current state.
initGameControls() sets up a listener for clicks on the field that will 1) call movePiece() which will try to move clicked piece on the blank spot if the former is somewhere around, 2) check if after move game is finished with checkFinish(), 3) call render() with updated state.
Now render() is a pretty simple function: it just gets the state and updates the DOM on the page accordingly.
Utility function initFormatControl() handles clicks and updates on the form for field size selection, and when the 'Play!' button is pressed will generate initial order of the pieces on the field and call render() with new state.
The main benefit of this approach is that almost all functions are decoupled from one another: you can tweak logic for finding blank space around target piece, to allow, for example, to swap pieces with adjacent ids, and even then functions for rendering, initialization and click handling will stay the same.
$(document).on('click','.puzzlepiece', function(){
var count = 0;
var imgarray = [];
var test =[0,1,2,3,4,5,6,7,8,'blank']
$('#slidingpuzzleContainer img').each(function(i){
var imgalt = $(this).attr('alt');
imgarray[i] = imgalt;
count++;
});
var is_same = (imgarray.length == test.length) && imgarray.every(function(element, index) {
return element === array2[index];
});
console.log(is_same); ///it will true if two array is same
});
try this... this is for only 3*3.. you pass the parameter and makethe array value as dynamically..
var bubble = [$('#bubble1'), $('#bubble2'), $('#bubble3'), $('#bubble4'), $('#bubble5'), $('#bubble6')];
var bubbleFirst = 0;
var visibleBubbles = 3;
var bubbleHeight = 200;
var bubbleTimeDelay = 5000;
var bubbleTimer = setTimeout(function(){animateBubbles();}, bubbleTimeDelay/2);
function animateBubbles(){
clearTimeout(bubbleTimer);//stop from looping before this is finished
for(var i = 0; i < visibleBubbles + 1; i++){
count = i + bubbleFirst;
if ( count >= bubble.length ) count = count - bubble.length;//keep index inside array
bubble[count].animate({top:'-=' + bubbleHeight}, 2000);
}
bubble[bubbleFirst].css('top', '600px');//put elements moving off top to bottom
//resetBubbles();
bubbleFirst++;
if(bubbleFirst >= bubble.length) bubbleFirst = 0;//bubbles have looped
bubbleTimer = setTimeout(function(){animateBubbles();}, bubbleTimeDelay);//start looping again
}
bubble1 starts with top: 0px;
bubble2 starts with top: 200px;
bubble3 starts with top: 400px;
bubble4-6 start with top: 600px;
all are position: absolute in a wrapper div
Apologies for the code dump. My problems are all centered around line 16:
bubble[bubbleFirst].css('top', '600px');
This code is seemingly never executed, there is no error in my console and I have verified that bubble[bubbleFirst] is returning the correct element using console.log. (It's a div)
I'm currently using a workaround, but it is not dynamic:
function resetBubbles(){
/*bubble[bubbleFirst].css('top', '600px');//put elements moving off top to bottom*/
if(bubble[0].css('top') == '-200px') bubble[0].css('top', '600px');
if(bubble[1].css('top') == '-200px') bubble[1].css('top', '600px');
if(bubble[2].css('top') == '-200px') bubble[2].css('top', '600px');
if(bubble[3].css('top') == '-200px') bubble[3].css('top', '600px');
if(bubble[4].css('top') == '-200px') bubble[4].css('top', '600px');
if(bubble[5].css('top') == '-200px') bubble[5].css('top', '600px');
}
I have no idea why this isn't working, it's probably a logical error on my part. But I can't for the life of me find it. Any help would be appreciated. Thanks for your time.
Is it possible that the call to animate conflicts with you custom settings the top property? ie. you set it, but top is immediately altered again by animate. You can pass a callback to animate that will run when the animation has finished. That's when you should reset your bubble position.
function animate_bubble(index) {
bubble[index].animate({ top: "0px" }, 2000 /* 2 seconds */, function () {
bubble[index].css({ top: "600px" });
animate_bubble(index);
}
}
animate_bubble(0);
animate_bubble(1);
// etc .. probably do this in a for-loop
You'll need to find some way to cancel the animation though, shouldn't be too hard.
The problem was that bubbleFirst was incremented before the animation was finished. I changed my function to the following:
function animateBubbles(){
clearTimeout(bubbleTimer);//stop from looping before this is finished
for(var i = 0; i < visibleBubbles + 1; i++){
count = i + bubbleFirst;
if ( count >= bubble.length ) count = count - bubble.length;//keep index inside array
if (count == bubbleFirst)
{
bubble[count].animate({top:'-=' + bubbleHeight, opacity: 0}, bubbleAnimationSpeed, function(){
bubble[bubbleFirst].css('top', displayHeight+'px');
bubble[bubbleFirst].css('opacity', '1');
});
}
else if(i == visibleBubbles)
{
bubble[count].animate({top:'-=' + bubbleHeight}, bubbleAnimationSpeed, function(){
bubbleFirst++;
if(bubbleFirst >= bubble.length) bubbleFirst = 0;//bubbles have looped
bubbleTimer = setTimeout(function(){animateBubbles();}, bubbleTimeDelay);/*start looping again*/
});
}
else bubble[count].animate({top:'-=' + bubbleHeight}, bubbleAnimationSpeed);
}
}
Thanks for your input guys!
Sorry about the confusing title, I'll explain better.
I have a 20x20 grid of div's, so its 400 of them each with an id, going from 0 to 399.
Each div is given one of three random values - red, green or blue - and when a div is clicked, a function is run to check if the div to the left, right, over and under are of the same value, if it is of the same value it will be simulated a click and the same function will run again.
The problem, is that the function sets vars, so if it finds that the div below has the same value, it will overwrite the vars set by the first click, hence never click any of the others.
JSfiddle - http://jsfiddle.net/5e52s/
Here is what I've got:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>untiteled</title>
<style>
body {
width: 420px;
}
.box {
width: 19px;
height: 19px;
border: 1px solid #fafafa;
float: left;
}
.box:hover {
border: 1px solid #333;
}
.clicked {
background: #bada55 !important;
}
</style>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$().ready(function(){
var colors = ['red', 'green', 'blue'];
var i = 0;
while(i<400){
var color = colors[Math.floor(Math.random() * colors.length)];
$('.test').append('<div class="box" id="'+i+'" value="'+color+'" style="background:'+color+';">'+i+'</div>');
i++;
}
$('.box').click(function(){
var t = $(this);
t.addClass('clicked');
id = t.attr('id');
val = t.attr('value');
//Set color
up = parseInt(id) - 20;
right = parseInt(id) + 1;
down = parseInt(id) + 20;
left = parseInt(id) - 1;
clickup = false;
clickdown = false;
if($('#'+down).attr('value') === val){
clickdown = true;
}
if(up > -1 && ($('#'+up).attr('value') === val)){
clickup = true;
}
if(clickdown == true){
$('#'+down).click();
}
if(clickup == true){
$('#'+up).click();
}
});
});
</script>
</head>
<body>
<div class="test">
</div>
</body>
I think the biggest root cause of your problem is you don't check if it already has class 'clicked' or not. That could make the infinite recursive. For example, if you click on the div#2 then the div#1 receives a simulated click, and div#2 receives a simulated click from div#1.
$('.box').click(function(){
var t = $(this);
if(t.hasClass('clicked')) {
return;
}
t.addClass('clicked');
var id = t.attr('id');
var val = t.attr('value');
//Set color
var up = parseInt(id) - 20;
var right = (id%20 != 19) ? ((0|id) + 1) : 'nothing' ;
var down = parseInt(id) + 20;
var left = (id%20 != 0) ? ((0|id) - 1) : 'nothing';
console.log(up, right, down, left);
if($('#'+down).attr('value') === val) {
$('#'+down).click();
}
if($('#'+right).attr('value') === val) {
$('#'+right).click();
}
if($('#'+up).attr('value') === val) {
$('#'+up).click();
}
if($('#'+left).attr('value') === val) {
$('#'+left).click();
}
});
You can schedule the clicks onto the event loop instead of calling them directly, eg:
if(clickdown == true){
setTimeout(function () {
$('#'+down).click();
});
}
I don't think that's your root cause though, it's probably a combination of global vars and scope issues. Try reformatting as such:
$('.box').click(function (event){
var t = $(this), id, val, up, right, down, left, clickup, clickdown;
//...
Your variables id and val are not in a var statement, thus are implicitly created as members of the window object instead of being scoped to the local function. Change the semicolon on the line before each to a comma so that they become part of the var statement, and your code should begin working.