I'm making an animation that each list item will float randomly inside its wrapper. I'm trying to put together using prototypal inheritance pattern but it's not doing it. There are a couple of places that I think it may be cause to the problem but I need some advice to proceed.
function Menu($item) {
this.item = $item;
};
Menu.prototype.makeNewPosition = function ($container) {
var h = $container.height() - 50;
var w = $container.width() - 50;
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
return [nh, nw];
};
Menu.prototype.move = function () {
$.each(this.item, function(index, value) {
var newq = this.makeNewPosition(value.parent());
value.animate({
top: newq[0],
left: newq[1]
}, 400, function() {
this.move();
});
});
};
var $menu = new Menu($('.nav li'));
$menu.move();
Within move function, there is animate and within this I'm calling move again to run the animation indefinitely. If it's calling itself in prototypal structure, can it be called using this?
Not also sure about the use of $.each within move function.
Here is JSfiddle
Here's a cleaner version
uses self instead of that - I use that, most people use self - I'm different :p
set $value = $(value) ... DRY
.
function Menu($item) {
this.item = $item;
};
Menu.prototype.makeNewPosition = function ($container) {
var h = $container.height() - 50;
var w = $container.width() - 50;
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
return [nh, nw];
};
Menu.prototype.move = function () {
var self = this;
$.each(this.item, function(index, value) {
var $value = $(value);
var newq = self.makeNewPosition($value.parent());
$value.animate({
top: newq[0],
left: newq[1]
}, 400, function() {
self.move();
});
});
};
var $menu = new Menu($('.nav li'));
$menu.move();
original answer below - which contains explanation of changes
Here's ALL the errors fixed, not just the first
function Menu($item) {
this.item = $item;
};
Menu.prototype.makeNewPosition = function ($container) {
var h = $container.height() - 50;
var w = $container.width() - 50;
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
return [nh, nw];
};
Menu.prototype.move = function () {
var that = this; // added that because the this isn't the this you want in the $.each function
$.each(this.item, function(index, value) {
var newq = that.makeNewPosition($(value).parent());
// ^^^^ ^^ ^
// that instead of this
// $(value) instead of value
$(value).animate({
// ^^ ^
// $(value) instead of value
top: newq[0],
left: newq[1]
}, 400, function() {
that.move();
//^^^^ another this to that change
});
});
};
var $menu = new Menu($('.nav li'));
$menu.move();
There's ways to clean up the code even more, but these changes should get you working at least
The working changes to your fiddle http://jsfiddle.net/wzmh63xb/16/
There is your mistakes. Try this. it is working.
Menu.prototype.move = function () {
var self = this;
$.each(this.item, function(index, value) {
var newq = self.makeNewPosition($(value).parent());
$(value).animate({
top: newq[0],
left: newq[1]
}, 400, function() {
self.move();
});
});
};
DEMO
Related
I continuously keep getting this error, and I don't really understand why.
Uncaught TypeError: Cannot read property 'top' of undefined
I have gone back in and added a if statement to see if it exists before hand, but still error persists.
$(document).ready(function(){
//setTimeout(function(){
animateDiv();
// },2000);
});
function makeNewPosition(){
// Get viewport dimensions (remove the dimension of the div)
var h = $(window).height() - 50;
var w = $(window).width() - 50;
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
return [nh,nw];
}
function animateDiv(){
var newq = makeNewPosition();
var oldq = $('.MyContain a').offset();
var speed = calcSpeed([oldq.top, oldq.left], newq);
// if (oldq.length) {
$('.MyContain a').animate({ top: newq[0], left: newq[1] }, speed, function(){
animateDiv();
});
//}
};
function calcSpeed(prev, next) {
var x = Math.abs(prev[1] - next[1]);
var y = Math.abs(prev[0] - next[0]);
var greatest = x > y ? x : y;
var speedModifier = 0.009;
var speed = Math.ceil(greatest/speedModifier);
return speed;
}
My mark-up is a bunch of text anchor links within .MyContain element. That I am trying to get just to move around randomly.
<div class="MyContain">
Sample
Demo
Coffee
<!-- etc -->
</div>
Please, play with teh fiddle below. ONE bug goes as it should - turns its "head" and crawls in proper direction. But several bugs (starting with two and up) destroy it all. Jquery "each" returns coordinates twice so instead of two sets of coordinates for two bugs FOUR are generated.
$(document).ready(function () {
function bug() {
$('.bug').each(function () {
//var bugs = $('.bug').length;
var h = $(window).height() / 2;
var w = $(window).width() / 2;
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
//$this = $(this);
//var newCoordinates = makeNewPosition();
var p = $(this).offset();
var OldY = p.top;
var NewY = nh;
var OldX = p.left;
var NewX = nw;
var y = OldY - NewY;
var x = OldX - NewX;
angle = Math.atan2(y, x);
angle *= 180 / Math.PI
angle = Math.ceil(angle);
console.log(p);
$(this).delay(1000).rotate({
animateTo: angle
});
$(this).animate({
top: nh,
left: nw
}, 5000, "linear", function () {
bug();
});
});
};
bug();
});
http://jsfiddle.net/p400uhy2/
http://jsfiddle.net/p400uhy2/4/
As mentioned by #Noah B, the problem is that each "bug" is setting the loop for all "bugs".
I'd make bug() function per element, so that each "bug" can be set individually.
EDIT (#Roko C. Buljan comment)
function bug() {
// ... your code ...
// calculate animation time, so that each of bugs runs same fast in long and short distance:
var top_diff = Math.abs(OldY - nh),
left_diff = Math.abs(OldX - nw),
speed = Math.floor(Math.sqrt((top_diff * top_diff) + (left_diff * left_diff))) * 15;
$(this).animate({
top: nh,
left: nw
}, speed, "linear", function () {
// rerun bug() function only for that single element:
bug.call(this);
});
};
$('.bug').each(bug);
DEMO
The problem is that you had .each() calling a function with .each() in it...so each bug had the bug() callback. You just have to move the bug() call outside of the .each(){}. See fiddle: http://jsfiddle.net/p400uhy2/2/
I found this post really very helpful and it is almost what I want to achieve
The problem is I want multiple div moving around a page, how can this be modified to allow for multiple elements moving randomly?
$(document).ready(function(){
animateDiv();
});
function makeNewPosition(){
// Get viewport dimensions (remove the dimension of the div)
var h = $(window).height() - 50;
var w = $(window).width() - 50;
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
return [nh,nw];
}
function animateDiv(){
var newq = makeNewPosition();
var oldq = $('.a').offset();
var speed = calcSpeed([oldq.top, oldq.left], newq);
$('.a').animate({ top: newq[0], left: newq[1] }, speed, function(){
animateDiv();
});
};
function calcSpeed(prev, next) {
var x = Math.abs(prev[1] - next[1]);
var y = Math.abs(prev[0] - next[0]);
var greatest = x > y ? x : y;
var speedModifier = .8;
var speed = Math.ceil(greatest/speedModifier);
return speed;
}
In this codepen I've made a new function targeting a new class but its drawing out the same movement.
http://codepen.io/Alex/pen/OPPBmX
One possibility is to contain the functionality within a function, and call that function multiple times.
You can have a containing div which all divs will be appended to:
<div class="animatedDivs"></div>
Then inside the function you can create a div, append it to that div, and animate it:
function newDiv() {
//Create
var $div = $("<div class='a'>");
//Append
$(".animatedDivs").append($div);
//Animate
animateDiv();
function animateDiv(){
//same code with $('.a') replaced by $div
};
}
function makeNewPosition(){
//same code
}
function calcSpeed(prev, next) {
//same code
}
Then you can create as many as you want:
$(document).ready(function () {
newDiv();
newDiv();
//etc...
});
Fiddle Example: http://jsfiddle.net/9agtb339/1/
I made some modifications on your code pen page and came up with an solution I believe:
I made all the divs the same class.
http://codepen.io/anon/pen/NPPOXg
$(document).ready(function(){
animateDiv();
animateDivTwo();
});
function makeNewPosition(){
// Get viewport dimensions (remove the dimension of the div)
var h = $(window).height() - 50;
var w = $(window).width() - 50;
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
return [nh,nw];
}
function animateDiv(element){
var newq = makeNewPosition();
var oldq = $('.a').offset();
var speed = calcSpeed([oldq.top, oldq.left], newq);
$(element).animate({ top: newq[0], left: newq[1] }, speed, function(){
animateDiv(element);
});
};
function animateDivTwo(){
var newq = makeNewPosition();
var oldq = $('.a').offset();
var speed = calcSpeed([oldq.top, oldq.left], newq);
$('.a').animate({ top: newq[0], left: newq[1] }, speed, function(){
animateDiv(this);
});
};
function calcSpeed(prev, next) {
var x = Math.abs(prev[1] - next[1]);
var y = Math.abs(prev[0] - next[0]);
var greatest = x > y ? x : y;
var speedModifier = .4;
var speed = Math.ceil(greatest/speedModifier);
return speed;
}
Your wrong:
You animate ".a" in your animateDivTwo function, seems like you want target ".b" here
You call animateDiv in animateDivTwo function
So, animateDivTwo calls once only
I correct your codepen
Please look into this JSFiddle.
Updated
<div class='a' name="animate"></div>
<div class='b' name="animate"></div>
$(document).ready(function(){
$("div[name=animate]").each(function(){
animateDiv($(this));
});
});
function animateDiv(c){
var newq = makeNewPosition();
$(c).animate({ top: newq[0], left: newq[1] }, function(){
animateDiv(c);
});
};
css
div.a, div.b {
width: 50px;
height:50px;
background-color:red;
position:fixed;
}
div.b{
background-color:green;
}
This is just an Idea to start with.
UPDATE
In this following updated JSFiddle I have created three Div. So this is the best way to add any number of divs. All that you have to do is add css. Infact we can randomize that too.
I am looking for a way to implement the exact functionality from this answer using angularjs: Specifically, for a box (div) to move randomly around a screen, while being animated. Currently I have tried
myApp.directive("ngSlider", function($window) {
return {
link: function(scope, element, attrs){
var animateDiv = function(newq) {
var oldRect = element.getBoundingClientRect();
var oldq = [oldRect.top, oldRect.left];
if (oldq != newq){
var dir = calcDir([oldq.top, oldq.left], newq);
element.moveTo(oldq.left + dir[0], oldq.top + dir[1]);
setTimeout(animateDiv(newq), 100);
}
};
var makeNewPosition = function() {
// Get viewport dimensions (remove the dimension of the div)
var window = angular.element($window);
var h = window.height() - 50;
var w = window.width() - 50;
var nh = Math.floor(Math.random() * h);
var nw = Math.floor(Math.random() * w);
return [nh,nw];
};
function calcDir(prev, next) {
var x = prev[1] - next[1];
var y = prev[0] - next[0];
var norm = Math.sqrt(x * x + y * y);
return [x/norm, y/norm];
}
var newq = makeNewPosition();
animateDiv(newq);
}
};
});
There appears to be quite a bit wrong with this, from an angular point of view. Any help is appreciated.
I like to take advantage of CSS in situations like this.
absolutely position your div
use ng-style to bind top and left attributes to some in-scope variable
randomly change the top and left attributes of this in-scope variable
use CSS transitions on top and left attributes to animate it for you
I made a plnkr! Visit Kentucky!
I have the following piece of code:
// Core Zoom Logic, independent of event listeners.
$.zoom = function(target, source, img) {
var outerWidth,
outerHeight,
xRatio,
yRatio,
offset,
position = $(target).css('position');
// This part of code is omitted
return {
init: function() {
outerWidth = $(target).outerWidth();
outerHeight = $(target).outerHeight();
xRatio = (img.width - outerWidth) / $(source).outerWidth();
yRatio = (img.height - outerHeight) / $(source).outerHeight();
offset = $(source).offset();
},
move: function (e) {
var left = (e.pageX - offset.left),
top = (e.pageY - offset.top);
top = Math.max(Math.min(top, outerHeight), 0);
left = Math.max(Math.min(left, outerWidth), 0);
img.style.left = (left * -xRatio) + 'px';
img.style.top = (top * -yRatio) + 'px';
},
automove: function() {
// can I recall this?
}
};
};
What I want to achieve is to add the following effect in automove() function:
$(img).animate({
top: newTop,
left: newLeft,
}, 1000, function() {
automove(); /* recall */
});
But how to call automove again from it's body? Maybe I should completely change the way functions are declared in $.zoom function?
If you want to recursively call automove() from inside itself the traditional method would be to use arguments.callee. So the code would look something like:
return {
/* ... */
automove: function() {
$(img).animate({
top: newTop,
left: newLeft,
}, 1000,
arguments.callee /* recall */
);
}
}
But in HTML5 this is deprecated and is actually illegal in strict mode. Instead you can simply give the function a name:
return {
/* ... */
automove: function myAutomove () { // <-- give it a name
$(img).animate({
top: newTop,
left: newLeft,
}, 1000,
myAutomove /* recall */
);
}
}
Named function expression works in all browsers old and new and is much easier to read.
note:
If a function does not require parameters you can simply pass a reference to it as a callback instead of wrapping it in an anonymous function:
setTimeout(function(){ foo() },100); // <-- this is completely unnecessary
setTimeout(foo,100); // <-- just need to do this instead
Try
$.zoom = function(target, source, img) {
var outerWidth,
outerHeight,
xRatio,
yRatio,
offset,
position = $(target).css('position');
// This part of code is omitted
var fnInit = function() {
outerWidth = $(target).outerWidth();
outerHeight = $(target).outerHeight();
xRatio = (img.width - outerWidth) / $(source).outerWidth();
yRatio = (img.height - outerHeight) / $(source).outerHeight();
offset = $(source).offset();
};
var fnMove = function (e) {
var left = (e.pageX - offset.left),
top = (e.pageY - offset.top);
top = Math.max(Math.min(top, outerHeight), 0);
left = Math.max(Math.min(left, outerWidth), 0);
img.style.left = (left * -xRatio) + 'px';
img.style.top = (top * -yRatio) + 'px';
};
var fnAutomove = function() {
$(img).animate({
top: newTop,
left: newLeft,
}, 1000, function() {
fnAutomove(); /* recall */
});
}
return {
init: fnInit,
move: fnMove,
automove: fnAutomove
};
};