var Y = 0.2; // Motion step
var X = 0.6;
(function go(){
$('#m').animate({
left: '+='+(X) ,
top: '+='+(Y)
}, 30, 'linear', go);
}());
Move an element diagonally but not under 45° (Y=1, X=1), rather by steps of a floated number.
Mozilla plays well, but all other browser won't move an element by a decimal px value.
What approach would you use?
Instead of trying to do it through recursion, why not set the left and top values as integers and do it in one animate() call. This way you don't have to deal with floating point numbers and it should still animate diagonally.
var Y = 20; // cannot be >1
var X = 60; // cannot be >1
function go(){
$('#m').animate({
left: '+='+(X) ,
top: '+='+(Y)
}, 300, 'linear');
}
go();
A pixel is, by definition, the smallest element that can be displayed (or not displayed) at the current screen resolution. You can use % and I believe it will work cross browser eg width: 45.5%, but pixels no. In addition you may not even notice the movement that small.
The issue is that when you're setting left and top they're getting rounded to pixels. You're seeing 45-degree motion because both X and Y get rounded to 1.
Why don't you increment two JS variables (which can store floating-point numbers) and then set left and top equal to them at the appropriate time (so the rounding will occur on the sum and not on the increment)?
var Y = 0.2; // cannot be >1
var X = 0.6; // cannot be >1
var cur_left = $('#m').left;
var cur_top = $('#m').top;
function go(){
cur_left += X;
cur_top += Y;
$('#m').animate({
left: '='+(cur_left),
top: '='+(cur_top)
}, 30, 'linear', go);
}
go();
Related
There is endlessly moving sprite "green block" from top to bottom and it works. Is it possible to show sprite moving like "around" the stage show at the top as much as hide in bottom. I don't know exactly how this effect can be called, but I mean when green block is starting to move down the scene border, then start showing it again at the top. How can it be done and can you, please, show how to do this?
const WIDTH = 500;
const HEIGHT = 500;
const app = new PIXI.Application({
width: WIDTH,
height: HEIGHT,
backgroundColor: 0x000000
});
document.body.appendChild(app.view);
const sprite = PIXI.Sprite.from('https://i.ibb.co/b3Sjn6M/greeenblock.png');
sprite.width = 100;
sprite.height = 100;
// Center
sprite.anchor.set(0.5);
sprite.x = app.screen.width / 2;
sprite.y = app.screen.height / 2;
app.stage.addChild(sprite);
// Listen for animate update
app.ticker.add((delta) => {
// Move from topto bottom
sprite.position.y += delta * 2;
if (sprite.position.y > HEIGHT + sprite.height / 2) {
sprite.position.y = -sprite.height / 2;
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.3.3/pixi.min.js"></script>
Solution (with flickering) provided by #Blindman67:
const WIDTH = 500;
const HEIGHT = 500;
const app = new PIXI.Application({
width: WIDTH,
height: HEIGHT,
backgroundColor: 0x000000
});
document.body.appendChild(app.view);
const sprite = PIXI.Sprite.from('https://i.ibb.co/b3Sjn6M/greeenblock.png');
const spriteReverse = PIXI.Sprite.from('https://i.ibb.co/b3Sjn6M/greeenblock.png');
sprite.width = 100;
sprite.height = 100;
spriteReverse.width = 100;
spriteReverse.height = 100;
// Center
sprite.anchor.set(0.5);
sprite.x = app.screen.width / 2;
sprite.y = app.screen.height / 2;
spriteReverse.anchor.set(0.5);
spriteReverse.x = app.screen.width / 2;
spriteReverse.y = app.screen.height / 2;
app.stage.addChild(sprite);
app.stage.addChild(spriteReverse);
let y = 0;
// Euqlidian modulo
const modAbs = (value, modulo) => (value % modulo + modulo) % modulo;
// Listen for animate update
app.ticker.add((delta) => {
// Move from topto bottom
y += delta * 2;
if (y > HEIGHT + sprite.height / 2) {
y = -sprite.height / 2;
}
// use modulo to warp
y = modAbs(y, HEIGHT);
// check if sprite overlaps the screen edge
spriteReverse.visible = false;
if (y + sprite.height > HEIGHT) { // is crossing then
spriteReverse.visible = true;
spriteReverse.position.y = (y - HEIGHT) // ... draw a copy at opposite edge.
}
sprite.position.y = y
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.3.3/pixi.min.js"></script>
If I understand you: you have one box which you wish to move in an infinite loop from the top to the bottom. Once it hits the bottom it should start showing at the top.
The easiest way I can think of would be to have two identical boxes.
Both starts at the top and only one moves down. Once it hits the bottom the other box can start moving down.
When the first box is completely off-screen you reset it's position.
And repeat.
% Remainder operator
This can be done using the remainder operator %
For example if the screen is 1000 pixels wide and you have a coordinate of 1500, that is the object has warped around the screen 1.5 times, using the remainder operator 1500 % 1000 = 500.
If only moving in a positive direction then this is all that is needed (apart from popping)
x = x % screenWidth;
// and/or for y
y = y % screenHeight;
Negative space
However there is a problem if the object moves in the other direction as the remainder operation keeps the sign of the number -1500 % 1000 === -500, and even worse if you use Math.abs on the result you still get the wrong value Math.abs(-1200 % 1000) === 200 which should be 800
You can fix this using a slightly more complex function. You can add it to the Math object or use a stand alone function as follows.
const modAbs = (value, modulo) => (value % modulo + modulo) % modulo;
With the above function negative values are correctly moved into positive space.
So if you have a coordinate x, y to make it warp the screen with
x = modAbs(x, screenWidth);
y = modAbs(y, screenHeight);
That seams easy, but unfortunately there are still some problems to overcome.
Popping
Using the above function to warp across the screen does not consider the size of the sprite, and because you are rendering only one copy when the sprite is move across the playfield edge it will not appear at the other side until the coordinate crossed the edge.
This causes the sprite to pop in and or out depending on the direction of movement and the position of the sprites origin.
There are two solutions.
Extend the playfield
If you make the playfield larger than the view (Viewable area) by 2 times the size of the sprite and warp using the larger playfield then the sprite will not warp until it has completely disappeared from view. This prevents the ugly popping in and out when warping and is most suited to NPC type sprites. For player (focused) sprites this is not a good options as the sprite will not be completely visible as it crosses the screen edges.
Render extra copies.
To keep the sprite fully visible at all times you need to render it more than once when it is crossing the screen. Example pseudo code
// use modulo to warp
x = modAbs(x, screenWidth);
// check if sprite overlaps the screen edge
if (x + spriteWidth > screenWidth) { // is crossing then
drawSprite(x - screenWidth, // ... draw a copy at opposite edge.
If you are only warping between top and bottom (or left and right) this is all that is needed.
If you are warping in all directions you will need to render the sprite up to 4 times. Twice when crossing top bottom or left right. 4 times if crossing in a corner.
As your question only indicates up and down warps I assume you don't need the extra code.
I'm trying to make big circle and move divs along the circle's circumference.
Each div must change the content inside the big circle.
The number of div(s) must be dependent on how many are fetched from database (from table category).
I tried to do this and modified the code by putting .eq() but the problem with .eq is that next circle will appear after that circle, all put in the same place. I want them all to appear at the same time like this without repeating functions
Updated your fiddle:
http://jsfiddle.net/wyW2D/1/
Used:
var t = -1.6;
var t2 = -1.6;
var x = 0;
var t = [-1.6, -1.6, -1.6], // starting angle in radians for each circle
delta = [0.05, 0.03, 0.02], // change in radians for each circle
// e.g the first moves fastest, the last
// slowest. if this gets too big, the
// movement won't look circular, since the
// animation is really a bunch of straight lines
finish = [1.4, 1.0, 0.6]; // the stopping point in radians for each
// circle. if the circle size changes, this
// may need to change
function moveit(i) {
t[i] += delta[i]; // move the angle forward by delta
var r = 300; // radius (the .inner div is 600 x 600)
var xcenter = -30; // center X position: this reproduces the .inner horizontal
// center but from the body element
var ycenter = 420; // center Y position: same here but vertical
// Basic trig, these use sin/cos to find the vert and horiz offset for a given
// angle from the center (t[i]) and a given radius (r)
var newLeft = Math.floor(xcenter + (r * Math.cos(t[i])));
var newTop = Math.floor(ycenter + (r * Math.sin(t[i])));
// Now animate to the new top and left, over 1ms, and when complete call
// the move function again if t[i] hasn't reached the finish.
$('div.circle'+(i+1)).animate({
top: newTop,
left: newLeft,
},1, function() {
if (t[i] < finish[i]) moveit(i);
});
// You can slow down the animation by increasing the 1, but that will eventually
// make it choppy. This plays opposite the delta.
}
// Start the ball rolling
$("document").ready(function(e) {
moveit(0);
moveit(1);
moveit(2);
});
This was a quick change to reduce the code to one function that used arrays (t, delta, finish) to keep track of the three circles. It could be improved to accept arbitrary circles, of any size, at any starting / ending angle.
Also, this kind of animation is much easier with CSS. It is simple to specify and has much better performance.
I've seen many answers about using closures in JS but nothing that I could adapt to my situation:
I have many words sprawled randomly across the browser window at various sizes and positions.
This function would shrink them all down to the same size, then position them side-by-side, one after the other, left to right (re-ordering the words into a sentence).
function alignWords() {
// starting X position
var x_align = 100;
// loop thru each word, shrink its size (ie. font-size) and position them end-to-end on X axis (with 5px spacing)
$('.other-word').each(function(index, item){
$(item).toggleClass("other-word-animate");
console.log("t- x1 = "+ x_align) // does not increment. Outputs: t- x1 = 100, t- x1 = 100, t- x1 = 100, etc...
$(item).animate({
'font-size': $('.main-word').css("font-size").substr(0, $('.main-word').css("font-size").length-2),
top: $('.main-word').css("top"),
left: x_align // always remains 100, which is wrong
}, function() {
x_align += $(this).width() + 5;
console.log("t- x = "+ x_align); // increments correctly. Outputs: t- x = 154, t- x = 311, t- x = 316, etc...
});
});
}
My incrementing of x_align in the animate() callback is not being reflected in the subsequent loop at left: x_align.
Help much appreciated,
All the callbacks are run long after the animation are started (they're all started at the same time). Your goal isn't 100% clear but you probably want to chain the animation, not run them in parallel like this for example :
var x_align = 100,
otherWords = $('.other-word'),
fontSize = $('.main-word').css("font-size").slice(0, -2),
top = $('.main-word').css("top"),
i = 0, n = otherWords.length;
(function doOne(){
if (i++>=n) return;
otherWords.eq(i).toggleClass("other-word-animate")
.animate({
fontSize: fontSize,
top: top,
left: x_align
}, function(){
x_align += $(this).width() + 5;
doOne();
});
})();
I have a canvas that is 1000x600px. I want to spawn sprites outside the canvas (but evenly distributed).
What is the best way to retrieve random values between (-500, -500) and (1500, 1100) but not between (0, 0) and (1000, 600)? I understand a while loop could be used to generate numbers until they are in range but that seems superfluous. Thanks.
If you want to generate a number between -500 and 1500, excluding 0 to 1000, you can just generate a number between 0 and 1000 ( 0 - -500 + 1500 - 1000).
If the number is less than 500, you subtract 500; if the number is greater or equal to 500, add 500.
Or, more generically:
function randomInt(outerMin, outerMax, innerMin, innerMax)
{
var usableRange = innerMin - outerMin + outerMax - innerMax,
threshold = innerMin - outerMin,
num = Math.floor(Math.random() * (usableRange + 1));
if (num < threshold) {
return num - threshold;
} else {
return num - threshold + innerMax;
}
}
randomInt(-500, 1500, 0, 1000);
For two-dimensional points you have to get more creative. First, you generate two points that ARE inside the forbidden area and then spread those values to the good areas:
function randomVector(outer, inner)
{
var innerWidth = inner.right - inner.left,
innerHeight = inner.bottom - inner.top,
x = Math.floor(Math.random() * (innerWidth + 1)),
y = Math.floor(Math.random() * (innerHeight + 1)),
midx = Math.floor(innerWidth / 2),
midy = Math.floor(innerHeight / 2);
if (x < midx) { // left side of forbidden area, spread left
x = x / midx * (inner.left - outer.left) - inner.left;
} else { // right side of forbidden area, spread right
x = (x - midx) / midx * (outer.right - inner.right) + inner.right;
}
if (y < midy) { // top side of forbidden area, spread top
y = y / midy * (inner.top - outer.top) - inner.top;
} else { // bottom side of forbidden area, spread bottom
y = (y - midy) / midy * (outer.bottom - inner.bottom) + inner.bottom;
}
// at this point I'm not sure how to round them
// but it probably should have happened one step above :)
return {
x: Math.floor(x),
y: Math.floor(y)
}
}
randomVector({
left: -500,
top: -500,
right: 1500,
bottom: 1100
}, {
left: 0,
top: 0,
right: 1000,
bottom: 600
});
Important
This works because the areas outside of your "forbidden" area are equal in their respective dimension, i.e. padding-top == padding-bottom && padding-left == padding-right.
If this will be different, the distribution is no longer uniform.
Generate a random number between 0 and 1000, if its over 500 add 500 (or 600 respectivly) if not negate it.
Instead of having a set of forbidden rectangles, you could calculate a set of allowed rectangles. To get a random position inside any allowed rectangle, you first choose a random rectangle and then a random position inside that chosen rectangle.
When the retangles don't have an equal size, you need to weight them by area, otherwise smaller rectangles will have a higher density than larger ones (a 200x100 rectangle needs to be 100 times as likely as a 10x20 rectangle).
Just generate those numbers between (0,0) and (1,1) and then use some linear function to do the mapping.
Otherwise, divide the area where you want the random coordinates to fall in rectangles. Let's say you obtain N such rectangles. Each of those rectangles may be populated through mapping the output of a random generator betweeen (0,0) and (1,1) to that rectangle (this is a linear mapping).
Using Javascript I'm crudely simulating Brownian motion of particles, but for some reason I don't understand my particles are drifting up and to the left.
The algorithm is pretty straight forward. Each particle is a div and I simply add or subtract a random number from each div's top and left position each round.
I read up on Math.random() a little, and I've tried to use a function that returns a random number from min to max inclussive:
// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
function ran(min, max)
{
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Here is the function for the movement of the particles:
var x, y, $elie, pos, nowX, nowY, i, $that;
function moveIt()
{
$("div.spec").each(function(i, v) {
x = ran(-5, 5);
y = ran(-5, 5);
$elie = $(v);
pos = $elie.position();
nowX = pos.left;
nowY = pos.top;
// The min and abs are to keep the particles within a box
// The drift occurs even if I remove min and abs
$elie.css("left", Math.min(Math.abs(nowX + x), 515));
$elie.css("top", Math.min(Math.abs(nowY + y), 515));
});
}
And here is how the particles are initially set up an the setInterval started.
$(function() {
$("body").append("<div/>").attr("id","box");
$elie = $("<div/>").attr("class","spec");
// Note that math random is inclussive for 0 and exclussive for Max
for (i = 0; i < 25; ++i)
{
$that = $elie.clone();
$that.css("top", ran(0, 495));
$that.css("left", ran(0, 495));
$("#box").append($that);
}
timer = setInterval(moveIt, 60);
$("input").toggle(function() {
clearInterval(timer);
this.value = " Start ";
}, function() {
timer = setInterval(moveIt, 60);
this.value = " Stop ";
});
});
My problem is that using the min and max from above ( -5, 5 ), all the particles drift up and to the left very fast.
jsFiddle example of drift (-5, 5)
Example of drift even with the removal of .min() and .abs().
To counteract this, I have to use a min and max of -1, 5.
jsFiddle example of no drift (-1, 5)
Here is the CSS for the div all the particles are contained in:
#box {
width:500px;
height:500px;
border:2px #000 solid;
position: relative; }
Here is the default CSS for each particle:
div.spec {
width:5px;
height:5px;
background-color:#00DDAA;
position:absolute; }
What is going on? Why does a min and max of -5 and 5 cause an upward and leftward drift?
A test of the random function ran() doesn't seem to show such a persistent negative drift.
jsFiddle example of testing ran()
The ran() function was taken from the MDC Math.random() page.
Your mistake is to use
pos = $elie.position();
rather than
pos = $elie.offset();
This wouldn't have made a difference had they been added to parent div, but your elements aren't properly added to a parent div, they're appended directly to the document body. So your other mistake is this:
$("body").append("<div/>").attr("id","box");
If you want the div to have id of 'box', the line should read:
$box = $("<div/>").attr("id","box");
$("body").append($box)
Otherwise you're actually giving "body" the id of "box"
EDIT:
The most efficient way to append the div would be the following (as noted by this post):
$(document.createElement('div')).appendTo('body').attr('id', 'box')
Instead of using .position(), try .offset() instead. Looks like it works.
Position.
Offset.
It works this way because you're setting the absolute 'left' and 'top' values in CSS. Instead, you can use this Example:
$elie.css("margin-left", nowX + x);
$elie.css("margin-top", nowY + y);