I have a nice working slider script (no libraries used). I need a math concept that would allow the slider to perform a callback on a specified pixel interval. So for example, my slider is 300px wide, and I specify the interval=10, then when the user slides the handle, the callback should happen at pixel positions 10,20,30,40 and so on. If the interval=3 then the callback should happen at pixel positions 3,6,9,12...etc.
It's the math formula that I am looking for specifically.
Any help would be appreciated.
Pat
So say you have the variables:
var width = 300; // px
var interval = 10; // px
var pos; // current position
And then you should do:
if ( pos % interval === 0 )
// do stuff
This is called modulus operator which returns the remainder of dividing left_var by right_var. If the remainder is 0 your check will return true, and so you do the callback (exactly at pixel 10, 20, 30, 40, etc.).
But please note that it's very naive to think that you will be able to handle every pixel change... What I'd rather do is to examine if the slider has entered to a given segment.
var segment = 1;
Then after the state of the slider changes you check for position like this:
if ( Math.floor( pos / interval ) === segment ) {
// do stuff because the slider
// has entered a new segment
// ...
segment++;
}
If the slider moves backwards and you want to fire the events accordingly just decrement the segment instead of incrementing.
Related
I am trying to control the speed of an animation so it matches the actual speed that a vehicle took.
let counter = 0;
const animate = () => {
counter += 1;
// Update point geometry to a new position based on the animation
// And the distance the point has travelled along the route.
const updatedPoint = turf.along(lineString, (counter / 100) * lineDistance, 'kilometers');
moveVehicleToPoint(updatedPoint);
// updatedPoint represents a point between/along between origin and destination
if (updatedPoint.geometry.coordinates[0] !== destination) {
requestAnimationFrame(animate);
}
}
animate();
I'm nearly there but maths isn't my strongest asset.
lineDistance is roughly 0.01-0.02km on average.
lineString contains the start and end coordinate.
turf.along take the lineString, a set distance, and returns the distance from the start to your provided distance along the line in kilometers.
Currently, I've included an arbitrary value of 100 to divide by. If the vehicle move took 1 second, this is pretty good. It will move along to the next point by roughly a second.
If it took 2 seconds, it'll be too slow, and finish moving well before the vehicle would have.
How can I include my durationSeconds variable, so that if I say it took 2 seconds, the animate() will perfectly animate along the line over 2 seconds?
Try setting up a variable in the outer scope that relates to the speed you need. Change it's value accordingly to control it.
I have an object in three.js (don't think this is relevant though) which can accelerate when pressing a button. That object should stop on its own due to friction if I stop pressing the button.
What I'm doing is reducing the acceleration of an object (subtracting from it), and if it reaches zero velocity it should stop in place and not start moving in the opposite direction.
I'm trying if (velocity == 0) but that doesn't work because it just passes from 0.0001 to -0.001.
Is there a way to know whether a value has passed zero in a continuous function, or a better way to program this problem?
How about if (velocity <= 0) {}?
You'll have to check the before and after values during the subtraction operation. A good way to do this is to compare their Math.sign values.
const beforeSign = Math.sign(velocity);
// Code to update velocity
const afterSign = Math.sign(velocity);
if( beforeSign !== afterSign ) {
// Do code for stopping
}
I noticed how other answers did not specifically address your issue:
If it reaches (I assume "crosses") zero velocity it should stop in place and not start moving in the opposite direction.
Since they are two seperate events, you will need to set a variable to indicate the state that the car is currently in.
velocityFunc(velocity, state = 'forward'){
let speed = Math.sign(velocity)
if (speed !== 1 || speed === 0 && state === 'forward'){
return false
}
if (speed !== -1 || speed === 0 && state === 'reverse'){
return false
}
return true
}
Then use the function like this:
if(!velocityFunc(velocity)){
//the car was moving forward (with decelerating velocity) but it should now stop, it has crossed the foward threshold
}
or
if(!velocityFunc(velocity, 'reverse')){
//the car was reversing (with decelerating velocity) but it should now stop, it has crossed the reverse threshold
}
This way, if:
The car is decelerating (while moving foward) and it reaches 0, and moves on to a - (negative speed | starts moving backwards), it will be
forced to stop.
If the car is decelerating (while in reverse), and it reaches 0, and starts moving foward (accelerating with positive velocity), it
will also be forced to stop.
NB: The above code is untested but should work.
In the game that I have, when the player walks up to the number and presses the "m" key one frame of the sprite is skipped.
To clarify, the following is my code that I use to keep track of when the frames should change by storing the position of the frames in a variable:
function animateStar() {
var pos = 0;
$(document).keyup(function(e) {
if (e.keyCode == 77) {
pos -= 32;
$('#n1').css('background-position', pos+'px -0px');
}
});
}
So as you can see, everytime the key code 77 (representing "m") is pressed, the frames will change, since each of my frames has a height and width of 32px.
However, the problem that I am having is that when my player walks away from the object and comes back to it, instead of continuing from where he left off, the frames sort of skip.
Here is my code: http://jsfiddle.net/j40s8gjt/
Since my sprite sheet is composed of numbers, you should be able to see what's up with the code.
Try setting the frame at a number between 3-5, walking away from the object then coming back to it and pressing m again, you should see that it sort of re loops everything.
A possible solution of this might be to store the current position of the object when the player leaves it, but I am not sure how to go about doing so.
I hope I've been clear enough, thank you!
Since you're defaulting to 0 each time you call animateStar() it starts over.
Grabbing the current position is what you need to do instead of defaulting to 0 each time.
in animateStar(), Change this line:
var pos = 0;
to this:
var pos = $('#n1').css('background-position').split('px ')[0] || 0;
That way it gets the current position when it starts, instead of starting from 0 again and resetting the frame.
Working example: http://jsfiddle.net/j40s8gjt/2/
i am trying to use the following code to increment number in a textbox
// Animate the element's value from 0 to 1100000:
$({someValue: 0}).animate({someValue: 1100000}, {
duration: 1000,
step: function() { // called on every step
// Update the element's text with value:
$('#counterx').text(Math.floor(this.someValue+1));
}
});
it is working with small numbers like from 0 to 100
but when it comes to large number like in the mentioned code,
it is not giving the target number,
it is animating to numbers like 1099933 or 1099610 or .....
and every time it changes.
so how can i make it to animate to the number i specify?
I have the same issue. The reasoning is because animate function uses a mathematical formula that is time based. You don't really notice this when animating something css based because close enough in pixels is good enough. It will get close to the final value but may not always be exactly the end value. Solution is to use the complete event to set that last value.
Here is what you need to do:
function animateNumber(ele,no,stepTime){
$({someValue: 0}).animate({someValue: no}, {
duration: stepTime,
step: function() { // called on every step. Update the element's text with value:
ele.text(Math.floor(this.someValue+1));
},
complete : function(){
ele.text(no);
}
});
}
animateNumber($('#counterx'),100,10000);
animateNumber($('#countery'),100,1000)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
counterx(slow): <span id=counterx>--</span>
<br/>
countery(fast): <span id=countery>--</span>
1) Javascript is a single threaded application. Timeouts and animations ONLY push the event to the end of the stack based on an ideal stacking order. A long running section of script can cause the actual firing time of that event well past the accuracy you are looking for.
2) Animation approximates how much to increment, and on larger numbers that resolution is very inaccurate.
3) jQuery only has one animation buffer. You might run into some serious rendering issues if you invoke more than one "counter" using animation. Make sure to stop the previous animation before making any adjustments that effect it.
4) Even with a timeout of 0, you can expect the real world delay of ~15. Even if that is the only "thread" you have running.
Solution:
take a snapshot of the DTG
set your interval to something within the human experience, say ~200
on each interval, check how much time has passed from the original DTG
set your text field to that delta number.
stop the interval with the original DTG + "your target number" > the new DTG
Animate is not designed to increment a counter as text (though it may work by accident, which could change with any new version of jQuery), it's designed to animate one or more CSS properties. You should be using setInterval instead.
http://jsfiddle.net/jbabey/mKa5r/
var num = 0;
var interval = setInterval(function () {
document.getElementById('result').innerHTML = num;
num++;
if (num === 100) {
clearInterval(interval);
}
}, 100);
Here's a solution that doesn't use .animate().
DEMO: http://jsfiddle.net/czbAy/4/
It's just a linear modification; you don't get the easing options if that's what you were after.
var counterx = $('#counterx'), // cache the DOM selection! :)
i = 0,
n = 1100000,
dur = 1000, // 1 second
int = 13,
s = Math.round(n / (dur / int));
var id = setInterval(function() {
counterx.text(i += s);
if (i >= n) {
clearInterval(id);
counterx.text(n);
}
}, int);
Here is a jquery plugin to animate numbers reliably, ut uses the complete callback to set the correct final number once the animation has finished:
https://github.com/kajic/jquery-animateNumber
Somehow this doesn't work...
var paper = Raphael("test", 500, 500);
var testpath = paper.path('M100 100L190 190');
var a = paper.rect(0,0,10,10);
a.attr('fill', 'silver');
a.mousedown( function() {
testpath.animate({x: 400}, 1000);
});
I can move rects this way but not paths, why is that, and how do I move a path object then?!
With the latest version of Raphael, you can do this:
var _transformedPath = Raphael.transformPath('M100 100L190 190', 'T400,0');
testpath.animate({path: _transformedPath}, 1000);
This saves you from the trouble of having to clone a temp object.
It seems a path object doesn't get a x,y value - so your animation probably still runs, but does nothing. Try instead animating the path function:
testpath.animate({path:'M400 100L490 190'},1000);
It makes it a bit trickier to write the animation, but you have the benefit of getting rotation and scaling for free!
BTW: I'm sure this is just an example, but in your above code testpath gets put in the global scope because you don't initialize as var testpath
Solved, with thanx to Rudu!
You need to create a new path to animate to. You can do this with clone() and then apply the transformations to that clone. Seems very complex for a simple move like this, but it works...
var paper = Raphael("test", 500, 500);
var testpath = paper.path('M100 100L190 190');
var a = paper.rect(0,0,10,10);
a.attr('fill', 'silver');
a.mousedown( function() {
var temp = testpath.clone();
temp.translate(400,0);
testpath.animate({path: temp.attr('path')}, 1000);
temp.remove();
});
TimDog answer was best solution.
In addition, just remember, transform string in this case means, that it will add 400 points to every path point/line X coordinate, and 0 points to every Y coordinate.
That means, M100 100L190 190 will turn into M500 100L590 190.
So, if you need to move a path element to another position, the difference between current position and new position coordinates should be calculated. You can use first element to do that:
var newCoordinates = [300, 200],
curPos = testpath.path[0],
newPosX = newCoordinates[0] - curPos[1],
newPosY = newCoordinates[1] - curPos[2];
var _transformedPath = Raphael.transformPath(testpath.path, "T"+newPosX+","+newPosY);
testpath.animate({path: _transformedPath});
Hope this will help someone.
Here's some code that generalises the best of the above answers and gives Raphael paths a simple .attr({pathXY: [newXPos, newYPos]}) attribute similar to .attr({x: newXPosition}) and .animate({x: newXPosition}) for shapes.
This lets you move your path to a fixed, absolute position or move it by a relative amount in a standard way without hardcoding path strings or custom calculations.
Edit: Code below works in IE7 and IE8. An earlier version of this failed in IE8 / VML mode due to a Raphael bug that returns arrays to .attr('path') in SVG mode but strings to .attr('path') in VML mode.
Code
Add this code (Raphael customAttribute, and helper function) after defining paper, use as below.
paper.customAttributes.pathXY = function( x,y ) {
// use with .attr({pathXY: [x,y]});
// call element.pathXY() before animating with .animate({pathXY: [x,y]})
var pathArray = Raphael.parsePathString(this.attr('path'));
var transformArray = ['T', x - this.pathXY('x'), y - this.pathXY('y') ];
return {
path: Raphael.transformPath( pathArray, transformArray)
};
};
Raphael.st.pathXY = function(xy) {
// pass 'x' or 'y' to get average x or y pos of set
// pass nothing to initiate set for pathXY animation
// recursive to work for sets, sets of sets, etc
var sum = 0, counter = 0;
this.forEach( function( element ){
var position = ( element.pathXY(xy) );
if(position){
sum += parseFloat(position);
counter++;
}
});
return (sum / counter);
};
Raphael.el.pathXY = function(xy) {
// pass 'x' or 'y' to get x or y pos of element
// pass nothing to initiate element for pathXY animation
// can use in same way for elements and sets alike
if(xy == 'x' || xy == 'y'){ // to get x or y of path
xy = (xy == 'x') ? 1 : 2;
var pathPos = Raphael.parsePathString(this.attr('path'))[0][xy];
return pathPos;
} else { // to initialise a path's pathXY, for animation
this.attr({pathXY: [this.pathXY('x'),this.pathXY('y')]});
}
};
Usage
For absolute translation (move to fixed X,Y position) - Live JSBIN demo
Works with any path or set of paths including sets of sets (demo). Note that since Raphael sets are arrays not groups, it moves each item in the set to the defined position - not the centre of the set.
// moves to x=200, y=300 regardless of previous transformations
path.attr({pathXY: [200,300]});
// moves x only, keeps current y position
path.attr({pathXY: [200,path.pathXY('y')]});
// moves y only, keeps current x position
path.attr({pathXY: [path.pathXY('x'),300]});
Raphael needs to handle both x and y co-ordinates together in the same customAttribute so they can animate together and so they stay in sync with each other.
For relative translation (move by +/- X,Y) - Live JSBIN demo
// moves down, right by 10
path.attr({pathXY: [ path.pathXY('x')+10, path.pathXY('y')+10 ]},500);
This also works with sets, but again don't forget that Raphael's sets aren't like groups - each object moves to one position relative to the average position of the set, so results may not be what are expected (example demo).
For animation (move a path to relative or absolute positions)
Before animating the first time, you need to set the pathXY values, due to a bug/missing feature up to Raphael 2.1.0 where all customAttributes need to be given a numeric value before they are animated (otherwise, they will turn every number into NaN and do nothing, failing silently with no errors, or not animating and jumping straight to the final position).
Before using .animate({pathXY: [newX,newY]});, run this helper function:
somePath.pathXY();
Yet another way is to use "transform" attribute:
testpath.animate({transform: "t400,0"}, 1000);
to move the path to the right by 400px, relative to the original position.
This should work for all shapes, including paths and rectangles.
Note that:
"transform" attribute is independent of x, y, cx, cy, etc. So these attributes are not updated by the animation above.
The value of "transform" attribute is always based on the original position, not the current position. If you apply the animation below after the animation above, it will move it 800px to the left relatively, instead of moving it back to its original position.
testpath.animate({transform: "t-400,0"}, 1000);