I'm trying to center the viewport on an object and zoom in/out when i click a button. I want to have an animation. To do this I'm using setInterval and moving the viewport in increments like so:
const objectCenterCoordenates = {
x: Math.round(rect1.left + rect1.width / 2),
y: Math.round(rect1.top + rect1.height / 2)
};
const centeredCanvasCoordenates = {
x: objectCenterCoordenates.x - canvas.width / 2,
y: objectCenterCoordenates.y - canvas.height / 2
};
let currentPoint = {
x: Math.round(canvas.viewportTransform[4]) * -1,
y: Math.round(canvas.viewportTransform[5]) * -1
};
console.log("Start animation");
let animation = setInterval(() => {
console.log("New frame");
if (canvas.getZoom() !== 2) {
let roundedZoom = Math.round(canvas.getZoom() * 100) / 100;
let zoomStep = roundedZoom > 2 ? -0.01 : 0.01;
let newZoom = roundedZoom + zoomStep;
canvas.zoomToPoint(
new fabric.Point(currentPoint.x, currentPoint.y),
newZoom
);
}
let step = 5;
let vpCenter = {
x: Math.round(canvas.getVpCenter().x),
y: Math.round(canvas.getVpCenter().y)
};
if (
vpCenter.x === objectCenterCoordenates.x &&
vpCenter.y === objectCenterCoordenates.y
) {
console.log("Animation Finish");
clearInterval(animation);
}
let xDif = Math.abs(vpCenter.x - objectCenterCoordenates.x);
let yDif = Math.abs(vpCenter.y - objectCenterCoordenates.y);
let stepX =
Math.round(vpCenter.x) > Math.round(objectCenterCoordenates.x)
? -step
: step;
if (Math.abs(xDif) < step)
stepX =
Math.round(vpCenter.x) > Math.round(objectCenterCoordenates.x)
? -xDif
: xDif;
let stepY =
Math.round(vpCenter.y) > Math.round(objectCenterCoordenates.y)
? -step
: step;
if (Math.abs(yDif) < step)
stepY =
Math.round(vpCenter.y) > Math.round(objectCenterCoordenates.y)
? -yDif
: yDif;
currentPoint = {
x: currentPoint.x + stepX,
y: currentPoint.y + stepY
};
canvas.absolutePan(new fabric.Point(currentPoint.x, currentPoint.y));
}, 4);
But sometimes, I haven't been able to determine why, it gets stuck in a loop moving a few pixels one way then going back the other. And the zoom is well implemented, since it's possible to get to the object before it finished zooming in/out.
Here is a codepen with my code: https://codepen.io/nilsilva/pen/vYpPrgq
I would appreciate any advice on making my algorithm better.
Looks like you have an exact match requirement for your clearInterval ...
if (
vpCenter.x === objectCenterCoordenates.x &&
vpCenter.y === objectCenterCoordenates.y
) {
console.log("Animation Finish");
clearInterval(animation);
}
The case that you describe stuck in a loop moving one way then going back the other is because the exact match it's never done, could be because the step you are taking or it could be a rounding issue
You can use Math.hypot to check if the two points are close enough, read more here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/hypot
The condition will look like:
if (Math.hypot(
vpCenter.x - objectCenterCoordenates.x,
vpCenter.y - objectCenterCoordenates.y
) < step) {
console.log("Animation Finish");
clearInterval(animation);
}
I am building an iOS application with Ionic. The application is a timer where the user will specify a time limit and then countdown that time for a certain activity. I am trying to achieve an interaction where the user will drag a handle around the outside of a circle and each clockwise rotation will increment the time limit by one minute, and going the opposite direction will decrement by one.
I have the circle working, where you can drag the handle and it will adhere to the bounds of the container. Now I am trying to use Moment.js to create the countdown, but am having a tough time getting the timer values to update inside of the touch event.
The $scope.duration variable is what I am using to track the timer value. I have tried using the moment().duration() method to specify a duration object and am initializing it to '00:00:00'. When I try to update that value in the touch gesture event, I am unable to update the timer value. I am assuming I either don't understand how to correctly apply updated scope values in Angular/Ionic, I don't know how to correctly use Moment.js, or quite possibly - both.
Here is my template code:
<ion-view hide-nav-bar="true" view-title="Dashboard">
<ion-content>
<div class="timer">
<div class="timer-slider"></div>
<span class="timer-countdown">
{{duration}}
</span>
</div>
</ion-content>
</ion-view>
And my large controller:
.controller('DashCtrl', function($scope, $ionicGesture) {
var $timer = angular.element(document.getElementsByClassName('timer')[0]);
var $timerSlider = angular.element(document.getElementsByClassName('timer-slider')[0]);
var timerWidth = $timer[0].getBoundingClientRect().width;
var sliderWidth = $timerSlider[0].getBoundingClientRect().width;
var radius = timerWidth / 2;
var deg = 0;
var X = Math.round(radius * Math.sin(deg*Math.PI/180));
var Y = Math.round(radius * -Math.cos(deg*Math.PI/180));
var counter = 0;
$scope.duration = moment().hour(0).minute(0).second(0).format('HH : mm : ss');
// Set timer circle aspect ratio
$timer.css('height', timerWidth + 'px');
$timerSlider.css({
left: X + radius - sliderWidth / 2 + 'px',
top: Y + radius - sliderWidth / 2 + 'px'
});
$ionicGesture.on('drag', function(e) {
e.preventDefault();
var pos = {
x: e.gesture.touches[0].clientX,
y: e.gesture.touches[0].clientY
};
var atan = Math.atan2(pos.x - radius, pos.y - radius);
deg = -atan/(Math.PI/180) + 180; // final (0-360 positive) degrees from mouse position
// for attraction to multiple of 90 degrees
var distance = Math.abs( deg - ( Math.round(deg / 90) * 90 ) );
if ( distance <= 5 || distance >= 355 )
deg = Math.round(deg / 90) * 90;
if(Math.floor(deg) % 6 === 0) {
console.log(Math.floor(deg));
$scope.duration = moment().hour(0).minute(0).second(counter++).format('HH : mm : ss');
}
if (deg === 360)
deg = 0;
X = Math.round(radius * Math.sin(deg * Math.PI / 180));
Y = Math.round(radius * -Math.cos(deg * Math.PI / 180));
$timerSlider.css({
left: X + radius - sliderWidth / 2 + 'px',
top: Y + radius - sliderWidth / 2 + 'px'
});
}, $timerSlider);
})
I hacked up a CodePen demo. It doesn't track the drag event all that well without a mobile format, but you can get the idea of what I am going for.
http://codepen.io/stat30fbliss/pen/xZRrXY
Here's a screenshot of the app in-progress
After updating $scope.duration, run $scope.$apply() and it should start working :)
I currently have a function which detects the direction of the mouse enter. It returns an integer (0-3) for each section as illustrated below.
I'm trying to figure out how to alter this function to produce an output based on this illustration:
Basically, I just want to figure out if the user is entering the image from the left or the right. I've had a few attempts at trial and error, but I'm not very good at this type of math.
I've set up a JSFiddle with a console output as an example.
The function to determine direction is:
function determineDirection($el, pos){
var w = $el.width(),
h = $el.height(),
x = (pos.x - $el.offset().left - (w/2)) * (w > h ? (h/w) : 1),
y = (pos.y - $el.offset().top - (h/2)) * (h > w ? (w/h) : 1);
return Math.round((((Math.atan2(y,x) * (180/Math.PI)) + 180)) / 90 + 3) % 4;
}
Where pos is an object: {x: e.pageX, y: e.pageY}, and $el is the jQuery object containing the target element.
Replace return Math.round((((Math.atan2(y,x) * (180/Math.PI)) + 180)) / 90 + 3) % 4; with return (x > 0 ? 0 : 1);
Another option which I like better (simpler idea):
function determineDirection($el, pos){
var w = $el.width(),
middle = $el.offset().left + w/2;
return (pos.x > middle ? 0 : 1);
}
How can I recreate this type movement with jquery for images: http://www.istockphoto.com/stock-video-12805249-moving-particles-loop-soft-green-hd-1080.php
I'm planning to use it as a web page background. If it is not possible with jquery I'll go with flash as3. But I prefer jquery.
Edit: Raphael is definitely better suited for this, since it supports IE. The problem with jQuery is that the rounded corners are a pain to do in IE due to CSS constraints... in Raphael cross browser circles are no sweat.
jsFiddle with Raphael - all browsers:
(though it might look nicer speeded up in IE)
(function() {
var paper, circs, i, nowX, nowY, timer, props = {}, toggler = 0, elie, dx, dy, rad, cur, opa;
// 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;
}
function moveIt()
{
for(i = 0; i < circs.length; ++i)
{
// Reset when time is at zero
if (! circs[i].time)
{
circs[i].time = ran(30, 100);
circs[i].deg = ran(-179, 180);
circs[i].vel = ran(1, 5);
circs[i].curve = ran(0, 1);
circs[i].fade = ran(0, 1);
circs[i].grow = ran(-2, 2);
}
// Get position
nowX = circs[i].attr("cx");
nowY = circs[i].attr("cy");
// Calc movement
dx = circs[i].vel * Math.cos(circs[i].deg * Math.PI/180);
dy = circs[i].vel * Math.sin(circs[i].deg * Math.PI/180);
// Calc new position
nowX += dx;
nowY += dy;
// Calc wrap around
if (nowX < 0) nowX = 490 + nowX;
else nowX = nowX % 490;
if (nowY < 0) nowY = 490 + nowY;
else nowY = nowY % 490;
// Render moved particle
circs[i].attr({cx: nowX, cy: nowY});
// Calc growth
rad = circs[i].attr("r");
if (circs[i].grow > 0) circs[i].attr("r", Math.min(30, rad + .1));
else circs[i].attr("r", Math.max(10, rad - .1));
// Calc curve
if (circs[i].curve > 0) circs[i].deg = circs[i].deg + 2;
else circs[i].deg = circs[i].deg - 2;
// Calc opacity
opa = circs[i].attr("fill-opacity");
if (circs[i].fade > 0) {
circs[i].attr("fill-opacity", Math.max(.3, opa - .01));
circs[i].attr("stroke-opacity", Math.max(.3, opa - .01)); }
else {
circs[i].attr("fill-opacity", Math.min(1, opa + .01));
circs[i].attr("stroke-opacity", Math.min(1, opa + .01)); }
// Progress timer for particle
circs[i].time = circs[i].time - 1;
// Calc damping
if (circs[i].vel < 1) circs[i].time = 0;
else circs[i].vel = circs[i].vel - .05;
}
timer = setTimeout(moveIt, 60);
}
window.onload = function () {
paper = Raphael("canvas", 500, 500);
circs = paper.set();
for (i = 0; i < 30; ++i)
{
opa = ran(3,10)/10;
circs.push(paper.circle(ran(0,500), ran(0,500), ran(10,30)).attr({"fill-opacity": opa,
"stroke-opacity": opa}));
}
circs.attr({fill: "#00DDAA", stroke: "#00DDAA"});
moveIt();
elie = document.getElementById("toggle");
elie.onclick = function() {
(toggler++ % 2) ? (function(){
moveIt();
elie.value = " Stop ";
}()) : (function(){
clearTimeout(timer);
elie.value = " Start ";
}());
}
};
}());
The first attempt jQuery solution is below:
This jQuery attempt pretty much failes in IE and is slow in FF. Chrome and Safari do well:
jsFiddle example for all browsers (IE is not that good)
(I didn't implement the fade in IE, and IE doesn't have rounded corners... also the JS is slower, so it looks pretty bad overall)
jsFiddle example for Chrome and Safari only (4x more particles)
(function() {
var x, y, $elie, pos, nowX, nowY, i, $that, vel, deg, fade, curve, ko, mo, oo, grow, len;
// 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;
}
function moveIt()
{
$("div.spec").each(function(i, v) {
$elie = $(v);
if (! $elie.data("time"))
{
$elie.data("time", ran(30, 100));
$elie.data("deg", ran(-179, 180));
$elie.data("vel", ran(3, 10));
$elie.data("curve", ran(0, 1));
$elie.data("fade", ran(0, 1));
$elie.data("grow", ran(-2, 2));
}
vel = $elie.data("vel");
deg = $elie.data("deg");
fade = $elie.data("fade");
curve = $elie.data("curve");
grow = $elie.data("grow");
len = $elie.width();
if (grow > 0)
len = Math.min(len + grow, 50);
else
len = Math.max(len + grow, 20);
$elie.css("-moz-border-radius", len/2);
$elie.css("border-radius", len/2);
$elie.css("width", len);
$elie.css("height", len);
pos = $elie.position();
$elie.data("time", $elie.data("time") - 1);
if (curve)
$elie.data("deg", (deg + 5) % 180);
else
$elie.data("deg", (deg - 5) % 180);
ko = $elie.css("-khtml-opacity");
mo = $elie.css("-moz-opacity");
oo = $elie.css("opacity");
if (fade)
{
$elie.css("-khtml-opacity", Math.max(ko - .1, .5));
$elie.css("-moz-opacity", Math.max(mo - .1, .5));
$elie.css("opacity", Math.max(oo - .1, .5));
} else
{
$elie.css("-khtml-opacity", Math.min(ko - -.1, 1));
$elie.css("-moz-opacity", Math.min(mo - -.1, 1));
$elie.css("opacity", Math.min(oo - -.1, 1));
}
if (vel < 3)
$elie.data("time", 0);
else
$elie.data("vel", vel - .2);
nowX = pos.left;
nowY = pos.top;
x = vel * Math.cos(deg * Math.PI/180);
y = vel * Math.sin(deg * Math.PI/180);
nowX = nowX + x;
nowY = nowY + y;
if (nowX < 0)
nowX = 490 + nowX;
else
nowX = nowX % 490;
if (nowY < 0)
nowY = 490 + nowY;
else
nowY = nowY % 490;
$elie.css("left", nowX);
$elie.css("top", nowY);
});
}
$(function() {
$(document.createElement('div')).appendTo('body').attr('id', 'box');
$elie = $("<div/>").attr("class","spec");
// Note that math random is inclussive for 0 and exclussive for Max
for (i = 0; i < 100; ++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 ";
});
});
}());
[Partial answer, just for the physics.]
[I just saw the previous answer, mine is somewhat along the same lines.]
You may try to simulate some sort of Brownian motion, i.e. a movement deriving
from the combination of a random force and a viscous damping. Pseudocode:
initialize:
x = random_position();
v_x = random_velocity(); // v_x = velocity along x
// and same for y
for (each time step) {
x += v_x;
v_x += random_force() - time_step / damping_time * v_x;
// and same for y
}
Keep the damping time long (~ 1 second) and the amplitude of the random force small. Otherwise the movement may be too jerky.
For an easy to implement Gaussian random number generator, look up Box-Muller in Wikipedia.
For the mathematics of it, you give every object a starting position and velocity. The "random walk" is achieved by computing a random angle that is constrained by some amount (experiment). Then change the angle of the velocity vector by this angle. You can also compute a random speed delta and change the magnitude of the vector by that amount. Because you're working with velocity, the movements will be somewhat smooth. A slightly more advanced approach to to work with acceleration directly and compute velocity and position based off that.
For your random steering value, a binomial distribution is preferable to a uniform one. Binomial distributions are concentrated around 0 instead of uniformly spread out. You can just do random() - random() (psuedocode)
Vector math is extensively documented but if you run into a snag, leave a comment.
very late answer from my side, but I thought I might give an approach...
I personally would use an svg vector image.
Create a jquery plugin which accepts opacity, size. and makes them move in a random direction.
Then do a javascript loop in creating a set of those particles (where opacity and size are random, plus the start location is random)
Then make the jquery plugin to initiate a new instance of itself when the particle is unloaded.
(If you look at the little movie you will see that they move in 1 direction and fade out, then another fades in.)
The opacity effect will give the depth perspective.
Not sure if my answer helps, but I would go in that direction.