Recursively call function returned as object - javascript

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
};
};

Related

How can I make images going down the screen randomly and at a certain speed (video game js)?

I am building a game where a spaceship moves into the screen with PC controllers. Now, my remaining part is to make a fireball images drop of the screen randomly with a precise speed and quantity (because the image is only one, we have to multiplicate it). Can someone achieve this?
Here is the code:
Fireball image:
<img src="Photo/fireball.png" id="fireball">
Spaceship image:
<img src="Photo/Spaceship1.png" id="icon-p">
Spaceship moving with controllers + prevent it from going out of screen:
let display = document.getElementById("body");
let rect = icon;
let pos = { top: 1000, left: 570 };
const keys = {};
window.addEventListener("keydown", function(e) {
keys[e.keyCode] = true
});
window.addEventListener("keyup", function(e) {
keys[e.keyCode] = false
});
const loop = function() {
if (keys[37] || keys[81]) { pos.left -= 10; }
if (keys[39] || keys[68]) { pos.left += 10; }
if (keys[38] || keys[90]) { pos.top -= 10; }
if (keys[40] || keys[83]) { pos.top += 10; }
var owidth = display.offsetWidth;
var oheight = display.offsetHeight;
var iwidth = rect.offsetWidth;
var iheight = rect.offsetHeight;
if (pos.left < 0) { pos.left = -10; }
if (pos.top < 0) { pos.top = -10; }
if (pos.left + iwidth >= owidth) { pos.left = owidth - iwidth; }
if (pos.top + iheight >= oheight) { pos.top = oheight - iheight; }
rect.setAttribute("data", owidth + ":" + oheight);
rect.style.left = pos.left + "px";
rect.style.top = pos.top + "px";
};
let sens = setInterval(loop, 1000 / 60);
// Random X coordiante
function rndScreenX(offset) {
return Math.floor(Math.random() * (window.innerWidth - offset));
}
// Set fireball coordinates (X is random)
let fireballElement = document.querySelector('#fireball');
let fireball = {
x: rndScreenX(fireballElement.offsetWidth),
y: 0
}
const loop = function() {
// Change fireball Y
fireball.y += 10;
fireballElement.style.top = fireball.y + 'px';
if (fireball.y > window.innerHeight) {
// Fireball is out of window
// Reset Y and get new random X
fireball.x = rndScreenX(fireballElement.offsetWidth);
fireballElement.style.left = fireball.x + 'px';
fireball.y = 0;
}
};
fireballElement.style.left = fireball.x + 'px';
let sens = setInterval(loop, 1000 / 60);
#fireball {
position: absolute;
/* Ignore this rule if you're using an image */
width: 50px;
height: 50px;
background: red;
border-radius: 40% 40% 50% 50%;
}
<img src="Photo/fireball.png" id="fireball">
This solution includes three configurable variables: spawnRate, advanceRate, and fallDistance. It uses them to determine how often new fireballs spawn, how often they move down the screen, and how far they move on each 'tick'.
The "main" part of the script consists of two setInterval calls, one to handle spawning new fireballs, and the other to handle advancing them down the screen.
(See the in-code comments for further explanation.)
const
display = document.getElementById("display"), // Container element
fireballs = [], // Array to hold all fireball objects
fallDistance = 6; // Measured in `vh` units (but could be whatever)
spawnRate = 2000,
advanceRate = 500;
// Adds the first fireball immediately
spawnFireball(fireballs);
// Moves all fireballs down every 500 milliseconds
const advancerTimer = setInterval(
function(){ advanceAll(fireballs, fallDistance, display); },
advanceRate
);
// Spawns a new fireball every 2000 milliseconds
const spawnerTimer = setInterval(
function(){ spawnFireball(fireballs); },
spawnRate
);
// Defines a function to add a fireball to the array
function spawnFireball(fireballs){
const
img = document.createElement("img"), // Element to add to screen
x = Math.floor(Math.random() * 96) + 2, // Random `x` position
y = 3; // `y` position starts near top of screen
img.src = "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse1.mm.bing.net%2Fth%3Fid%3DOIP.UyMqod0eO6Qcmco1Zrmj0QAAAA%26pid%3DApi&f=1",
img.classList.add("fireball"); // To style fireballs
img.style.left = x + "vw"; // `x` position will never change
newFb = { x, y, img }; // `fb` object includes coords + img element
fireballs.push(newFb); // Adds the new fireball to the array
}
// Defines a function to advance a fireball's position
function advance(fb, distance){
fb.y += distance;
}
// Defines a function to draw a fireball in the container
function draw(fb, container){
if(fb.y > 100){ return; } // Ignores below-screen fireballs
fb.img.style.top = fb.y + "vh"; // Updates the location on screen
container.appendChild(fb.img); // The `img` property holds our DOM element
}
// Defines a function to advance and draw all fireballs
function advanceAll(fireballs, distance, container){
for(let fb of fireballs){
advance(fb, distance);
draw(fb, container)
}
}
#display{ height: 99vh; width: 99vw; position: relative; }
.fireball{ height: 2em; width: 2em; position: absolute; }
<div id="display"></div>

Passing properties object to jQuery animate function

I have this piece of code:
var delta = _window.width() / 30, // resize hexagons by delta px
window_height = _window.height(); // .box-1/window height
$('.hexagon').each(function () { // get animated boxes
var $this = $(this),
width_1 = $this.width(),
height_1 = $this.height(),
multi = 0, // adjust resize to scroll speed (depends on delta)
ratio = 1.167; // to calculate height
// animation properties
var enlarge_obj = {
width: function() {return width_1 += (delta * multi)},
height: function() {return width_1 * ratio}
},
shrink_obj = {
width: function() {return width_1 -= (delta * multi)},
height: function() {return width_1 * ratio}
};
....
function scrollHandler() {
...
$this.animate(enlarge_obj, 0, 'linear');
...
$this.animate(shrink_obj, 0, 'linear');
}
}
I want to pass enlarge_obj and shrink_obj to jQuery animate function.
But it doesn't work.
If I write code like this:
$this.animate({width: width_1 += (delta * multi), height: width_1 * ratio},0,'linear');
it works fine.
What am I doing wrong? Thx.

Random moving animation using prototypal inheritance pattern

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

fengyuanchen Cropper - How to Fit Image into Canvas If Rotated?

I gone through documentation of cropper by fengyuanchen. I want the image to be fit by default into canvas if rotated. But I couldnt find a way to achieve this. Any idea how to achieve this functionality?
I want it to be like this to be default: link
Check issue demo here: link
I fixed this behavior but for my special needs. I just needed one rotate button which rotates an image in 90° steps. For other purposes you might extend/change my fix.
It works in "strict" mode by dynamically change the cropbox dimensions.
Here my function which is called, when I want to rotate an image. Ah and additionally the misplacement bug has also been fixed.
var $image;
function initCropper() {
$image = $('.imageUploadPreviewWrap > img').cropper({
autoCrop : true,
strict: true,
background: true,
autoCropArea: 1,
crop: function(e) {
}
});
}
function rotateImage() {
//get data
var data = $image.cropper('getCropBoxData');
var contData = $image.cropper('getContainerData');
var imageData = $image.cropper('getImageData');
//set data of cropbox to avoid unwanted behavior due to strict mode
data.width = 2;
data.height = 2;
data.top = 0;
var leftNew = (contData.width / 2) - 1;
data.left = leftNew;
$image.cropper('setCropBoxData',data);
//rotate
$image.cropper('rotate', 90);
//get canvas data
var canvData = $image.cropper('getCanvasData');
//calculate new height and width based on the container dimensions
var heightOld = canvData.height;
var heightNew = contData.height;
var koef = heightNew / heightOld;
var widthNew = canvData.width * koef;
canvData.height = heightNew;
canvData.width = widthNew;
canvData.top = 0;
if (canvData.width >= contData.width) {
canvData.left = 0;
}
else {
canvData.left = (contData.width - canvData.width) / 2;
}
$image.cropper('setCanvasData', canvData);
//and now set cropper "back" to full crop
data.left = 0;
data.top = 0;
data.width = canvData.width;
data.height = canvData.height;
$image.cropper('setCropBoxData',data);
}
This is my extended code provided by AlexanderZ to avoid cuttong wider images than container :)
var contData = $image.cropper('getContainerData');
$image.cropper('setCropBoxData',{
width: 2, height: 2, top: (contData.height/ 2) - 1, left: (contData.width / 2) - 1
});
$image.cropper('rotate', 90);
var canvData = $image.cropper('getCanvasData');
var newWidth = canvData.width * (contData.height / canvData.height);
if (newWidth >= contData.width) {
var newHeight = canvData.height * (contData.width / canvData.width);
var newCanvData = {
height: newHeight,
width: contData.width,
top: (contData.height - newHeight) / 2,
left: 0
};
} else {
var newCanvData = {
height: contData.height,
width: newWidth,
top: 0,
left: (contData.width - newWidth) / 2
};
}
$image.cropper('setCanvasData', newCanvData);
$image.cropper('setCropBoxData', newCanvData);
Not a direct answer to the question ... but i'm betting many people that use this plugin will find this helpfull..
Made this after picking up #AlexanderZ code to rotate the image.
So ... If you guys want to ROTATE or FLIP a image that has already a crop box defined and if you want that cropbox to rotate or flip with the image ... use these functions:
function flipImage(r, data) {
var old_cbox = $image.cropper('getCropBoxData');
var new_cbox = $image.cropper('getCropBoxData');
var canv = $image.cropper('getCanvasData');
if (data.method == "scaleX") {
if (old_cbox.left == canv.left) {
new_cbox.left = canv.left + canv.width - old_cbox.width;
} else {
new_cbox.left = 2 * canv.left + canv.width - old_cbox.left - old_cbox.width;
}
} else {
new_cbox.top = canv.height - old_cbox.top - old_cbox.height;
}
$image.cropper('setCropBoxData', new_cbox);
/* BUG: When rotated to a perpendicular position of the original position , the user perceived axis are now inverted.
Try it yourself: GO to the demo page, rotate 90 degrees then try to flip X axis, you'll notice the image flippped vertically ... but still ... it fliped in relation to its original axis*/
if ( r == 90 || r == 270 || r == -90 || r == -270 ) {
if ( data.method == "scaleX") {
$image.cropper("scaleY", data.option);
} else {
$image.cropper("scaleX", data.option);
}
} else {
$image.cropper(data.method, data.option);
}
$image.cropper(data.method, data.option);
}
function rotateImage(rotate) {
/* var img = $image.cropper('getImageData'); */
var old_cbox = $image.cropper('getCropBoxData');
var new_cbox = $image.cropper('getCropBoxData');
var old_canv = $image.cropper('getCanvasData');
var old_cont = $image.cropper('getContainerData');
$image.cropper('rotate', rotate);
var new_canv = $image.cropper('getCanvasData');
//calculate new height and width based on the container dimensions
var heightOld = new_canv.height;
var widthOld = new_canv.width;
var heightNew = old_cont.height;
var racio = heightNew / heightOld;
var widthNew = new_canv.width * racio;
new_canv.height = Math.round(heightNew);
new_canv.width = Math.round(widthNew);
new_canv.top = 0;
if (new_canv.width >= old_cont.width) {
new_canv.left = 0;
} else {
new_canv.left = Math.round((old_cont.width - new_canv.width) / 2);
}
$image.cropper('setCanvasData', new_canv);
if (rotate == 90) {
new_cbox.height = racio * old_cbox.width;
new_cbox.width = racio * old_cbox.height;
new_cbox.top = new_canv.top + racio * (old_cbox.left - old_canv.left);
new_cbox.left = new_canv.left + racio * (old_canv.height - old_cbox.height - old_cbox.top);
}
new_cbox.width = Math.round(new_cbox.width);
new_cbox.height = Math.round(new_cbox.height);
new_cbox.top = Math.round(new_cbox.top);
new_cbox.left = Math.round(new_cbox.left);
$image.cropper('setCropBoxData', new_cbox);
}
var photoToEdit = $('.photo_container img');
$( photoToEdit ).cropper({
autoCrop : true,
crop: function(e) {}
});
$("#rotate_left_btn").click( function () {
$( photoToEdit ).cropper('rotate', -90);
var containerHeightFactor = $(".photo_container").height() / $( photoToEdit).cropper('getCanvasData').height;
if ( containerHeightFactor < 1 ) { // if canvas height is greater than the photo container height, then scale (on both x and y
// axes to maintain aspect ratio) to make canvas height fit container height
$( photoToEdit).cropper('scale', containerHeightFactor, containerHeightFactor);
} else if ( $( photoToEdit).cropper('getData').scaleX != 1 || $( photoToEdit).cropper('getData').scaleY != 1 ) { // if canvas height
// is NOT greater than container height but image is already scaled, then revert the scaling cuz the current rotation will bring
// the image back to its original orientation (landscape/portrait)
$( photoToEdit).cropper('scale', 1, 1);
}
}
I Fixed this issue hope fully. i have added or changed the option to 0 (viewMode: 0,). Now its working well.
cropper = new Cropper(image, {
dragMode: 'none',
viewMode: 0,
width: 400,
height: 500,
zoomable: true,
rotatable: true,
crop: function(e) {
}
});
document.getElementById('rotateImg').addEventListener('click', function () {
cropper.rotate(90);
});

jQuery each loop returns data twice

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/

Categories

Resources