SOLVED -
I am trying to animate an image to rotate following the angle of a sine wave - the sine wave is created using the jQuery.path plugin.
The problem is with getting a smooth rotation.
I am currently using the jQuery rotate plugin for the rotation, which - as it currently stands - is not creating a smooth rotation.
See http://hellosmithers.com/GNU/STALLMANQUEST.html for JavaScript and jQuery.
The script is relatively heavily commented.
Currently the rotation just repeats and takes a certain amount of time to complete - this means it will not work for all screen sizes as the sine wave takes longer to complete the wider the screen.
function mRot() { // image rotation - it goes from rA[0] (-8 degrees) to rA[1] (8 degrees) then swaps the values
// flip the values in rA - making the rotation oposite each iteration of this funciton
rA[1] = [rA[0], rA[0] = rA[1]][0];
$("#rmat").rotate({
duration: 1700,
angle: rA[0],
animateTo: rA[1],
callback: mRot
}); // I would like to remove this function - instead tying the rotation into the sine wave function somehow
}
The sine wave is created as follows:
SineWave = function() { // create the sine wave - as per https://github.com/weepy/jquery.path
this.css = function(p) {
s = Math.sin(p * 12);
x = winWidth - p * (winWidth + 250);
y = s * 40 + (winHeight - 440);
return {top: y + "px", left: x + "px"};
}
}
Thanks
I figured it out - the angle is derived from p, I've used (p * 12) -
SineWave = function() {
this.css = function(p) {
rA[0] = p * 12; // get the angle
s = Math.sin(p * 12);
x = winWidth - p * (winWidth + 250);
y = s * 40 + (winHeight - 440);
return {top: y + "px", left: x + "px"};
}
}
The function mRot uses rA -
function mRot() {
rA[0] = prA[1]; // rA[0] will always be the previous rA[1]
if (prA[1] < 0) rA[1] = Math.abs(rA[1]); // if the previous rA[1] is a negative number, make the current rA positive.
else rA[1] = -(rA[1]); // otherwise make it negative
prA = rA;
$("#rmat").rotate({
duration: 1500,
angle: rA[0],
animateTo: rA[1],
callback: mRot
});
}
Making sure to declare the array variables -
rA = new Array (-8, 8);
prA = rA[1] = [rA[0], rA[0] = rA[1]][0];
If you want to see the full code - visit http://hellosmithers.com/GNU/STALLMANQUEST.html
Related
I am using svg.js to create an animation of a bicyle rider. Semi-complete version here: https://pedalfuriously.neocities.org/. I'm running in to a bit of a problem with moving and rotating svg elements during animation created with requestAnimationFrame (rather than the svg.js built in animation).
If you take a look at the link, and use the cadence slider to make the rider pedal very fast, and then flip the slider quickly all the way back to zero, you can see that his lower leg "jiggles" in a disconnected way. What's really doing my head in is that the postion of the legs are determined in each frame based on an absolute relation to the rotation of the cranks (rather than taking some delta time value to determine movement over that frame).
I think I've been able to confirm what aspect of my code is causing the problem. Here is a minimal example that doesn't exhibit the exact behaviour, but I think illustrates the kind of thing I think is responsible:
var draw = SVG("drawing").viewbox(0, 0, 400, 400)
var origin = {
x: 70,
y: 70
}
var length = 60
var blueLine = draw.group()
blueLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#00f",
width: 4
})
blueLine.angle = 0
var greenLine = draw.group()
greenLine.line(0, 0, 0 + length, 0).move(origin.x, origin.y)
.stroke({
color: "#0f0",
width: 4
})
greenLine.angle = 0
var previous = 0
var dt = 0
var step = function(timestamp) {
dt = timestamp - previous
previous = timestamp
blueLine.angle += 0.18 * dt
blueLine.rotate(blueLine.angle, origin.x, origin.y)
var endX = Math.cos(toRad(blueLine.angle)) * length
var endY = Math.sin(toRad(blueLine.angle)) * length
// Comment out this line, and rotation works fine
greenLine.move(endX, endY)
greenLine.angle = blueLine.angle - 10
// Comment out this line, and movement works fine
greenLine.rotate(greenLine.angle, origin.x, origin.y)
// But they don't work together. If I both move and rotate
// the green line, it goes in this crazy huge arc, rather
// than rotating neatly around the end of the blue line
// as expected.
window.requestAnimationFrame(step)
}
window.requestAnimationFrame(step)
function toRad(deg) {
return deg * (Math.PI / 180)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/2.6.4/svg.js"></script>
<div id="drawing"></div>
Something else I noticed with my actual code is that if I move the position of the legs, it changes the severity of the problem, or even stops it altogether. If the hips are positioned all the way near the front of the bicycle, the problem is not nearly as bad. Also, if I disable rotation on the lower legs, there is no jiggling. In some positions, the lower leg will just rotate out of the screen instantly on load, even before any motion has been started.
I'm hoping for some guidance on wether I'm misunderstanding the way manipulating elements works, either in svg.js in particular, or SVG in general.
Thank you kind vector graphics experts!
Here is the actual code for the legs. The step() function would probably be the most relevant. Not sure if it will be helpful:
Rider.Leg = function(foot, front, xOffset, yOffset) {
var upper = front ? SVGE.upperLeg : SVGE.upperLegBack
var lower = front ? SVGE.lowerLeg : SVGE.lowerLegBack
this.foot = foot
this.draw = foot.draw
this.geo = {
upper: {
x: this.foot.pedal.gear.x + 150,
y: this.foot.pedal.gear.y - 750,
length: 396
},
lower: {
length: 390
}
}
this.upper = this.draw.group().svg(upper).move(this.geo.upper.x, this.geo.upper.y)
.transform({ scale: 0.95, cx: 0, cy: 0 })
this.lower = this.draw.group().svg(lower).move(this.geo.upper.x, this.geo.upper.y)
}
// Step function does not take in a time argument. Positioning of legs is based only on
// the absolute position of other elements, none of which jiggle.
Rider.Leg.prototype.step = function () {
var angle = this.pedalAngle() - Math.PI
var ha = this.scaleneAngle(this.geo.lower.length, this.geo.upper.length, this.pedalDistance())
var ka = this.scaleneAngle(this.pedalDistance(), this.geo.lower.length, this.geo.upper.length)
var x = this.geo.upper.length * Math.cos(ha + angle)
var y = this.geo.upper.length * Math.sin(ha + angle)
this.upper.rotate(Drive.toDeg(angle + ha), 0, 0)
this.lower.move(this.geo.upper.x + x, + this.geo.upper.y + y)
this.lower.rotate(Drive.toDeg(angle + ha + ka - Math.PI), 0, 0)
}
// Gets the distance between the hip joint and the pedal
Rider.Leg.prototype.pedalDistance = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.hypot(xDist, yDist)
}
// Gets the angle between the hip joint and the pedal
Rider.Leg.prototype.pedalAngle = function () {
var pos = this.foot.getPos()
var xDist = this.geo.upper.x - pos.x
var yDist = this.geo.upper.y - pos.y
return Math.atan2(yDist, xDist)
}
Rider.Leg.prototype.scaleneAngle = function (a, b, c) {
return Math.acos(((b * b) + (c * c) - (a * a)) / (2 * b * c))
}
When you call move() on a group it is internally represented as a translation. svg.js figures out crazy ways to translate the object to the new place without changing any other transformations. That often does not work out. Especially not, when you rotate.
Thats why you should avoid these absolute transformations and go with relative ones. Just call untransform before every move and go from zero. Then you can do:
greenLine.transform({x:endX, y:endY, relative: true})
To move the line by a certain amount. That should work way better.
Here is a JSFiddle of the function I built some time ago in JQuery before I learned AngularJS for the project I intended to use it in.
var numberOfPosts = 1; // To calculate the absolute/starting position of each post
var post = $('#post'); // Need to track multiple posts, ideally by array of getElementsByClass
var postOffset = post.offset(); // Relative to the document
var postPosition = post.position(); // Relative to the parent
var radiansBetweenPosts = (90 / numberOfPosts) * Math.PI / 180;
$('#wrapper').mousemove(function(event) {
// Mouse horizontal percentage position inside the wrapper (double to make full circle)
mouseX = (event.pageX - postOffset.left) / post.parent().width() * 2;
x = (Math.cos(Math.PI * mouseX + radiansBetweenPosts)) * 50 + 50; // Multiply by % size of a quadrant,
y = (Math.sin(Math.PI * mouseX + radiansBetweenPosts)) * 50 + 50; // add a % offset to the centre of the circle
post.css({
'left': x + '%',
'top': y + '%'
});
// Mouse horizontal % coordinates from the centre of the circle
$('p').html(Math.round(mouseX * 100));
});
And here is a Plunker of the same idea I translated to AngularJS, which is how it currently behaves on my project.
angular.module('mouseMovement', [])
.controller('MouseMovementController', ['$scope', '$element', function MouseMovementController($scope, $element) {
$scope.msg = "Mouse X position inside the div"
numberOfPosts = 1
radiansBetweenPosts = (90 / numberOfPosts) * Math.PI / 180
$scope.mousePosition = function(event) {
postOffsetLeft = event.target.querySelector('.postDiv').offsetLeft
frameWidth = event.target.offsetWidth
mouseXpercent = (event.pageX - postOffsetLeft) / frameWidth * 2
x = (Math.cos(Math.PI * mouseXpercent + radiansBetweenPosts)) * 50 + 50
y = (Math.sin(Math.PI * mouseXpercent + radiansBetweenPosts)) * 50 + 50
$scope.position = {
left: x + '%',
top: y + '%'
}
$scope.mouseX = Math.round(mouseXpercent * 100)
$scope.postX = Math.round(x)
$scope.postY = Math.round(y)
}
}])
It appears to me that when the mouse is moved across the div, the coordinates jump between a single digit and a two or three digit number very quickly, which you can observe if you move the mouse for a bit and check the numbers a few times. That I believe is what causes the position to spazz out like that.
Oddly, that only happens when the $scope.position variable is there, so if you comment that bit out, both the Post X and Post Y numbers will steadily change as they should when you move your mouse across the div.
What am I missing here? It seems like the coordinate calculation is suddenly wrong when the styles are applied, but that can't be true. To make it more weird, at some sedctions of the div the numbers are steadily and correctly changing, for example verticall under this bolded word on the Plunker "Mouse X position inside the div"
If it's something in the way AngularJS works internally, what solutions are there?
In addition to that, I'll need to somehow keep tracking the mouse movement across the gray div even if the mouse appears on top of the
Your math is off. Try this. My math isn't exact but it's closer to what you're looking for
angular.module('mouseMovement', [])
.controller('MouseMovementController', ['$scope', '$element', function MouseMovementController($scope, $element) {
$scope.msg = "Mouse X position inside the div"
numberOfPosts = 1
radiansBetweenPosts = (2 / numberOfPosts) * Math.PI
$scope.mousePosition = function(event) {
postOffsetLeft = event.target.querySelector('.postDiv').offsetLeft
frameWidth = event.target.offsetWidth
mouseXpercent = (event.pageX) / frameWidth
x = Math.PI * (Math.cos(mouseXpercent * radiansBetweenPosts)) * 10 + 40
y = Math.PI * (Math.sin(mouseXpercent * radiansBetweenPosts)) * 10 + 20
$scope.position = {
left: x + '%',
top: y + '%'
}
$scope.mouseX = Math.round(mouseXpercent * 100)
$scope.postX = Math.round(x)
$scope.postY = Math.round(y)
}
}])
I have a project with a circle that, when clicked, rotates to a predefined position. It is almost there, but the last requirement is that it always rotates clockwise to the marker. I just can't seem to figure out how to get the right value so that when i set css transform:rotate(Xdeg), it will always go clockwise. Keeping the angle between 0 and 360 would also be a plus for another piece of this, but not necessary.
See this fiddle, javascript below as well Rotation
$(function () {
$('body').on('click', '#graph1', function (e) {
console.log('********************');
//get mouse position relative to div and center of div for polar origin
var pos = getMousePosAndCenter(e, 'graph1');
//get the current degrees of rotation from the css
var currentRotationDegrees = getCSSRotation('#graph1');
console.log('CSS Rotation Value: ' + currentRotationDegrees);
//current rotation in radians
var currentRotationRadians = radians(currentRotationDegrees);
//radians where clicked
var clickRadiansFromZero = Math.atan2(pos.y - pos.originY, pos.x - pos.originX);
//degrees the click is offset from 0 origin
var offsetDegrees = degrees(clickRadiansFromZero);
//how many degrees to rotate in css to put the mouse click at 0
var degreesToZero;
if (offsetDegrees >= 0)
degreesToZero = currentRotationDegrees - Math.abs(offsetDegrees);
else
degreesToZero = currentRotationDegrees + Math.abs(offsetDegrees);
console.log("Degrees to Zero: " + degreesToZero);
//distance in pixels from origin
var distance = calculateDistance(pos.originX, pos.originY, pos.x, pos.y);
console.log("Distance From Origin(px): " + distance);
$('#graph1').css('transform','rotate(' + degreesToZero + 'deg)')
});
});
function getMousePosAndCenter(e, id) {
var rect = document.getElementById(id).getBoundingClientRect();
return {
x: (((e.clientX - rect.left) / rect.width) * rect.width) + 0.5 << 0,
y: (((e.clientY - rect.top) / rect.height) * rect.height) + 0.5 << 0,
originY: (rect.height / 2),
originX: (rect.width / 2)
};
}
function radians(degrees) {
return degrees * Math.PI / 180;
};
function degrees(radians) {
return radians * 180 / Math.PI;
};
function calculateDistance(originX, originY, mouseX, mouseY) {
return Math.floor(Math.sqrt(Math.pow(mouseX - originX, 2) + Math.pow(mouseY - originY, 2)));
}
function getCSSRotation(id) {
var matrix = $(id).css('transform');
var values = matrix.split('(')[1],
values = values.split(')')[0],
values = values.split(',');
var a = values[0];
var b = values[1];
var c = values[2];
var d = values[3];
var cssRotation = degrees(Math.atan2(b, a));
return cssRotation;
}
Think out of the box:
We can CSS3 rotate an element with transform to i.e: 720° ...
it will make 2 clockwise turns. (OK, in our UI it can only do max a 359 turn but let's follow the math)
If we than animate it to 810°... it just means that it'll do a 90° clockwise move!
So all we need to do is always increase a degree variable to insanity!
HEY! If at some point you want to keep track of the current normalized 0-360 degree...
you can always retrieve that value doing ourCurrentInsanelyHighDegree % 360 = UIdegrees
Here's a jsBin demo
and this is all the JS you need.
function getCSSRotation( $el ) {
var matrix = $el.css('transform'),
v = matrix.split('(')[1].split(')')[0].split(','),
rds = Math.atan2(v[1], v[0]);
return rds*180/Math.PI <<0; // Degrees
}
var $EL = $("#graph1"),
w = $EL.width(),
r = w/2, // Radius
x = parseInt($EL.css("left"), 10),
y = parseInt($EL.css("top"), 10),
d = getCSSRotation( $EL ); // Initial degree (ONLY ONCE!)
$EL.on("click", function(e){
var mx = e.clientX-x-r, // Click coord X
my = e.clientY-y-r, // Click coord Y
rds = Math.atan2(-my, -mx), // Radians
md = (rds*180/Math.PI<<0) + 180; // Mouse Degrees
d += (360-md); // always increment to insanity!!
$(this).css({transform:"rotate("+ d +"deg)"});
});
#graph1 {
position:absolute;
top:10px; left:30px;
width:200px; height:200px;
background:url(//placehold.it/200x200&text=IMAGE);
transition:transform 2s ease;
transform:rotate(30deg);
transform-origin:50% 50%;
border-radius:50%;
}
#marker {
position: absolute;
top:110px;
left:230px;
border-top:1px solid black;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="graph1"></div>
<div id="marker">Wherever you click, it rotates to here</div>
UPDATE:
Figuring it would be easy to do, I found it a little harder than I thought. The other answer with jQuery.animate works, but animate doesn't have the fluid framerate that css animation does (it runs on the GPU).
Here's a modified fiddle with a CSS solution: http://jsfiddle.net/2g17cjuL/2/
Keeping the angle between 0 and 360 would also be a plus
You cannot keep going forward (ie rotating by a positive number) and keep the rotation positive, however, in my fiddle offsetDegrees (the number of degrees additional rotated), or the remainder of totalDegreesdivided by 360 should give you what you need to use elsewhere.
Requrement: That it always rotates clockwise.
One thing: If you use CSS transitions, it'll calculate the shortest route for you. You want a bit more control over rotational direction, so I commented out the transition:transform 1s ease; in your CSS because we'll control this manually.
JAVASCRIPT
I borrowed this JQuery function and modified it so we can feed it a starting angle, and ending angle and it'll animate #graph1 for us. (Read the link to change duration, easing, and to use the complete callback)
$.fn.animateRotate = function(angle, start, duration, easing, complete) {
var args = $.speed(duration, easing, complete);
var step = args.step;
return this.each(function(i, e) {
args.complete = $.proxy(args.complete, e);
args.step = function(now) {
$.style(e, 'transform', 'rotate(' + now + 'deg)');
if (step) return step.apply(e, arguments);
};
$({deg: start}).animate({deg: angle}, args);
});
};
I also modified your JQuery so it won't rotate counter-clockwise: when currentRotationDegrees is greater than degreesToZero, it'll subtract 360, and then use this new value as the starting position for `animateRotate().
if(currentRotationDegrees > degreesToZero){
currentRotationDegrees -= 360;
}
$('#graph1').animateRotate(degreesToZero, currentRotationDegrees);
Here it is in action.
http://jsfiddle.net/q4nad31t/1/
I currently have an issue with my code (written in Javascript); I have arrays objects that keep filling as the time goes. An example of an object:
monster.push({
range: 200,
attackSpeed: 500,
lastFire: 100,
id: 'ogre',
speed : 50,
pos:[canvas.width*Math.random(), canvas.height*Math.random()],
sprite: new Sprite('images/sheet_characters.png',[508,224],64,64],6,[0])
and
hero={
attackSpeed: 200,
lastGetHit: Date.now(),
lastFire: Date.now(),
health : 100,
speed: 256, //pixel/second
pos:[canvas.width/2,canvas.height/2],
sprite: new Sprite('images/sheet_characters.png',[256,0],[32,32],8,[0]) };
The position field of the objects change quite often and I want to add a function that determines the slope between the monster and the hero (we want the monster to fire at the hero) and then the attack should follow a linear movement.
What I currently have
for(var i=0; i<monster.length; i++){
var mob = monster[i];
mob.sprite.update(delta); //animatie
var newPos = moveTowards(mob, hero, delta);
mob.pos[0] = newPos[0]
mob.pos[1] = newPos[1]
if(checkBounds(mob.pos,mob.sprite.size)){
monster.splice(i,1);
}
mobAttacks(mob);
var attack = enemyAttacks[i]; //atacks updaten
attack.sprite.update(delta);
attack.pos[0] = attack.speed * Math.cos(attack.direction)));
attack.pos[1] = attack.speed * Math.sin(attack.direction)));
if(checkBounds(attack.pos,attack.sprite.sieze)){
enemyAttacks.splice(i,1);
}
}
In this for-loop I can access the position of the monster that fires and also the hero position as it is a global variable. Now the function to attack is :
function mobAttacks(object)
{
var distance = Math.sqrt(Math.pow((hero.pos[0]-object.pos[0]),2) + Math.pow((hero.pos[1]-object.pos[1]),2));
if( Date.now() - object.lastFire > object.attackSpeed && object.range >= distance)
{
deltaY = hero.pos[1] - object.pos[1];
deltaX = hero.pos[0] - object.pos[0];
var direction = Math.atan(deltaY/deltaX);
enemyAttacks.push({
pos:[(object.pos[0]+object.sprite.size[0]/2), (object.pos[1]+object.sprite.size[1]/2)],
direction: direction,
speed: 128, //pixel/s
sprite: new Sprite('images/sheet_objects.png', [231,3],[24,24],6,[0])
});
object.lastFire = Date.now();
}
}
The angle between both objects is calculated and I make a new object (the attack) with the start position of the monster.
The result is quite odd:
The slope is off, so is the Y position of the boulder. Also when the hero is on the left side of the monster, there is no boulder to be spotted.
After some hours of tinkering with the code I came to the conclusion that I couldn't solve my current problem.
EDIT:
attack.pos[0] += attack.speed * Math.cos(attack.direction)*delta;
attack.pos[1] += attack.speed * Math.sin(attack.direction)*delta;
Solved the issue that the boulders are no longer cast from a random position.
Now the angle is a not going negative when I'm in the 2nd or 3rd kwadrant (position left when viewed from the monster perspective)
Get all the trig out of your code, it's unnecessary. Let
deltaX = hero.pos[0] - object.pos[0];
deltaY = hero.pos[1] - object.pos[1];
then
distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
deltaX /= distance;
deltaY /= distance;
will make <deltaX,deltaY> a normalized vector (one with a length of 1).
Then you can update the position of the attack for delta time using simply:
attack.pos[0] += attack.speed * attack.deltaX * delta;
attack.pos[1] += attack.speed * attack.deltaY * delta;
If you don't have any use for the speed and direction separately, you can also pre-multiply speed into deltaX and deltaY when you initialize the attack, meaning that the update becomes only
attack.pos[0] += attack.deltaX * delta;
attack.pos[1] += attack.deltaY * delta;
which is nice and simple.
We got 3 points: start, end and mail.
The mail image, moves in a curved line from the start and end point, this is done by jQuery animate.
Now the next step is to make the mail image rotate while the animation is running. So at the start point and end point it would be rotated 0 degrees, but while animating, it should rotate facing the path of the animation. (see the image)
What I have tried:
JSFiddle
// Init dom elements
var $start = $('#start');
var $end = $('#end');
var $mail = $('#mail');
// Get position coordinates
var startPos = $start.position();
var endPos = $end.Position();
// Angle calculation
var getAngle = function(currX, currY, endX, endY) {
var angle = Math.atan2(currX - endX, currY - endY) * (180 / Math.PI);
if (angle < 0) {
angle = Math.abs(angle);
} else {
angle = 360 - angle;
}
return angle;
};
// Mail angle
var getMailAngle = function() {
var currPos = $mail.position();
var endPos = $end.position();
return getAngle(currPos.left, currPos.top, endPos.left, endPos.top);
};
// Animate
$mail.animate({top: endPos.top, left: endPos.left}, {
specialEasing: {left: "easeInSine", top: "linear"},
// Executed each "animation" frame, so we rotate here.
step: function() {
var angle = getMailAngle();
$(this).css('transform', 'rotate(' + angle + 'deg'));
}
});
But the code above is not correct, the angle doesn't face up when started / ended, I have very little experience with geometry math, so I really appreciate help for the rotating calculations.
First off, you need to use an easing animation that starts and ends with the same "angle". If you look at the different easing options, swing, easeInOutQuad and easeInOutSine are some of the valid options.
To calculate an approximation of the angle, you can look at the mail icon's current position and its next position (in the next animation frame). To get a good approximation you need to "manually" calculate the current and next position using the easing function. This also means you need to control the animation manually.
Here's a code snippet, and you can also see it on JSFiddle.
// Init dom elements
var $start = $('#start');
var $end = $('#end');
var $mail = $('#mail');
// Get position coordinates
var startPos = $start.offset();
var endPos = $end.offset();
// Angle calculation
var getAngle = function(currX, currY, endX, endY) {
var angle = Math.atan2(currX - endX, currY - endY) * (180 / Math.PI);
if (angle < 0) {
angle = Math.abs(angle);
} else {
angle = 360 - angle;
}
return angle;
};
// Animate
var maxframe = 1000;
$({frame: 0}).animate({frame: maxframe}, {
easing: "linear",
duration: 1000,
// Executed each "animation" frame, so we rotate here.
step: function() {
var easing = $.easing.easeInOutQuad;
var left = easing(0, this.frame, startPos.left, endPos.left - startPos.left, maxframe);
var leftNext = easing(0, this.frame+1, startPos.left, endPos.left - startPos.left, maxframe);
var top = startPos.top + (endPos.top - startPos.top) * this.frame / maxframe;
var topNext = startPos.top + (endPos.top - startPos.top) * (this.frame + 1) / maxframe;
var angle = getAngle(left, top, leftNext, topNext);
$mail.offset({left: left, top: top});
$mail.css('transform', 'rotate(' + angle + 'deg)');
},
// Set the final position
complete: function() {
$mail.offset($end.offset());
$mail.css('transform', '');
}
});