The Plan
I want to make a animation using JavaScript and CSS, The plan is to make snow fall from the top of the div to the bottom. In short currently I create a div and just append a few child DOM elements that will be created with JavaScript and appended to the already created parent. The snow will also pick up when the browser window changes and then automatically create. The code is done using OOP and it currently will change the CSS properties of each new child DOM element that was created to increase the top and left properties (they will all be set to position absolute) and once the DOM element is off screen it will reset and start over only making sure to stick to use the limit I set in the Object property.
Problem
Currently If I run this method snow.animate in a setInterval - it says that it cant find the length property of the array that I pushed all the created child DOM elements in, but if i console.log() the array and try and get the length of the array then it actually shows me the length. I logged before the for loop and inside it to check if I can get the length of the array and it gets it but not when run through the setInterval.
Question
My question is, am I missing something or will I need to just make separate function and not use a Object to store all the methods?
note - I made a while loop inside the animation method - I felt very uncomfortable doing this. Is this safe or bad practice and is there another way of using the while loop to create the child elements that will be pushed in the array to use in the for loop that comes after.
DEMO Code - JSFIDDLE: https://jsfiddle.net/sb9o0t4m/
HTML
<div id="snowglobe"></div>
CSS
body {
background-color: #000;
margin: 0;
}
/*---------------Snow Flakes--------------*/
#snowglobe {
width: 100%;
min-height: 650px;
position: relative;
top: 0;
left: 0;
overflow: hidden;
}
#snowglobe .flake {
position: absolute;
width: 1px;
height: 1px;
color: rgba(255,255,255,0);
text-shadow: 0 0 20px #fff;
}
JS
var snow = {
//variables
width: window.innerWidth,height: 650,flakeCount: 10,
gravity: 0.7,windSpeed: 20,flakes: [],
currentFlake: 0,snowGlobe: document.getElementById('snowglobe'),
//methods
getRandom: function(min,max){
return Math.floor(Math.random() * (max - min) + min);
},
animate: function(){
//while loop
while (this.currentFlake < this.flakeCount){
var flake = document.createElement('div'),
newFlake;
flake.className = "flake";
flake.style.fontSize = this.getRandom(20,50) + 'px';
flake.style.top = this.getRandom(0,this.height) + 'px';
flake.style.left = this.getRandom(0,this.left) + 'px';
flake.innerHTML = "*";
newFlake = this.snowGlobe.appendChild(flake);
//set a speed property to the newflake obj
newFlake.speed = this.getRandom(1,100);
this.flakes.push(newFlake);
this.currentFlake++;
}
var flakes = this.flakes;
//use the array of dom elements
for(var i = 0; i < flakes.length; i++){
positionX = false;
positionY = false;
//new Y position
positionY = parseFloat(flakes[i].style.top) + (flakes[i].speed / 100) * this.gravity;
if(positionY > this.height){
positionY = 0 - parseInt(flakes[i].style.fontSize);
positionX = getRandom(0, width);
}
//new X position
if (!positionX) positionX = parseFloat(flakes[i].style.left) + Math.sin(positionY / this.windSpeed);
if (positionX < -20) positionX = this.width + 20;
if (positionX > this.width + 20) positionX = -20;
// Set new position
flakes[i].style.top = positionX + 'px';
flakes[i].style.left = positionX + 'px';
}
}
}
//check browser window, did it resize?
window.onresize = function(){
snow.width = window.innerWidth;
console.log(snow.width);
}
setInterval(snow.animate, 1000/60);
//if you want, coment out the above line and uncoment the one below to see the snow loading
//snow.animate();
Thanks in advance
The problem is that this is not set to the proper object. For details, see the "this" problem.
You could call the method like this:
setTimeout( function(){ snow.animate(); }, 1000/60 );
but it's better to use requestAnimationFrame:
var snow = {
...
animate: function() {
...
window.requestAnimationFrame( (function(){ this.animate() }).bind(this) );
}
}
and you could simply start the animation by calling snow.animate().
I also took the liberty of fixing some errors that you wouldn't be aware of because the code wouldn't run (referencing width without this etc..).
Here's an updated fiddle.
I left your initialisation loop as is; since it only runs once, normally you'd put this in a separate method, usually a constructor or initialisation method. The only downside here is that there is the overhead of checking the while condition on each animation frame.
Related
I have two DIV's of different widths on top of each other. The top DIV displayDIV is wider than the bottom DIV captureDIV.
In the displayDIV I'm drawing a dot who's X position is proportionate to the mouse position within captureDIV.
As you move the mouse in captureDIV the dot moves proportionately in DisplayDIV.
It makes much more sense if you look at this fiddle
My code is as follows...
let capture = document.getElementById('captureDIV');
let display = document.getElementById('displayDIV');
let circle = document.getElementById('circle');
capture.addEventListener('mousemove', handleMouseMove);
function handleMouseMove(event) {
const captureRect = capture.getBoundingClientRect();
const captureWidth = captureRect.right - captureRect.left;
const relativeX = event.x - captureRect.left;
let percent = (relativeX / captureWidth) * 100;
let roundedPercent = parseFloat(Math.round(percent * 100) / 100).toFixed(2);
moveDotTo(roundedPercent);
}
function moveDotTo(percentage) {
const displayRect = display.getBoundingClientRect();
const displayWidth = displayRect.right - displayRect.left;
const circleX = displayRect.left + displayWidth * (percentage / 100);
const circleY = displayRect.top + (displayRect.height / 2);
const style = `top:${circleY}px;left:${circleX}px;`;
circle.setAttribute('style', style);
}
I also have a number of buttons that can set the position of the dot within DisplayDIV such as...
let move20 = document.getElementById('move20');
move20.addEventListener('click', function(event) {
moveDotTo(20);
});
Using Vanilla JS not CSS tricks, how can I create a function to animate (rather than move) the dot from its existing position to the new position.
function animateDotTo(percentage) {
// clever code here
}
I need to be able to call the animateDotTo(percentage) function from either a button or from the mousemove event handler.
The dot should always animate to its new position regardless of how the move is triggered. For instance if the mouse is moved out of the left side of the captureDIV round the bottom and then into the right side of the captureDIV the dot should animate across the DisplayDIV not jump as it does now. Equally pressing one of the move to x% buttons should animate the dot from its current position to the new one.
If you are drawing a circle and moving it around, I would suggest drawing to a <canvas> element instead of moving a <div> by setting its top and left properties. Even using transform: translate(x, y) might be better.
In order to smoothly transition your dot from one location to another, using JavaScript, you will want:
The dot's current position as x and y coordinates,
The dot's target position as x and y coordinates, and
The speed at which the dot moves as a scalar.
Updating the current position is done at every animation frame with window.requestAnimationFrame. With these in hand, and a way of applying the resulting calculated position to the dot, you can use a method like this one: How to move an object using X and Y coordinates in JavaScript to move your dot (the example moves a canvas, but if you know the x and y, then you can set them to top and bottom).
Answering my own question, with thanks to Billy Brown for pointing me in the right direction. Using window.requestAnimationFrame is the way to go.
var currentPercentage;
var startPercentage;
var targetPercentage;
function animateDotTo(percentage) {
targetPercentage = percentage;
startPercentage = currentPercentage;
window.requestAnimationFrame(step);
}
function step(timestamp) {
var fps = 7;
var maxStep = 30;
var distStartToTarget = Math.abs(startPercentage - targetPercentage);
var stepSize = Math.min(distStartToTarget / fps, maxStep);
if (targetPercentage < startPercentage) {
currentPercentage -= stepSize,0;
if (currentPercentage > targetPercentage) {
window.requestAnimationFrame(step);
}
} else if (targetPercentage > startPercentage) {
currentPercentage += stepSize,100;
if (currentPercentage < targetPercentage) {
window.requestAnimationFrame(step);
}
} else {
return;
}
if (currentPercentage > 100 ) { currentPercentage = 100; }
if (currentPercentage < 0 ) { currentPercentage = 0; }
moveDotTo(currentPercentage);
}
Updated fiddle
A simple trick in css transition will fix this.
Of course. You don't want it to animate when you're actually moving the mouse. So what I did is that I separate the transition css property on another class and then remove that class on mouse move, re-attaching it when we click the move buttons.
CSS
#circle {
position: absolute;
left: -100px;
top: -100px;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #000;
transition: none;
}
#circle.animate{
transition: 500ms ease;
}
JS
move20.addEventListener('click', function(event) {
moveDotTo(20); animateDotTo();
});
move60.addEventListener('click', function(event) {
moveDotTo(60);animateDotTo();
});
move80.addEventListener('click', function(event) {
moveDotTo(80);animateDotTo();
});
function moveDotTo(percentage) {
circle.classList.remove("animate");
const displayRect = display.getBoundingClientRect();
const displayWidth = displayRect.right - displayRect.left;
const circleX = displayRect.left + displayWidth * (percentage / 100);
const circleY = displayRect.top + (displayRect.height / 2);
const style = `top:${circleY}px;left:${circleX}px;`;
circle.setAttribute('style', style);
}
function animateDotTo(percentage) {
circle.classList.add("animate");
}
http://jsfiddle.net/8pm2grjd/
If you want it to animate even if you're triggering the movement using mousemove, you can disregard the class approach and just slap the transition property on the css. But this will simulate the annoying mouse delay effect similar to input delay on video games due to V-Sync.
So I have been trying endlessly to try and do something similar too what this site is doing (http://whois.domaintools.com/). I'm trying to get a webpage, so wherever the mouse moves over the webpage, that kind of effect follows it (I'm sorry I don't know what I would call the effect).
I've read how to ask questions on here, but I don't know what too look for so it's difficult for me to attempt this. So far this link (http://p5js.org/learn/demos/Hello_P5_Drawing.php) I've used the code from this and played around with it but i'm just puzzled as too how I would go about doing this.
Thanks for any help, I've been banging my head against a brick wall for a good couple of days now.
This seems to be some kind of particle system. I would start the following way: First create a class for a particle, it should have a random x and y coordinate, and it should change it's postion periodically to a random new postion. Then create a lot of instances of the particle and distribute them over the page.
http://jsfiddle.net/aggoh0s1/3/
/* each particle will move in a 100px100px square */
var gutterWidth = 100;
/* class definition */
var Particle = function(x, y) {
var t = this;
t.x = x;
t.y = y;
t.elem = $('<div class="particle" />');
t.elem.css({ left: x+"px", top: y+"px"});
$('body').append(t.elem);
/* create a new position every 500-1000 milliseconds */
var milliSecs = 500 + Math.random() * 500;
t.ptinterval = setInterval(function() {
var dx = Math.round(Math.random() * gutterWidth);
var dy = Math.round(Math.random() * gutterWidth);
t.elem.animate({left: (t.x + dx)+"px", top: (t.y + dy) + "px"}, 600);
}, milliSecs);
};
/* create a 1000px1000px area where particles are placed each 100px */
var particles = [];
var newParticle;
for(var x = 0; x < 1000; x = x + gutterWidth) {
for(var y = 0; y < 1000; y = y + gutterWidth) {
newParticle = new Particle(x,y);
particles.push(newParticle);
}
}
CSS:
.particle {
width: 2px;
height: 2px;
background-color: black;
position: absolute;
}
Using this logic, you could also use a canvas to display the particles instead of a html div like it is done on whois.domaintools.com. The next step should be to connect the particles with lines to each other, and after that some code should hide all particles that are some distance away from the mouse position.
I've developed the following solution for the effect which you are referring. This is done using jQuery using the event mousemove(). Bind this event to your body where the content is.
Method :
Create an element with the following css on your body. You can create the element onthefly using jQuery as well.
<div class='hover'></div>
CSS
.hover{
position:absolute;
width:100px;
height:100px;
background-color:#fff;
}
The add the following code to your page.
$('body').mousemove(function(event){
$('.hover').css({
'top' : event.pageY,
'left': event.pageX
})
});
The above code will bind an event to your mouse move and I change the element position according to the mouse coordinates.
This fiddle shows a running example
I've given you the basic idea of the solution! You will have to medle with the css and jquery to add the looks and feels of the effect which you refer to.
See the simple example
<img id="imgMove" src="Images/img1.jpg" height="100" width="100" style="position: absolute;" />
JQuery
$(document).ready(function () {
$(document).mousemove(function (e) {
$("#imgMove").css({ "top": e.pageY - 50, "left": e.pageX - 50 }); // e.pageX - Half of Image height, width
})
})
first of all i want to say that the following function is by chance and by this i mean that its functioning is very strange:
function x (e,s,v){ // e =element, s = desired size of an element, v = speed
var div = document.getElementById(e),
width = 0;
for(var i =0; width<s; i++){
if(i%v === 0){
width = width+1;
div.setAttribute('style', 'width:'+width+'px;');
}else{
width = width +0;
}
};
};
this function is working perfectly and doing what i want but the problem is that the width is changing at once when the working of this function is finished.
in detail
i want that the width of an element increase smoothly, increase 'one' by 'one' px. so i made this function.
there is an if statement because if i didn't put that there then the width of that element would increase at once. that if statement delay the time between the adding of two pixels
but now the problem is that it is adding pixels one by one but the width is increasing at once after the completion of the function.
for example if i write in console x('aynElement', 500, 100) then it is adding pixels one by one but the width of the element is increasing at once when the function stop functioning
you can see this yourself in console
link to JsFiddle for full code
secondly
the problem is that this is strange. you had absolutely felt weird after reading this function. please anyone explain me this weirdness.
thanks
You can use setTimeout() to do this.
function x(e, s, v) { // e =element, s = size of an element, v = speed
var div = document.getElementById(e),
width = 0;
for (var i = 0; i < (s - width); i++) {
setTimeout(function() {
div.setAttribute('style', 'width:' + width+++'px;');
}, i * (1 / (v * 0.01)));
};
};
x('d1', 400, 10)
#d1 {
width: 100px;
height: 100px;
background-color: #aaa;
}
<body>
<div id="d1" style="width:200px;"></div>
</body>
I think this would solve it better (might need the one or other adjustment, just a quick scrape):
DEMO
setInterval(function(){
grow('id',250) //set your parameters here
}, 100); //this is the speed - the lower the quicker
var width = 1;
function grow(e,s) {
document.getElementById('d1').style.width = width+"px";
width++;
};
setInterval(function(){
grow('id',500)
}, 10);
var width = 250;
function grow(e,s) {
document.getElementById('d1').style.width = width+"px";
width++;
};
#d1{
height: 100px;
background-color: #000;
}
<body>
<div id="d1" ></div>
</body>
I've been fooling around with JavaScript/JQuery, and decided to create a little program which would animate a ball bouncing around a rectangular boundary area. I believe that the logic of my code should make sense, but for some reason I can't get it to change directions. What is even stranger is that I put the ball's x and y positions as text on it, but it seems statically stuck (it doesn't change), but I see when I inspect the element that it's left and top css parameters are changing over time.
Here's the code:
<!DOCTYPE html>
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js">
</script>
<style>
.ball {
width: 50px;
height: 50px;
background: red;
-moz-border-radius: 50px;
-webkit-border-radius: 50px;
border-radius: 50px;
position: absolute;
}
.boundary {
width: 700px;
height: 500px;
background: #AAAAAA;
border-style:solid;
border-width:5px;
position: absolute;
}
</style>
<script>
$(document).ready(function(){
var b = new ball(1, 1, 10, 10);
for(i = 0; i < 1000; i++)
b.moveBall();
});
function ball(xPos, yPos, xVel, yVel)
{
this.xPos = xPos;
this.yPos = yPos;
this.xVel = xVel;
this.yVel = yVel;
this.rightBound = false
this.leftBound = false;
this.upBound = false;
this.downBound = false;
this.width = 50;
this.height = 50;
this.moveBall = moveBall;
function moveBall()
{
var h = 500;
var w = 700;
//detect if it is at x bounds
if(this.xPos + this.width + this.xVel > w)
{this.rightBound = true;}
else if(this.xPos + this.xVel < -1)
this.leftBound = true;
else
{
this.rightBound = false;
this.leftBound = false;
}
//detect if it is at y bounds
if(this.yPos + this.height + this.yVel > h)
{this.downBound = true;}
else if(this.yPos + this.yVel < -1)
this.upBound = true;
else
{
this.upBound = false;
this.downBound = false;
}
//handle velocity changes for bounds
//so you switch the x direction if x bound is met, same for y
if(this.rightBound || this.leftBound)
this.xVel *= -1;
if(this.upBound || this.downBound)
this.yVel *= -1;
//now give motion
this.xPos += xVel;
this.yPos += yVel;
//now draw
$(".ball").animate({
left:this.xPos + 'px',
top:this.yPos + 'px'
}, 150).text(this.xPos + "," + this.yPos);
}
}
</script>
</head>
<body>
<div class="boundary">
<div class="ball"></div>
</div>
</body>
</html>
The weird thing is that it seems to automatically put the end value of 10,001, 10,001 (assuming it never changes direction) as it's (x,y) coordinates from the very beginning. Anything that could point me in the right direction would be appreciated! And sorry if it's some basic error, I tried to ensure it wasn't but sometimes they slip through!
You're doing your ball.moveBall in a loop, not on a timer. So it's going as fast as computably possible. Yes, computably isn't a word. Moving on.
By calling $.animate you're saying you want jQuery to handle moving the object from one spot to another. jQuery goes way way way way way slower than the computer. Hang on, this is getting confusing. Let me put it simply.
You loop through ball.moveBall 1000 times. How long does that take? Virtually no time at all. That's why the coordinates stay stuck at 1000 the whole time. It actually gets there super super super fast. So fast, it gets there basically before jQuery has time to start moving the ball. And.... then the ball starts moving. Why? Why doesn't it actually move to position 1000,1000 right away? Well, the coordinates do get to 1000,1000. But jQuery is like, "Oh, okay, move the ball to position 1000,1000. I can do that! Really really slowly...". You're probably tired of hearing the explanation, so here's the fix:
Change $(".ball").animate to $(".ball").css. And change your loop to window.setInterval(function(){b.moveBall()},1000) or something like that. I've set up a fiddle for you here: http://jsfiddle.net/uXbwR/
You'll notice it moves really slowly. That's cause I set the interval to 1000 milliseconds, which means it only moves once every second. For a game you'll want something like 1000/60 (once every 60th of a second), but I tried that and it makes the ball move super fast. You've got the ball's speed really high. You might wanna try turning that down a bit.
That's all I've got.
Edit
Computationally. That's the word I was looking for.
You should call the next step of the animation only when the preceding has completed. You are telling animate to take 150ms, but the while loop completes almost instantly, without waiting each step.
[EDIT]
#Samuel answer is complete and already suggested you a good workaround. I guess this is going to be beyond the purposes of your application, but if you're interested in setting up a proper Javascript game mainloop these are some useful resources followed by an implementation of mine:
Fabien Sanglard, Game timers: Issues and solutions
Paul Irish, requestAnimationFrame for smart animating.
var RENDERING_FRAME_TIME = 1000/60; // ms
var PHYSICS_FRAME_TIME = 5; // ms
var currentTime = new Date().getTime();
var accumulator = 0;
(function mainloop(){
newTime = new Date().getTime();
accumulator = newTime - currentTime;
currentTime = newTime;
while (accumulator > PHYSICS_FRAME_TIME) {
integrate(PHYSICS_FRAME_TIME);
accumulator -= PHYSICS_FRAME_TIME;
}
requestAnimationFrame(mainloop);
render();
})();
Actually I'm looking for a jQuery plug-in that can handle this:
there is a container with overflow hidden
inside of this is another one, which is way larger
when i move over the div, the part I'm seeing depends on my current position
when I'm in the left top corner I see the top left corner of the inner container
when I'm in the middle i see the middle of the container …
I wrote a little JavaScript that does that:
this.zoom.mousemove( function(event) {
var parentOffset = $(this).parent().offset();
var relativeX = event.pageX - parentOffset.left;
var relativeY = event.pageY - parentOffset.top;
var differenceX = that.zoom.width() - that.pageWidth;
var differenceY = that.zoom.height() - that.pageHeight;
var percentX = relativeX / that.pageWidth;
var percentY = relativeY / that.pageHeight;
if (1 < percentX) {
percentX = 1;
}
if (1 < percentY) {
percentY = 1;
}
var left = percentX * differenceX;
var top = percentY * differenceY;
that.zoom.css('left', -left).css('top', -top);
});
But this isn't very smooth and kinda jumpy, when you enter from another point of the container. So, before reinventing the wheel: Is there one plug in, that does exactly that and has iOS support (drag instead of mouse move)? All zoom plug ins (like Cloud Zoom) are over the top for this purpose and most have no support for dragging on iOS.
And if there's not something like this. How can I make this smoother and cooler. Any approach would be helpful. :)
Many thanks.
So, here is my solution - which works pretty well and is easy to achieve. There could be done some improvement, but to get the idea i'll leave it that way. :)
there is a demo on jsfiddle:
http://jsfiddle.net/insertusernamehere/78TJc/
CSS
<style>
div.zoom_wrapper {
position: relative;
overflow: hidden;
width: 500px;
height: 500px;
border: 1px solid #ccc;
cursor: crosshair;
}
div.zoom_wrapper > * {
position: absolute;
}
</style>
HTML
<div class="zoom_wrapper">
<img id="zoom" src="http://lorempixel.com/output/people-q-c-1020-797-9.jpg" alt="">
</div>
JAVASCRPT
<script>
var zoom = null;
// this function will work even if the content has changed
function move() {
// get current position
var currentPosition = zoom.position();
var currentX = currentPosition.left;
var currentY = currentPosition.top;
// get container size
var tempWidth = zoom.parent().width();
var tempHeight = zoom.parent().height();
// get overflow
var differenceX = zoom.width() - tempWidth;
var differenceY = zoom.height() - tempHeight;
// get percentage multiplied by difference (in pixel) cut by percentage (here 1/4) that is used as "smoothness factor"
var tempX = zoom.data('x') / tempWidth * differenceX / 4;
var tempY = zoom.data('y') / tempHeight * differenceY / 4;
// get real top and left values to move to and the last factor slows it down and gives the smoothness (and it's corresponding with the calculation before)
var left = (tempX - currentX) / 1.25;
var top = (tempY - currentY) / 1.25;
// finally apply the new values
zoom.css('left', -left).css('top', -top);
}
$(document).ready(function () {
zoom = $('#zoom');
//handle mousemove to zoom layer - this also works if it is not located at the top left of the page
zoom.mousemove( function(event) {
var parentOffset = $(this).parent().offset();
zoom.data('x', event.pageX - parentOffset.left);
zoom.data('y', event.pageY - parentOffset.top);
});
// start the action only if user is over the container
zoom.hover(
function() {
zoom.data('running', setInterval( function() { move(); }, 30) );
},
function() {
clearInterval(zoom.data('running'));
}
);
});
</script>
Note:
This one has, of course, no support for touch devices. But for that I use (again)/I can recommend the good old iScroll … :)