I'm making a sliding puzzle (something like this) using HTML, CSS and JavaScript/jQuery.
Everything works fine, but I want to add a sliding transition each time a slide moves.
The code works based on the following logic:
12, 16 or 48 <div>s are created (depending on the level of difficulty).
A background-image is assigned to all of them, and cropped accordingly to simulate a "piece" of the original image.
They all have a class of image, and also piece<number>, where <number> is the number of the tile. The last tile (piece12, piece16 or piece48) also has a class of voidBlock, that removes the background-image. This is the empty tile that permits the ones directly next to it to move into its place. All the other tiles have an additional class, imgBlock.
When one of the tiles that are next to the voidBlock is clicked, it exchanges classes with the voidBlock. For example, if we were to click piece6, which happens to be next to the voidBlock(let's say the difficulty is set to easy, and thus voidBlock is piece12), this would happen:
The original voidBlock changes its piece12 class to piece6.
The original voidBlock changes its voidBlock class to imgBlock.
The original piece6 changes its piece6 class to piece12.
The original piece6 changes its imgBlock class to voidBlock.
After that, a draw() function is called; it sets the background-position of each tile, depending on its piece number.
As you might have guessed by now, none of the <div>s actually move. They remain still as the background-position of their background-image changes through their classes.
Although I do have some experience in programming (namely in Python and Java), web development in general is very new to me. I have tried various methods of transitioning, but the best thing I could come up with was to have the background image move each time a tile moved (which is just weird and unintuitive).
My question is, thus: is there any way to have an animation/transition of the tiles sliding each time that they move?
Code
(the lines that are commented out have nothing to do with the question)
CSS:
Part of the main.css file:
.image {
background-image: url(/Users/user/puzzle-test/img/image001.jpg);
width: 200px;
height: 200px;
display: inline-block;
vertical-align: top;
margin: 10px 10px 10px 10px;
}
.voidBlock {
background-image: none;
}
.imgBlock {
background-image: url(/Users/user/puzzle-test/img/image001.jpg);
}
JavaScript/jQuery:
The <div> click handler:
var clickHandler = function() {
var voidPosX = $(".voidBlock").css("left");
var voidPosY = $(".voidBlock").css("top");
voidPosX = parseInt(voidPosX.substring(0, voidPosX.length-2));
voidPosY = parseInt(voidPosY.substring(0, voidPosY.length-2));
var posX = $(this).css("left");
var posY = $(this).css("top");
posX = parseInt(posX.substring(0, posX.length-2));
posY = parseInt(posY.substring(0, posY.length-2));
if((voidPosX == posX - finalWidth) && (voidPosY == posY)){
posX -= finalWidth;
voidPosX += finalWidth;
move($(this));
}else if((voidPosX == posX + finalWidth) && (voidPosY == posY)){
posX += finalWidth;
voidPosX -= finalWidth;
move($(this));
}else if((voidPosY == posY - finalHeight) && (voidPosX == posX)){
posY -= finalHeight;
voidPosY += finalHeight;
move($(this));
}else if((voidPosY == posY + finalHeight) && (voidPosX == posX)){
posY += finalHeight;
voidPosY -= finalHeight;
move($(this));
}
}
The move function (used in the click handler) (h and v are the horizontal and vertical dimensions of the puzzle):
function move(element) {
//moveCounter++;
var temp = element.attr("class").split(" ");
for(var i = 0; i < temp.length; i++){
if(temp[i].substring(0, 5) == "piece"){
var pieceNum = temp[i].substring(5);
}
}
element.addClass("tempBlock1");
$(".voidBlock").addClass("tempBlock2");
element.filter(".tempBlock1").addClass("piece"+(h*v));
element.filter(".tempBlock1").removeClass("piece"+pieceNum);
element.filter(".tempBlock1").addClass("voidBlock");
element.filter(".tempBlock1").removeClass("imgBlock");
$(".voidBlock").filter(".tempBlock2").addClass("piece"+pieceNum);
$(".voidBlock").filter(".tempBlock2").removeClass("piece"+(h*v));
$(".voidBlock").filter(".tempBlock2").addClass("imgBlock");
$(".voidBlock").filter(".tempBlock2").removeClass("voidBlock");
$(".tempBlock1").removeClass("tempBlock1");
$(".tempBlock2").removeClass("tempBlock2");
draw();
}
The draw function (the numbers 800 and 600 represent the dimensions of the image):
function draw() {
//var imgSelect = $("input[type='radio'][name='bgImage']:checked").val();
var j = 1;
for(var pY = 0; pY > -600; pY -= finalHeight){
for(var pX = 0; pX > -800; pX -= finalWidth){
$(".piece"+j).css("background-position", pX+"px "+pY+"px");
//$(".piece"+j).css("background-image", "url(/Users/user/puzzle-test/img/"+imgSelect+".jpg)");
//$(".piece"+j).find(".helpNum").text(j);
j++;
}
}
//$(".move").text("Moves: " + moveCounter);
$(".voidBlock").css("background-image", "none");
//$(".grid").find("img").attr("src", "img/"+imgSelect+".jpg");
//$(".image:not(.voidBlock)").css("cursor", "pointer");
//$(".voidBlock").css("cursor", "default");
}
Finally, the <div>s are created by the shuffle function, like this (pieces is a shuffled array of the puzzle pieces, basically used to tell where to position each tile):
// DRAW
for(var i = 0; i < pieces.length; i++){
// HTML
var el = "<div class=\"image piece"+(pieces[i])+"\"><p class=\"helpNum\"></p></div>";
$(".grid").append(el);
$(".piece"+(h*v)).addClass("voidBlock");
}
// CSS div positioning
var j = 0;
for(var pY = 0; pY > -600; pY -= finalHeight){
for(var pX = 0; pX > -800; pX -= finalWidth){
$(".piece"+pieces[j]).css({left: pX, top: pY});
j++;
}
}
I finally solved it thanks to #BillyNate 's answer, which was to set the position property of the <div>s to absolute, then move them by changing the top and left properties.
Related
I'm working on my Javascript project.
In this project I have to create some animations.
In this specific case, I have to make a ball bounce up and down.
The code below works great just from the top to the bottom, but not viceversa.
var div = document.getElementById('container-ball');
function createBall(event)
{
var x = event.clientX;
var y = event.clientY;
var newBall = document.createElement('div');
newBall.style.position = "absolute"
newBall.style.width = '15px';
newBall.style.height = '15px';
var bx = newBall.style.left = x + 'px';
var by = newBall.style.top = y + 'px';
newBall.style.borderRadius = '10px';
newBall.style.backgroundColor = 'white';
var incrementPos = 0;
var id = setInterval(bounce, 5);
function bounce()
{
incrementPos++;
by = newBall.style.top = incrementPos + y + "px";
if(by == 650 + "px")
{
clearInterval(id)
var id2 = setInterval(function bounceUp()
{
incrementPosYMax -= 650
by = newBall.style.bottom = by + "px" - incrementPosYMax
}, 5)
}`/*Function that make the ball bounce down and up(but when it came at 650 px it stopped )*/ì
} /*End of the set interval */`
div.appendChild(newBall);
}
div.addEventListener("click", createBall);
This down below is the HTML CODE
<html>
<head>
<link rel= "stylesheet" href="style/style.css">
</head>
<body>
<div id ="container-ball">
</div>
<script src="js/main.js"></script>
</body>
Working example (comments see below):
const areaHeight = 150; // it is 650px in the original question
var div = document.getElementById('container-ball');
function createBall(event) {
var x = event.clientX;
var y = event.clientY;
var newBall = document.createElement('div');
newBall.className = 'ball';
var bx = newBall.style.left = x + 'px';
var by = newBall.style.top = y + 'px';
var incrementPos = 0;
var id = setInterval(bounce, 5);
let direction = 1; // 1 = down, -1 = up
function bounce() {
incrementPos += direction;
by = newBall.style.top = incrementPos + y + "px";
if (by == areaHeight + "px" || by == y + 'px') {
direction = -direction;
}
}
div.appendChild(newBall);
}
#container-ball {
width: 300px;
height: 157px;
background: gray;
}
#container-ball .ball {
position: absolute;
width: 15px;
height: 15px;
border-radius: 10px;
background-color: white;
border: 1px solid #333;
box-sizing: border-box;
}
<div id="container-ball" onclick="createBall(event)"></div>
Click on the grey box
Now, the explanation.
I've moved ball's styles to CSS - this is easier to control in the future. So when I have a class for ball, I can write in my code: newBall.className = 'ball';
I removed incrementPosYMax because I do not really understand if you need it
I understand your 'bounce' as bounce, so my ball just fall to the floor and then return to the original position. I do not really understand if you mean that (please, comment if it is wrong).
Your program is quite small, so I do not see the need for another setInterval, so all the animation in my example is inside only one setInterval
I've added new variable direction to control the direction for the ball (1 = down, -1 = up)
I do not like the parts with by == areaHeight + "px", but I keep them for you, because you use it in your code.
This code have some bugs, that you (or me if you ask) can fix. I just need to understand that my approach is correct
How the direction works:
Take a look at this line by = newBall.style.top = incrementPos + y + "px"; here you set new "y" coordinate for the ball as sum of 'original' "y" coordinate (in y) and offset (the distance that ball moved over the time) in incrementPos. So, if you increase incrementPos, then the ball's position will be lower (because "zero" in browser is at the top left corner, bigger "y" means lower the element).
Before my change, in your code you changed the offset with this line: incrementPos++; (means you increase incrementPos by 1 on every bounce step).
To move to another direction, you need to subtract 1 on every bounce step.
To reflect the "direction" of that move, I've added direction variable (so 1 means move down, and -1 means move up)
Now the offset is changed by: incrementPos += direction; (so I add this direction, not always 1)
Sometimes we need to change the "direction" with this code: direction = -direction;
The "levels" where we need to change direction is checked by this code: if (by == areaHeight + "px" || by == y + 'px') - here we check bottom (areaHeight) and top (y - it is where user clicks the mouse)
I want for my project, detect collision into multiple div.
Please see picture below:
Red box and black border is a div
Red box is movable
How detect if the red box touch one border around it?
I tried solution with canvas but is not a good way for me.
While this is not the exact solution, it seems to be working for cases in which the movable div is in the intersection area of given divs.
The movable div is indicated with the id #draggable. All the other divs used for intersection are selected with data-category="container" attribute.
HTML
<div data-category="container" style="width:200px;height:200px;border:3px solid #000; position:absolute;top:10;left:10"></div>
<div data-category="container" style="width:500px;height:300px;border:3px solid #000; position:absolute;top:100;left:100"></div>
<div data-category="container" style="width:400px;height:400px;border:3px solid #000; position:absolute;top:130;left:50"></div>
<div id="draggable" style="height:20px;width:30px;background-color:red;position:absolute;top:160;left:130"></div>
SCRIPT
var minTop = Number.MAX_VALUE;
var posTop, posLeft = 0;
var minBottom = Number.MAX_VALUE;
var minLeft = Number.MAX_VALUE;
var minRight = Number.MAX_VALUE;
$(document).ready(function()
{
//loop through intersection divs
$('div[data-category=container]').each(function(){
var diffTop = $('#draggable').offset().top - $(this).offset().top; //get distance between tops
var diffLeft = $('#draggable').offset().left - $(this).offset().left; //get distance between lefts
var diffBottom = ($(this).offset().top + $(this).height()) - ($('#draggable').offset().top + $('#draggable').height()); //get distance between bottoms
var diffRight = ($(this).offset().left + $(this).width()) - ($('#draggable').offset().left + $('#draggable').width()); //get distance between rights
//store min top
if (diffTop > 0 && diffTop < minTop)
{
minTop = diffTop;
posTop = $(this).offset().top;
}
//store min left
if (diffLeft > 0 && diffLeft < minLeft)
{
minLeft = diffLeft;
posLeft = $(this).offset().left;
}
//store min bottom
if (diffBottom > 0 && diffBottom < minBottom)
{
minBottom = diffBottom;
}
//store min right
if (diffRight > 0 && diffRight < minRight)
{
minRight = diffRight;
}
});
//create div within the intersection area
$("<div id = '#divFrame' style='border:3px solid blue;position:absolute;top:" + (posTop) + ";left:" + (posLeft) + ";width:" + (minLeft + minRight + $('#draggable').width()) + "px;height:" + (minTop + minBottom + $('#draggable').height()) + "px;'></div>").appendTo("body");
});
Below is how it looks like when the script is executed for the given html.
If you are using JQueryUI, maybe you can set the draggable object's boundries using containment option as follows.
$( "#draggable" ).draggable( { containment: "#divFrame" } );
I'd suggest a solution based on Element.getBoundingClientRect (documentation here). This method returns an object with 6 properties: top, bottom, left, right, width and height. You can use this method to find the areas your <div>s are covering.
Secondly, you'll have to create a method that checks if two areas overlap:
var rectanglesOverlap = function(rec1, rec2) {
// Return true if overlap, false if none
}
Once you have your red rectangle stored and all black bordered divs in an array you can check which of your rectangles overlap like so:
var overlappingRects = blackRects.filter(rectanglesOverlap.bind(null, redRect);
The length of the overlappingRects array now tells you how many overlap there is.
Let me know if you need help selecting the right elements or writing the overlap method. But there's a lot to find online about these topics already...
I am a JS noob. I am getting into browser game programming and wanted to make a quick example of a ball dropping and bouncing just to learn. For some reason, when I created a jsfiddle my code actually didn't work, the onclick event for my div id="ball" didn't seem to be attaching, but when I run it in my browser it does. But that is not my question.
In this code, the user clicks the ball, which is just a div with a black bg. The div then follows the users cursor, and when the user clicks a second time, the div begins to fall towards the bottom of the window. When it hits the bottom, it should bounce back up, with an apex half the distance between the y coordinate of where it was originally dropped and the bottom of window. So if it was dropped at y position 600 and the bottom of the page is 800, the apex for the first bounce should be 700. The 2nd bounce, the apex would be 750. 3rd bounce, 775. You get the idea. Can someone help me a bit here? I am guessing I need to increment a counter each time the ball hits the bottom?
<html>
<head>
<style>
#ball {
width: 50px;
height: 50px;
background-color: black;
position: absolute;
}
</style>
<script>
window.onload = function() {
var ballClicked = false;
var ballFalling = false;
var ballX = 100;
var ballY = 100;
var timesBounced = 0;
var bounceApex = 0;
var startingDropHeight = 0;
var intervalVar;
var ball = document.getElementById("ball");
ball.style.left = ballX;
ball.style.top = ballY;
ball.onclick = function() {
if (ballClicked == false) {
ballClicked = true;
} else {
ballClicked = false;
ballFalling = true;
startingDropHeight = ballY;
intervalVar = setInterval(function(){dropBall()} , 5);
}
};
document.onmousemove = function(e) {
if (ballClicked == true) {
ballX = e.pageX;
ballY = e.pageY;
ball.style.left = ballX;
ball.style.top = ballY;
}
};
function dropBall() {
if (ballFalling == true) {
ballY = ballY + 1;
ball.style.top = ballY;
if (ballY == window.innerHeight - 50) {
timesBounced = timesBounced + 1;
bounceApex = (startingDropHeight + (window.innerHeight - 50)) / 2;
ballFalling = false;
if (bounceApex > window.innerHeight - 50) {
clearInterval(intervalVar);
}
};
} else {
ballY = ballY - 1;
ball.style.top = ballY;
if (ballY == bounceApex) {
ballFalling = true;
};
}
};
};
</script>
</head>
<body>
<div id="ball"></div>
</body>
</html>
When adding left and top styles, you need to specify the unit as well. So, instead of:
ball.style.left = 100;
it should be:
ball.style.left = "100px";
I've fixed that in your code and made a working jsfiddle, will improve the bouncing in a bit. See it here: http://jsfiddle.net/12grut99/
About the repetitive bouncing, this line is the issue:
bounceApex = (startingDropHeight + (window.innerHeight - 50)) / 2;
You're always calculating the apex based on the original drop height, yet after every bounce, the drop height should be the previous bounceApex (the highest point the ball reached).
Okay, I've seen a few things that sort of * answer my question, but none of them quite do what I want to do / I'd like to understand how to do this myself from start to finish as a learning exercise. I'm a novice at all this, so bear with me!
What I'm Trying to Do:
I have a black page and I'd like 20-30 small, white div boxes to fadeIn at random positions on the page (like stars is sort of the vibe I'm going for).
Ideally, they wouldn't overlap and they would be randomly sized between 5px and 10px, but I recognize that this might be getting a little too complicated.
Here's what I have so far
I've been working off this jsfiddle and well as this one. This is what I've come up with (that doesn't work, they all fade in equally spaced in a line and don't stay confined from to the site)
Here's my jsfiddle, code below
function randomPosition() {
var h = $(window).height()-10;
var w = $(window).width()-10;
var newHeight = Math.floor(Math.random() * h);
var newWidth = Math.floor(Math.random() * w);
return [newHeight, newWidth];
}
$(document).ready(function() {
var newPosition = randomPosition();
$('.star').css( {
'margin-left':newPosition[1]+'px',
'margin-top':newPosition[0]+'px'
}).each(function(index) { $(this).delay(1500*index).fadeIn('slow');
})
});
CSS
body {
background-color: black;
}
.star {
height: 10px;
width: 10px;
background-color: white;
display: none;
}
HTML (is there a way to do this with just a for loop or something similar?)
<div class="star"> </div>
<div class="star"> </div>
<div class="star"> </div>
<div class="star"></div>
The sizing and positioning isn't too hard. The thing is to do it all in the each loop - currently you get 1 position and use it for everything. Also you will want to make them position:absolute so they don't go off the page.
I've updated your fiddle to set the random position and a size between 5 and 10px:
The overlapping is a bit harder. You need to keep track of the sizes and positions you have generated and in the same .each function compare the current generated size+position to the previous ones to check for overlapping.
http://jsfiddle.net/5ocb5aww/3/
function randomPosition() {
var h = $(window).height()-10;
var w = $(window).width()-10;
var newHeight = Math.floor(Math.random() * h);
var newWidth = Math.floor(Math.random() * w);
return [newHeight, newWidth];
}
function randomSize() {
return Math.round(Math.random() * 5) + 5;
}
$(document).ready(function() {
// stores generated star positions
var stars = [];
$('.star').each(function(index) {
var newPosition, newSize;
// check for overlap
var isOverlap = true;
while(isOverlap)
{
newPosition = randomPosition();
newSize = randomSize();
// check previous stars to see if an edge of this one overlaps
isOverlap = $.grep(stars, function(s) {
return (
(newPosition[1] >= s.x1 && newPosition[1] <= s.x2)
|| (newPosition[1]+newSize >= s.x1 && newPosition[1]+newSize <= s.x2)
)
&& (
(newPosition[0] >= s.y1 && newPosition[0] <= s.y2)
|| (newPosition[0]+newSize >= s.y1 && newPosition[0]+newSize <= s.y2)
);
}).length > 0;
}
// store to check later stars against it
stars.push({
x1: newPosition[1],
x2: newPosition[1] + newSize,
y1: newPosition[0],
y2: newPosition[0] + newSize,
size: newSize});
$(this).css({
'margin-left':newPosition[1]+'px',
'margin-top':newPosition[0]+'px',
'width':newSize + 'px',
'height':newSize + 'px'
});
$(this).delay(800*index).fadeIn('slow');
})
});
Here is my approach to your exercise ... the overlapping position would require a little bit more effort ... I'll leave you that to sort for yourself (may require restructuring the code I'm handing here)
jsFiddle Demo
JS
function starDust(wdt, hgt, tSt, tAp){
var timer = tAp * 1000;
var defInt = tSt,
starInt = setInterval(function(){
var posX = Math.floor((Math.random() * wdt) + 1),
posY = Math.floor((Math.random() * hgt) + 1),
size = Math.floor((Math.random() * 10) + 1);
$('body').append('<div class="star"></div>');
$('.star:last').css({'width':size,'height':size,'left':posX,'top':posY}).hide().fadeIn('slow');
var totalStars = $('.star').length;
if(totalStars == defInt){
clearInterval(starInt);
}
}, timer);
}
$(function(){
// Function arguments: starDust(max X position in px, max Y position in px, total number of stars, time in seconds between stars show);
starDust(600,300,25,1);
});
CSS
body{
background-color:#000;
}
.star{
position: absolute;
background-color:#fff;
min-width:5px;
min-height:5px;
}
I'm working on a portal/dashboard type interface which has panels/widgets that can be freely dragged around the dashboard space as long as they don't overlay any other panels. New panels can be added to the dashboard via a menu containing all available panels, and when a menu item is clicked, the panel is placed into the dashboard. The panels currently occupying the dashboard space are all represented in an object like this:
{
'panel_1': { top: 0, left: 0, width: 300, height: 350 },
'panel_2': { top: 0, left: 370, width: 200, height: 275 },
'panel_3': { top: 370, left: 0, width: 275, height: 400 },
...
}
My question is, what is a valid algorithm that would, when the user clicks one in the menu, correctly place a new panel (of a given width and height) in unoccupied space that is closest to a left and top (x and y) value of 0, 0, without overlapping any of the existing panels?
I think, simple bruteforce algorithm will fit you. As I remember, fit rectangle solve another problem
Iterate over your dashboard axis to find out, whether you can place your rectangle, until X < rectangle.widh + dashboard.width, same for Y.
Foreach X,Y on dashboard iterate over every panel to find whether they overlap. You can apply some optimization, to decrease amount of iteration. If panel overlap rectangle, you can increase X or Y(which is in nested loop) not by 1, but by width or height of panel.
In most cases, you will not make dashboard.width*dashboard.height*panel.count iteration. With some optimization, it will find best fit rather quick
I know this is an old question but if anyone wants a proof of concept then it looks like this:
function findSpace(width, height) {
var $ul = $('.snap-layout>ul');
var widthOfContainer = $ul.width();
var heightOfContainer = $ul.height();
var $lis = $ul.children('.setup-widget'); // The li is on the page and we dont want it to collide with itself
for (var y = 0; y < heightOfContainer - height + 1; y++) {
var heightOfShortestInRow = 1;
for (var x = 0; x < widthOfContainer - width + 1; x++) {
//console.log(x + '/' + y);
var pos = { 'left': x, 'top': y };
var $collider = $(isOverlapping($lis, pos, width, height));
if ($collider.length == 0) {
// Found a space
return pos;
}
var colliderPos = $collider.position();
// We have collided with something, there is no point testing the points within this widget so lets skip them
var newX = colliderPos.left + $collider.width() - 1; // -1 to account for the ++ in the for loop
x = newX > x ? newX : x; // Make sure that we are not some how going backwards and looping forever
var colliderBottom = colliderPos.top + $collider.height();
if (heightOfShortestInRow == 1 || colliderBottom - y < heightOfShortestInRow) {
heightOfShortestInRow = colliderBottom - y; // This isn't actually the height its just the distance from y to the bottom of the widget, y is normally at the top of the widget tho
}
}
y += heightOfShortestInRow - 1;
}
//TODO: Add the widget to the bottom
}
function isOverlapping($obsticles, tAxis, width, height) {
var t_x, t_y;
if (typeof (width) == 'undefined') {
// Existing element passed in
var $target = $(tAxis);
tAxis = $target.position();
t_x = [tAxis.left, tAxis.left + $target.outerWidth()];
t_y = [tAxis.top, tAxis.top + $target.outerHeight()];
} else {
// Coordinates and dimensions passed in
t_x = [tAxis.left, tAxis.left + width];
t_y = [tAxis.top, tAxis.top + height];
}
var overlap = false;
$obsticles.each(function () {
var $this = $(this);
var thisPos = $this.position();
var i_x = [thisPos.left, thisPos.left + $this.outerWidth()]
var i_y = [thisPos.top, thisPos.top + $this.outerHeight()];
if (t_x[0] < i_x[1] && t_x[1] > i_x[0] &&
t_y[0] < i_y[1] && t_y[1] > i_y[0]) {
overlap = this;
return false;
}
});
return overlap;
}