Good day people!
I have run into an issue with my simple single day calendar script.
I've been tasked to create a single day calendar, which shows each block hour from 9am to 6pm. If an event overlaps another, they should equal the same width and not overlap. I have managed to achieve this for two events, however if more than two overlap, things go abit south, I need help figuring out a method to fix this where any number of events overlap, their widths will equal the same.
Events are rendered on the calendar using a global function:
renderDay([{start: 30, end: 120},{start: 60, end: 120}])
which takes an array of objects as an argument, where the integers are the number of minutes pasted from 9am. eg. 30 is 9:30am, 120 is 11am
here is the collision function I took from stackoverflow
// collision function to return boolean
// attribute: http://stackoverflow.com/questions/14012766/detecting-whether-two-divs-overlap
function collision($div1, $div2) {
let x1 = $div1.offset().left;
let y1 = $div1.offset().top;
let h1 = $div1.outerHeight(true);
let w1 = $div1.outerWidth(true);
let b1 = y1 + h1;
let r1 = x1 + w1;
let x2 = $div2.offset().left;
let y2 = $div2.offset().top;
let h2 = $div2.outerHeight(true);
let w2 = $div2.outerWidth(true);
let b2 = y2 + h2;
let r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
return true;
}
I run a loop on all the event divs which I want to check for overlaps
// JQuery on each, check if collision
$('.event').each(function(index, value) {
// statement to break out on final loop
if(index === $('.event').length - 1) return;
console.log('at index: ', index);
// if collison === true, halve width of both event divs, re-position
if(collision( $('#e-'+index) , $('#e-'+(index + 1)) )) {
$('#e-'+index).css('width', $('#e-'+index).width() / 2);
$('#e-'+(index+ 1)).css('width', $('#e-'+(index+ 1)).width() / 2).css('left', $('#e-'+(index + 1)).width());
if(collision)
}
})
}
})
Screenshots to help visualize :)
When two overlap, they have equal widths
When three or more overlap, things go wrong
Any help would be greatly appreciated!
DW
After looking at the code, it seems overly complex to check the rendered elements for collision when you can work this out from the start and end times.
The way I've done it, is to group events which collide in arrays like so:
let collisions = [
// only 1 event in this array so no collisions
[{
start: 30,
end: 120
}],
// 3 events in this array which have overlapping times
[{
start: 300,
end: 330
}, {
start: 290,
end: 330
}, {
start: 300,
end: 330
}]
];
Then we iterate through each group of collisions, and create the elements with the appropriate width and positioning.
for (var i = 0; i < collisions.length; i++) {
var collision = collisions[i];
for (var j = 0; j < collision.length; j++) {
var event = collision[j];
let height = event.end - event.start;
let top = event.start + 50;
// 360 = max width of event
let width = 360 / collision.length;
// a lot of this could be moved into a css class
// I removed the "display: inline-block" code because these are absolutely positioned. Replaced it with "left: (j * width)px"
let div = $(`<div id=${'e-'+ (i + j)}>`).css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('left', (j * width) + 'px')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
// append event div to parent container
$('#events').append(div);
}
}
//**********************************************************************
//
// TITLE - Thought Machine Coding Challenge, Single Day Calendar
// AUTHOR - DOUGLAS WISSETT WALKER
// DATE - 21/04/2016
// VERSION - 0.0.3
// PREVIOUS - 0.0.2
//
//**********************************************************************
let arr = [{
start: 30,
end: 120
}, {
start: 70,
end: 180
}, {
start: 80,
end: 190
}, {
start: 300,
end: 330
}, {
start: 290,
end: 330
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 220,
end: 260
}, {
start: 400,
end: 440
}, {
start: 20,
end: 200
}];
let renderDay;
$(document).ready(() => {
renderDay = function(array) {
$('.event').each(function(i, el) {
$(el).remove();
});
// background colors for events
let arrayOfColors = [
'rgba(255, 153, 153, 0.75)',
'rgba(255, 204, 153, 0.75)',
'rgba(204, 255, 153, 0.75)',
'rgba(153, 255, 255, 0.75)',
'rgba(153, 153, 255, 0.75)',
'rgba(255, 153, 255, 0.75)'
]
let collisions = mapCollisions(array);
let eventCount = 0; // used for unique id
for (let i = 0; i < collisions.length; i++) {
let collision = collisions[i];
for (let j = 0; j < collision.length; j++) {
let event = collision[j];
let height = event.end - event.start;
let top = event.start + 50;
// 360 = max width of event
let width = 360 / collision.length;
// a lot of this could be moved into a css class
// I removed the "display: inline-block" code because these are absolutely positioned
// Replaced it with "left: (j * width)px"
let div = $("<div id='e-" + eventCount + "'>").css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('left', (j * width) + 'px')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
eventCount++;
// append event div to parent container
$('#events').append(div);
}
}
}
renderDay(arr);
});
// Sorry this is pretty messy and I'm not familiar with ES6/Typescript or whatever you are using
function mapCollisions(array) {
let collisions = [];
for (let i = 0; i < array.length; i++) {
let event = array[i];
let collides = false;
// for each group of colliding events, check if this event collides
for (let j = 0; j < collisions.length; j++) {
let collision = collisions[j];
// for each event in a group of colliding events
for (let k = 0; k < collision.length; k++) {
let collidingEvent = collision[k]; // event which possibly collides
// Not 100% sure if this will catch all collisions
if (
event.start >= collidingEvent.start && event.start < collidingEvent.end || event.end <= collidingEvent.end && event.end > collidingEvent.start || collidingEvent.start >= event.start && collidingEvent.start < event.end || collidingEvent.end <= event.end && collidingEvent.end > event.start) {
collision.push(event);
collides = true;
break;
}
}
}
if (!collides) {
collisions.push([event]);
}
}
console.log(collisions);
return collisions;
}
html,
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
#container {
height: 100%;
width: 100%;
}
#header-title {
text-align: center;
}
#calendar {
width: 400px;
height: 620px;
margin-top: 70px;
}
#events {
position: absolute;
top: 80px;
left: 100px;
width: 800px;
height: 620px;
}
.event {
box-shadow: 0 0 20px black;
border-radius: 5px;
}
.hr-block {
border-top: 2px solid black;
height: 58px;
margin: 0;
padding: 0;
margin-left: 100px;
min-width: 360px;
opacity: .5;
}
.hr-header {
position: relative;
top: -33px;
left: -68px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/responsive.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script charset="UTF-8" src="js/moment.js"></script>
<script charset="UTF-8" src="js/script2.js"></script>
<title>Thought Machine Code Challenge</title>
</head>
<body>
<div id="container">
<div class="header">
<h1 id="header-title"></h1>
</div>
<div id="calendar">
<div class="hr-block">
<h2 class="hr-header">09:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">10:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">11:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">12:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">13:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">14:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">15:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">16:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">17:00</h2>
</div>
<div class="hr-block">
<h2 class="hr-header">18:00</h2>
</div>
</div>
</div>
<div id="events">
</div>
<script>
document.getElementById("header-title").innerHTML = moment().calendar();
</script>
</body>
</html>
working index, script and css files
//**********************************************************************
//
// TITLE - Thought Machine Coding Challenge, Single Day Calendar
// AUTHOR - DOUGLAS WISSETT WALKER
// DATE - 21/04/2016
// VERSION - 0.0.3
// PREVIOUS - 0.0.2
//
//**********************************************************************
let arr = [{start: 30, end: 120},{start: 300, end: 330},{start: 290, end: 330}];
let renderDay;
$(document).ready(() => {
renderDay = function(array) {
$('.event').each(function(i, el) {
$(el).remove();
});
// background colors for events
let arrayOfColors = [
'rgba(255, 153, 153, 0.75)',
'rgba(255, 204, 153, 0.75)',
'rgba(204, 255, 153, 0.75)',
'rgba(153, 255, 255, 0.75)',
'rgba(153, 153, 255, 0.75)',
'rgba(255, 153, 255, 0.75)'
]
// iterate through each event time
array.forEach((eventTimes, index) => {
// define event height and top position on calendar
let height = eventTimes.end - eventTimes.start;
let top = eventTimes.start + 50;
// max width of event
let width = 360;
// create event div
let div = $(`<div id=${'e-'+index}>`).css('position', 'absolute').css('top', top)
.css('height', height).css('width', width).css('display', 'inline-block')
.css('backgroundColor', arrayOfColors.shift()).addClass('event')
.text('New Event').css('fontWeight', 'bold');
// append event div to parent container
$('#events').append(div);
})
// JQuery on each, check if collision
$('.event').each(function(index, value) {
// statement to break out on final loop
if(index === $('.event').length - 1) return;
console.log('at index: ', index);
// if collison === true, halve width of both event divs, re-position
if(collision( $('#e-'+index) , $('#e-'+(index + 1)) )) {
$('#e-'+index).css('width', $('#e-'+index).width() / 2);
$('#e-'+(index+ 1)).css('width', $('#e-'+(index+ 1)).width() / 2).css('left', $('#e-'+(index + 1)).width());
}
})
}
})
// collision function to return boolean
// attribute: http://stackoverflow.com/questions/14012766/detecting-whether-two-divs-overlap
function collision($div1, $div2) {
let x1 = $div1.offset().left;
let y1 = $div1.offset().top;
let h1 = $div1.outerHeight(true);
let w1 = $div1.outerWidth(true);
let b1 = y1 + h1;
let r1 = x1 + w1;
let x2 = $div2.offset().left;
let y2 = $div2.offset().top;
let h2 = $div2.outerHeight(true);
let w2 = $div2.outerWidth(true);
let b2 = y2 + h2;
let r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2) return false;
return true;
}
// render events using renderDay(arr) in console
html, body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
#container {
height: 100%;
width: 100%;
}
#header-title {
text-align: center;
}
#calendar {
width: 400px;
height: 620px;
margin-top: 70px;
}
#events {
position: absolute;
top: 80px;
left: 100px;
width: 800px;
height: 620px;
}
.event {
box-shadow: 0 0 20px black;
border-radius: 5px;
}
.hr-block {
border-top: 2px solid black;
height: 58px;
margin: 0;
padding: 0;
margin-left: 100px;
min-width: 360px;
opacity: .5;
}
.hr-header {
position: relative;
top: -33px;
left: -68px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="css/styles.css">
<link rel="stylesheet" href="css/responsive.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<script charset="UTF-8" src="js/moment.js"></script>
<script charset="UTF-8" src="js/script2.js"></script>
<title>Thought Machine Code Challenge</title>
</head>
<body>
<div id="container">
<div class="header">
<h1 id="header-title"></h1>
</div>
<div id="calendar">
<div class="hr-block"><h2 class="hr-header">09:00</h2></div>
<div class="hr-block"><h2 class="hr-header">10:00</h2></div>
<div class="hr-block"><h2 class="hr-header">11:00</h2></div>
<div class="hr-block"><h2 class="hr-header">12:00</h2></div>
<div class="hr-block"><h2 class="hr-header">13:00</h2></div>
<div class="hr-block"><h2 class="hr-header">14:00</h2></div>
<div class="hr-block"><h2 class="hr-header">15:00</h2></div>
<div class="hr-block"><h2 class="hr-header">16:00</h2></div>
<div class="hr-block"><h2 class="hr-header">17:00</h2></div>
<div class="hr-block"><h2 class="hr-header">18:00</h2></div>
</div>
</div>
<div id="events">
</div>
<script>
document.getElementById("header-title").innerHTML = moment().calendar();
</script>
</body>
</html>
events not rendering, you need to run:
renderDay([{start:30, end:120, start: 60, end: 120}]) in console
Related
I'm trying to realise an infinite up and down move of the platform . How can I modify the code to get this thing ? I have only managed to have just one un-down movement . I know that I could do this with CSS animations but I would like to modify my code .
var n = 0;
var grid = document.querySelector('.grid');
function move() {
const pixels = [200, 196, 192, 188, 184, 180, 176, 172, 168, 164, 160, 164, 168, 172, 176, 180];
const style = grid.style.bottom
const computedStyle = window.getComputedStyle(grid)
console.log('bottom from computed style', computedStyle.bottom)
grid.style.bottom = pixels[n] + 'px';
n++;
}
move();
setInterval(move, 90);
.grid {
background-color: blue;
height: 20px;
width: 100px;
left: 100px;
bottom: 200px;
position: absolute;
}
<div class="grid"></div>
The function is not looping because you are not resetting n - after the first up down movement, n goes out of bounds, grid.style.bottom = pixels[n] + 'px'; tries to set the stile to undefined +'px' and fails, and the bar stays where it is.
I added n = n % pixels.length; to reset n once it goes out of bounds.
var n = 0;
var grid = document.querySelector('.grid');
function move() {
const pixels = [200, 196, 192, 188, 184, 180, 176, 172, 168, 164, 160, 164, 168, 172, 176, 180];
const style = grid.style.bottom
const computedStyle = window.getComputedStyle(grid)
console.log('bottom from computed style', computedStyle.bottom)
n = n % pixels.length;
grid.style.bottom = pixels[n] + 'px';
n++;
}
move();
setInterval(move, 90);
.grid {
background-color: blue;
height: 20px;
width: 100px;
left: 100px;
bottom: 200px;
position: absolute;
}
<div class="grid"></div>
You can have a Boolean checking once you get at the end of your array and once you do you start to decrement your n variable. This way it will go from 200px -> 180px -> 200px
let grid = document.querySelector(".grid");
let n = 0;
let bool = true
function move() {
const pixels = [200, 196, 192, 188, 184, 180, 176, 172, 168, 164, 160, 164, 168, 172, 176, 180]
const style = grid.style.bottom;
const computedStyle = window.getComputedStyle(grid);
console.log("bottom from computed style", computedStyle.bottom);
grid.style.bottom = pixels[n] + "px";
if(n === pixels.length - 1 ){
bool = false
} else if(n === 0){
bool = true
}
bool ? n++ : n--
}
move();
setInterval(move, 90);
.grid {
background-color: blue;
height: 20px;
width: 100px;
left: 100px;
bottom: 200px;
position: absolute;
}
<div class="grid"></div>
Instead of incrementing in an interval, better compute the position based on the time passed since start.
According to your pixels array and your interval, you move 40px up/down every 900ms. (10 steps of 4px / 90ms)
const start = Date.now()
function move(){
const time = Date.now() - start;
let t = time / 900; // the time passed in terms of up/down strokes
// t = t % 1; // turns this into a sawtooth pattern, just up-strokes
// not what we want, we want every other stroke to be a down-stroke.
t = t&1 ? // every other stroke
(1 - t%1) : // move down
(t%1); // otherwise mode up
// now we have out position as a percentage value 0..1;
// let's compute the pixels.
const pos = 200 - 40*t; // start at 200px and travel a fraction of 40px down.
grid.style.bottom = pos + "px";
// rAF is way smoother than your 90ms interval.
requestAnimationFrame(move);
}
move();
const DURATION = 900;
const grid = document.querySelector('.grid');
const start = 0;
function move() {
let t = (Date.now() - start) / 900;
t = t&1 ? 1-t%1 : t%1; // zig-zag
//t = t%1; // sawtooth; try this instead of the zig-zag and see/understand the difference.
//add some easing; try it.
//t = t*t*(3-2*t);
grid.style.bottom = 200 - 40*t + "px";
requestAnimationFrame(move);
}
move();
.grid {
background-color: blue;
height: 20px;
width: 100px;
left: 100px;
bottom: 200px;
position: absolute;
}
<div class="grid"></div>
I have an array of keys that stores which keys are being pressed. Whenever I press the left arrow, spacebar, and the up arrow together, the array will only keep two of the keycodes, leaving one not to be pushed into the array. When these three keys are pressed, the character is supposed to move left, jump up, and shoot a bullet. One of those three actions won't occur. I am using Google Chrome, and I don't know what will happen on other browsers.
var $c = $('canvas');
var ctx = $c[0].getContext('2d');
var x = 20;
var y = 150;
var keys = [];
var bulletX = x + 2;
var bulletY = 0;
var bullets = [];
var face = 1;
function plr() {
ctx.fillStyle = 'black';
ctx.fillRect(x, y, 20, 20);
}
$c.keydown(function(e) {
if (_.includes(keys, e.which) === false) {
keys.push(e.which);
}
});
$c.keyup(function(e) {
_.pull(keys, e.which);
});
function shoot() {
bullets.forEach(function(bullet) {
ctx.fillStyle = 'red';
ctx.fillRect(bullet.bX, bullet.bY, 8, 4);
if (bullet.direction === 0) {
bullet.bX -= 7;
}
if (bullet.direction === 1) {
bullet.bX += 7;
}
if (bullet.bX > 700 || bullet.bX < 0) {
_.pull(bullets, bullet);
}
});
}
setInterval(function() {
ctx.clearRect(0, 0, 700, 500);
if (keys.includes(32)) {
bullets.push({
direction: face,
bX: x + face * 12,
bY: y + 8
});
}
if (keys.includes(37)) {
face = 0;
x -= 3;
}
if (keys.includes(38)) {
y-=3;
}
if (keys.includes(39)) {
face = 1;
x += 3;
}
plr();
shoot();
}, 30);
.canvas {
background-color: #a3c2ba;
outline: none;
border: #fff;
margin: auto;
display: block;
position: relative;
top: 50px;
border-radius: 0px;
}
body {
margin: 0;
}
<!DOCTYPE html>
<html>
<head>
<title>Game</title>
<script src='https://code.jquery.com/jquery-3.4.1.min.js'></script>
<script src='https://cdn.jsdelivr.net/npm/lodash#4.17.11/lodash.min.js'></script>
</head>
<body>
<canvas class='canvas' width='300' height='200' tabindex='1' />
</body>
</html>
I have concluded that this is a problem with my keyboard. I tried this on my brother's computer and it worked fine. Thanks to everyone who attempted to help though!
I have created three circles and made it bounce off the wall without using HTML canvas. Now I want two circles to collide with each other and move those circles in the opposite direction. I tried to detect the collision by checking it's position but it doesn't work. I don't know where I went wrong.
Here's my code:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Bounce Ball</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
.circle{
height: 50px;
width: 50px;
position: absolute;
border-radius: 50%;
}
.container {
height: 300px;
width: 300px;
background-color: skyblue;
position: relative;
}
</style>
</head>
<body>
<div class ="container" id ="container">
<div class="circle" id = "circle1" style="background-color:black;
height: 50px; width: 50px;top:0; left:0"></div>
<div class="circle" id ="circle2" style="background-color:rgb(197, 100,
100);height: 50px; width: 50px;top:200px;left: 150px"></div>
<div class="circle" id ="circle3" style="background-color:brown;height:
50px;width: 50px;top:50px;left: 640px"></div>
</div>
<script>
var container = document.getElementById("container");
container.style.width="700px";
container.style.height = "300px";
var balls = document.getElementsByClassName("circle");
for(var i=0; i <balls.length; i++){
var speed={x:3,y:-3}
setInterval(draw, 50 , balls[i], speed);
}
function draw(ball, speed) {
if(parseInt(ball.style.left) > (parseInt(container.style.width)-
parseInt(ball.style.width)) || (parseInt(ball.style.left) <0) ){
speed.x = -speed.x;
}
ball.style.left = parseInt(ball.style.left) + speed.x + 'px';
if(parseInt(ball.style.top) > (parseInt(container.style.height)-
parseInt(ball.style.height)) || (parseInt(ball.style.top) <0)){
speed.y = -speed.y;
}
ball.style.top = parseInt(ball.style.top) + speed.y + 'px';
//for colliding two circles
for(var i =0 ; i <= balls.length-1; i++){
for(var j = i + 1; j < balls.length; j++){
if(parseInt(balls[i].style.left) +
parseInt(balls[i].style.width) ==
parseInt(balls[j].style.left) ||
parseInt(balls[j].style.left) +
parseInt(balls[j].style.width) ==
parseInt(balls[i].style.left) &&
parseInt(balls[i].style.top) +
parseInt(balls[i].style.height) ==
parseInt(balls[j].style.top) || parseInt(balls[j].style.top)
+ parseInt(balls[j].style.height) ==
parseInt(balls[i].style.top)) {
speed.x = - speed.x;
speed.y = -speed.y;
}
ball[i].style.left = parseInt(ball[i].style.left) +
speed.x + 'px';
ball[j].style.left = parseInt(ball[j].style.left) +
speed.x + 'px';
ball[i].style.top = parseInt(ball[i].style.top) +
speed.y + 'px';
ball[j].style.top = parseInt(ball[j].style.top) +
speed.y + 'px';
}
}
}
</script>
</body>
</html>
I would recommend moving as much as possible into javascript variables so you don't need to consult the HTML for every parameter.
You had quite the number of typos, among them speed.x = - speed.x; where you meant speed.x = -speed.x; and your code was difficult to read without any comments or helper functions to explain what's going on.
I have fixed your typos and restructured your code in the snippet below. Try checking the developer console, typically by pressing F12, as this will show you code errors with line number and severity rating.
In my snippet below i have tried to move the parameters into JavaScript to show how that would work, while still leaving some on the HTML nodes:
//Basic properties
var width = 700;
var height = 300;
//Get container
var container = document.getElementById("container");
// Set dimensions
container.style.width = width + "px";
container.style.height = height + "px";
//Load balls
var balls = Array.prototype.slice.call(document.getElementsByClassName("circle"))
.map(function(ball) {
return {
HTMLNode: ball,
xPos: parseInt(ball.style.left),
yPos: parseInt(ball.style.top),
xAcc: 3,
yAcc: -3,
size: 50
};
});
//Utility functions
function angleBetween(x1, y1, x2, y2) {
return Math.atan2(y2 - y1, x2 - x1);
}
function distanceBetween(x1, y1, x2, y2) {
return Math.abs(y2 - y1) + Math.abs(x2 - x1);
}
//Draw function
function draw() {
//Loop through balls
for (var ballIndex1 = 0; ballIndex1 < balls.length; ballIndex1++) {
var ball1 = balls[ballIndex1];
//Collide with horisontal wall
if (ball1.xPos > width - ball1.size || ball1.xPos < 0) {
ball1.xAcc = -ball1.xAcc;
}
//Collide with vertical wall
if (ball1.yPos > height - ball1.size || ball1.yPos < 0) {
ball1.yAcc = -ball1.yAcc;
}
//Collide with other balls
for (var ballIndex2 = ballIndex1 + 1; ballIndex2 < balls.length; ballIndex2++) {
var ball2 = balls[ballIndex2];
//Test within collision distance
if (distanceBetween(ball1.xPos, ball1.yPos, ball2.xPos, ball2.yPos) > ball1.size) {
continue;
}
//Get angle of collision
var angle = angleBetween(ball1.xPos, ball1.yPos, ball2.xPos, ball2.yPos);
//Apply force to acceleration
ball1.xAcc = -Math.cos(angle) * 3;
ball2.xAcc = -ball1.xAcc;
ball1.yAcc = -Math.sin(angle) * 3;
ball2.yAcc = -ball1.yAcc;
}
//Apply acceleration to position
ball1.yPos += ball1.yAcc;
ball1.xPos += ball1.xAcc;
//Apply to node
ball1.HTMLNode.style.left = ball1.xPos + "px";
ball1.HTMLNode.style.top = ball1.yPos + "px";
}
}
//Start simulation
setInterval(draw, 1000 / 60);
.circle {
position: absolute;
border-radius: 50%;
height: 50px;
width: 50px;
}
.container {
height: 300px;
width: 300px;
background-color: skyblue;
position: relative;
}
<div class="container" id="container">
<div class="circle" id="circle1" style="background-color:black;
top:0; left:0"></div>
<div class="circle" id="circle2" style="background-color:rgb(197, 100,
100);top:200px;left: 150px"></div>
<div class="circle" id="circle3" style="background-color:brown;top:50px;left: 640px"></div>
</div>
I have a task to make animation with JavaScript.
Basically I have two squares (red and yellow) and a two buttons (button 1 and button 2).
When I click on button1 the red square goes from the (top-left corner) to the (bottom-right corner).
I need to make another button (button2) such that when I click on it I need the red square to go back to the beginning.
I need it to do the opposite move (moving from the bottom-right corner to the top-left corner).
What changes should I do in the second function?
here is the code
function myMove1() {
var elem = document.getElementById("animate");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
function myMove2() {
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
I'm going to assume the teacher is trying to teach basic javascript, and tell you how I'd solve this with the parts you've provided.
That said, your commenters are correct, requestAnimationFrame is the right tool here. Also, the 5 ms delay on your interval is really short (125fps). If you made this number, I'd suggest changing it to 16, which is roughly 60fps.
// We want each function to be able to see these vars.
var pos = 0;
// Either -1, 0, or 1, depending on if were moving forward, backwards or
// stopped.
var direction = 0;
// This var now serves dual purpose, either its a number which is the
// interval id or its falsy, which we can use to understand the animation
// has stopped.
var id = null;
// Doing this here, will save the browser from having to redo this step on
// each frame.
var elem = document.getElementById("animate");
// Render the elem to the correct starting location.
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
// A single animation function.
function frame() {
// Assume we are heading for 350.
var goal = 350
if (direction < 0) {
// unless the goal is -1, when the goal is zero.
goal = 0
}
if (pos != goal) {
pos += direction;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
} else {
// Reset all the shared vars.
direction = 0;
clearInterval(id);
id = null;
}
}
function myMove1() {
if (id) {
clearInterval(id)
}
direction = 1;
id = setInterval(frame, 5);
}
function myMove2() {
if (id) {
clearInterval(id)
}
direction = -1;
id = setInterval(frame, 5);
}
#animate {
position: absolute;
width: 10px;
height: 10px;
background-color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
</body>
</html>
What you're asking is straightforward: take the function you already wrote and change the increment direction on pos. The only difference is you'll need to keep track of x and y coordinates separately since they move in opposite directions. I used this object initialized to the start position of the box:
pos = {x: 350, y: 0};
function myMove1() {
var elem = document.getElementById("animate");
var pos = 0;
var id = setInterval(frame, 5);
function frame() {
if (pos == 350) {
clearInterval(id);
} else {
pos++;
elem.style.top = pos + 'px';
elem.style.left = pos + 'px';
}
}
}
function myMove2() {
var elem = document.getElementById("animate");
var pos = {x: 350, y: 0};
var id = setInterval(frame, 5);
function frame() {
if (pos.y >= 350 || pos.x <= 0) {
clearInterval(id);
} else {
pos.x--;
pos.y++;
elem.style.top = pos.y + 'px';
elem.style.left = pos.x + 'px';
}
}
}
#container {
width: 400px;
height: 400px;
position: relative;
background: yellow;
}
#animate {
width: 50px;
height: 50px;
position: absolute;
background-color: red;
}
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div id="animate"></div>
</div>
However, these functions aren't reusable without parameters; this code is WET (wrote everything twice). The animation is brittle because each click creates a new timeout (you can spam the buttons and watch it crumble). Entities in the animation have no state. If you want to change the position or add another box, you have to we-write and duplicate all of your code again.
With that in mind, here's a sketch to illustrate a somewhat improved version as food for thought. The functions and objects are more general and don't need to be re-written for new movements you decide to add. The Box class keeps track of entity state over time. requestAnimationFrame() is used to update and draw all entities on the screen at once, avoiding the many problems with setTimeout.
const lerp = (v0, v1, t) => (1 - t) * v0 + t * v1;
const dist = (a, b) => ((a.x - b.x) ** 2 + (a.y - b.y) ** 2) ** 0.5;
class Box {
constructor(elem, pos, size, color, speed) {
this.elem = elem;
this.speed = speed;
this.from = this.to = this.pos = pos;
this.t = 0;
this.elem.style.position = "absolute";
this.elem.style.background = color;
this.elem.style.height = `${size}px`;
this.elem.style.width = `${size}px`;
this.elem.style.top = `${this.pos.y}px`;
this.elem.style.left = `${this.pos.x}px`;
}
move(to) {
this.from = {x: this.pos.x, y: this.pos.y};
this.to = {x: to.x, y: to.y};
this.t = 0;
}
update() {
if (dist(this.pos, this.to) > 1) {
this.pos.x = lerp(this.from.x, this.to.x, this.t);
this.pos.y = lerp(this.from.y, this.to.y, this.t);
this.elem.style.top = `${this.pos.y}px`;
this.elem.style.left = `${this.pos.x}px`;
this.t += this.speed;
}
}
}
const data = [
{color: "red", pos: {x: 0, y: 0}, size: 10},
{color: "yellow", pos: {x: 350, y: 0}, size: 10},
];
const elems = document.getElementsByClassName("box");
const boxes = [];
for (let i = 0; i < elems.length; i++) {
boxes.push(new Box(elems[i], data[i].pos, data[i].size, data[i].color, 0.01));
}
function myMove1() {
boxes[0].move({x: 350, y: 350});
boxes[1].move({x: 0, y: 350});
}
function myMove2() {
boxes[0].move({x: 0, y: 0});
boxes[1].move({x: 350, y: 0});
}
(function render() {
boxes.forEach(e => e.update());
requestAnimationFrame(render);
})();
<p>
<button onclick="myMove1()">button 1</button>
<button onclick="myMove2()">button 2</button>
</p>
<div id="container">
<div class="box"></div>
<div class="box"></div>
</div>
Lastly, consider using CSS animations, JS canvas or an animation framework to do this sort of task; these tools will abstract away a lot of the math and state representation that animations involve.
I have a class, which basically draws a 16x30 grid.
Ideally, I would like to be able to execute:
OSD.setCursor(x y);
OSD.print('Text');
and have it position the word Text at position x, y with each character of Text positioned in the correct location in the grid.
Here's what I have so far:
class MAX7456 {
constructor() {
this.items = null;
this.divs = null;
}
begin() {
var ratioH = 16,
ratioW = 30;
var parent = $('<div />', {
class: 'grid',
width: ratioW * 25,
height: ratioH * 18
}).addClass('grid').appendTo('body');
for (var i = 0; i < ratioH; i++) {
for(var p = 0; p < ratioW; p++) {
this.divs = $('<div />', {
width: 25 - 1,
height: 18 - 1
}).appendTo(parent);
this.items = $('<span />', {
width: 25 - 1,
height: 18 - 1,
style: "padding-left: 2px;"
}).appendTo(this.divs);
}
}
}
setCursor(x, y) {
$('div > span:nth-child(2n-1)').text(function (i, txt) {
$(this).append(i)
i++;
//console.log(txt + $(this).next().text());
});
}
print (txt) {
}
}
var OSD = new MAX7456();
OSD.begin(); // create grid
OSD.setCursor(0, 0); // set text at cursor (x, y)
OSD.print("Label 2");
body {
padding: 0;
font-size: 12px;
}
.grid {
border: 1px solid #ccc;
border-width: 1px 0 0 1px;
}
.grid div {
border: 1px solid #ccc;
border-width: 0 1px 1px 0;
float: left;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
JSFiddle
Now this may seems not the ideal solution looking for, In that case my apologies. My requirement was to create a tiled grid based on an image so this how i managed to do it.
Total tile count can be vary as you need. (My case 2500 tiles)
When you adjust the image size that will determine what size of the tile can be.
(function($) {
var imagePadding = 0;
var pluginName = "tiles",
defaults = {
x: 2, // tiles in x axis
y: 2, // tiles in y axis
gap: {
x: 1,
y: 1
}
};
function Plugin(elem, options) {
options = $.extend({}, defaults, options);
var $elem = $(elem).wrap("<div class='tiles-wrapper' />"),
width = $elem.outerWidth(),
height = $elem.outerHeight(),
n_tiles = options.x * options.y,
tiles = [];
$elem.parent(".tiles-wrapper").css({
position: "relative",
width: width,
height: height
});
for (var $i = 0; $i < n_tiles; $i++) {
if ($i >= imagePadding) {
tiles.push("<div class='tile' data-id='" + $i + "' data-clipboard-text='" + $i + "'>" + $i + "</div>");
} else {
tiles.push("<div class='tile' data-id='" + $i + "' data-clipboard-text='" + $i + "'></div>");
}
}
var $tiles = $(tiles.join(""));
// Hide original image and insert tiles in DOM
$elem.hide().after($tiles);
// Set backgrounds
$tiles.css({
float: "left",
width: (width / options.x) - (options.gap.x || options.gap),
height: (height / options.y) - (options.gap.y || options.gap),
marginRight: options.gap.x || options.gap,
marginBottom: options.gap.y || options.gap,
backgroundImage: "url(" + $elem[0].src + ")",
lineHeight: (height / options.y) - (options.gap.y || options.gap) + "px",
textAlign: "center"
});
// Adjust position
$tiles.each(function() {
var pos = $(this).position();
this.style.backgroundPosition = -pos.left + "px " + -pos.top + "px";
});
}
$.fn[pluginName] = function(options) {
return this.each(function() {
new Plugin(this, options);
});
};
}(jQuery));
window.onload = function() {
$('#img').tiles({
x: 21.909,
y: 21.909
});
$(".tile").click(function() {
console.log($(this).data("id"));
});
};
.tiles-wrapper {
z-index: 999;
}
.tile:hover {
opacity: .80;
filter: alpha(opacity=80);
background: #fecd1f!important;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script type="text/javascript" src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<!-- Latest compiled and minified JavaScript -->
<script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<div class="container">
<div class="row">
<div class="col-md-8">
<div class="banner-head"></div>
<div class="row">
<div class="col-md-12">
<div class="image-holder">
<img id="img" src="data:image/gif;base64,R0lGODlh6ANsAwAAACH5BAAAAAAALAAAAADoA2wDhwAAAAAAMwAAZgAAmQAAzAAA/wArAAArMwArZgArmQArzAAr/wBVAABVMwBVZgBVmQBVzABV/wCAAACAMwCAZgCAmQCAzACA/wCqAACqMwCqZgCqmQCqzACq/wDVAADVMwDVZgDVmQDVzADV/wD/AAD/MwD/ZgD/mQD/zAD//zMAADMAMzMAZjMAmTMAzDMA/zMrADMrMzMrZjMrmTMrzDMr/zNVADNVMzNVZjNVmTNVzDNV/zOAADOAMzOAZjOAmTOAzDOA/zOqADOqMzOqZjOqmTOqzDOq/zPVADPVMzPVZjPVmTPVzDPV/zP/ADP/MzP/ZjP/mTP/zDP//2YAAGYAM2YAZmYAmWYAzGYA/2YrAGYrM2YrZmYrmWYrzGYr/2ZVAGZVM2ZVZmZVmWZVzGZV/2aAAGaAM2aAZmaAmWaAzGaA/2aqAGaqM2aqZmaqmWaqzGaq/2bVAGbVM2bVZmbVmWbVzGbV/2b/AGb/M2b/Zmb/mWb/zGb//5kAAJkAM5kAZpkAmZkAzJkA/5krAJkrM5krZpkrmZkrzJkr/5lVAJlVM5lVZplVmZlVzJlV/5mAAJmAM5mAZpmAmZmAzJmA/5mqAJmqM5mqZpmqmZmqzJmq/5nVAJnVM5nVZpnVmZnVzJnV/5n/AJn/M5n/Zpn/mZn/zJn//8wAAMwAM8wAZswAmcwAzMwA/8wrAMwrM8wrZswrmcwrzMwr/8xVAMxVM8xVZsxVmcxVzMxV/8yAAMyAM8yAZsyAmcyAzMyA/8yqAMyqM8yqZsyqmcyqzMyq/8zVAMzVM8zVZszVmczVzMzV/8z/AMz/M8z/Zsz/mcz/zMz///8AAP8AM/8AZv8Amf8AzP8A//8rAP8rM/8rZv8rmf8rzP8r//9VAP9VM/9VZv9Vmf9VzP9V//+AAP+AM/+AZv+Amf+AzP+A//+qAP+qM/+qZv+qmf+qzP+q///VAP/VM//VZv/Vmf/VzP/V////AP//M///Zv//mf//zP///wAAAAAAAAAAAAAAAAiuAPcJHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEOKHEmypMmTKFOqXMmypcuXMGPKnEmzps2bOHPq3Mmzp8+fQIMKHUq0qNGjSJMqXcq0qdOnUKNKnUq1qtWrWLNq3cq1q9evYMOKHUu2rNmzaNOqXcu2rdu3cOPKnUu3rt27ePPq3cu3r9+/gAMLHky4sOHDiBMrXsy4sePHkCNLnky5suXLmDNrSN7MubPnz6BDix5NurTp06hTq17NurXr17Bjy55Nu7bt27hz697Nu7fv38CDCx9OvLjx48iTK1/OvLnz59CjS59Ovbr169izaznfzr279+/gw4sfT768+fPo06tfz769+/fw48ufT7++/fv48+vfz7+///8ABijggAQWaOCBCCao4II0DDbo4IMQRijhhBRWaOGFGGao4YYcdujhhyCGKOKIJJZo4okopqjiiiy26OKLMMYo44w01i1o44045qjjjjz26OOPQAYp5JBEFmnkkUgmqeSSTDbp5JNQRinllFRWaeWVWGYpqeWWXHbp5ZdghinmmGSWaeaZaKap5ppstunmm3DGKeecdNZp55145qkn55589unnn4AGKuighBZq6KGIJqrooow26uijkEYq6aSUVmrppZhmI6rpppx26umnoIYq6qiklmrqqaimquqqrLbq6quwxirrrLTWImrrrbjmquuuvPbq66/ABivssMQWa+yxyCar7LLMNuvss9AfRivttNRWa+212Gar7bbcduvtt+CGK+645JZr7rnoph6r7rrstuvuu/DGK++89NZr77345qvvvvz26++/AAcdLPDABBds8MEIJ6zwwgw37PDDEEcs8cQUV2zxxRgbZ6zxxhx37PHHIIcs8sgkl2zyySinrPLKLLfsG/LLMMcs88w012zzzTjnrPPOPPfs889ABy300BlEF2300UgnrfTSTDft9NNQRy311FRXbfXVGVhnrfXWXHft9ddghy322GSXbfbZaKet9toXbLft9ttwxy333HTXbffdeOet99589+0X99+ABy744IQXbvjhiCeu+OKMN+7445AWRy755JRXbvnlmGeu+eacd+7556CHLhf66KSXbvrpqKeu+uqst+7667DHLvvstBXXbvvtuOeu++689+7778AHL/zwxBcVb/zxyCev/PLMN+/889BHL/301FdvFP312Gev/fbcd+/99+CHL/745JdvFP756Kev/vrst+/++/DHL//89NdvFf/9+Oev//789+///wAMoAAHSMACGhXwgAhMoAIXyMAGOvCBEIygBCdIwQoUWvCCGMygBjfIwQ568IMgDKEIR0gVwhKa8IQoTKEKV8jCFrrwhTCMoQxnE0jDGtrwhjjMoQ53yMMe+vCHQAwVohCHSMQiGvGISEyiEpfIxCY68YlQE4yiFKdIxSpa8YpYzKIWt8jFLnoS8YtgDKMYx0jGMprxjGhMoxrXE8jGNrrxjXCMoxznSMc62vGOeMwSox73yMc++vGPgAykIAdJyEIaE/KQiEykIhfJyEY68pGQjKQkJ0kSyUpa8pKYzKQmN8nJTnryk6AMEaUoR0nKUprylKhMpSpXycpWErrylbCMpSxnScta2vKWuMylLhB3ycte+vKXwAymMIdJzGIaEvOYyEymMpfJzGY685nQjKY0pxFJzWpa85rYzKY2t8nNbnrzmxDgDKc4x0nOcprznOhMpzrXEcnOdrrznfCMpzznSc962vOeEPjMpz73yc9++vOfAA2oQAcPStCCGvSgCE2oQhfK0IY6EfShEI2oRCdK0Ypa9KIYzahGDzfK0Y569KMgDalIR0rSkg+a9KQoTalKV8rSlrr0pTAPjalMZ0rTmtr0pjjNqU53D8rTnvr0p0ANqlCHStSiGg/1qEhNqlKXytSmOvWpUI0PqlSnStWqWvWqWM2qVrfKENWuevWrYA2rWMdK1rKa9awNaE2rWtfK1ra69a1wjQ+rXOdK17ra9a54zate98oO17769a+ADaxgB0vYwhoP9rCITaxiF8vYxjr2sZCNDqxkJ0vZylr2spjNrGY3DsvZznr2s6ANrWhHS9rSDpr2tKhNrWpXy9rWuva1DbCNrWxnS9va2va2uM0OrW53y9ve+va3wA2ucIcOS9ziGve4yE2ucpfL3OYMOve50I2udKdL3epaDve62M2udrfL3e5697vgDg2veMdL3vKa97zoTa96DNfL3va6973wja985wxL3/ra9774za9+98sO3/76978ADrCAB0zgAhsN+MAITrCCF8zgBjv4wQwQjrCEJ0zhClv4whgOzrCGN8zhDnv4wyAOsYgQR0ziEpv4xChOsYpX7JqAAAA7"
alt="event picture" />
</div>
</div>
</div>
</div>
</div>
</div>