I am not sure if I have used the right word here. I guess easing means it does not follow the mouse immediately but with some delay?
At the moment the iris is rotati
There are many ways to do easing. Two methods I will describe in short are deterministic easing and (surprisingly) non-deterministic. The difference being is that the destination of the ease is either known (determined) or unknown (awaiting more user input)
Deterministic easing.
For this you have a starting value and an end value. What you want to do is find a position between the two based on some time value. That means that the start and end values need to also be associated with a time.
For example
var startVal = 10;
var startTime = 100;
var endVal = 100;
var endTime = 200;
You will want to find the value at time 150 halfway between the two. To do this you convert the time to a fraction where the time 100 (start) returns 0 and the time 200 (end) return 1, we call this normalised time. You can then multiply the difference between the start and end values by this fraction to find the offset.
So for a time value 150 to get the value (theValue) we do the following.
var time = 150;
var timeDif = endTime - startTime
var fraction = (startTime - time) / timeDif; // the normalised time
var valueDif = endVal - startVal;
var valueOffset = valueDif * fraction;
var theValue = startVal + valueOffset;
or more concise.
// nt is normalised time
var nt = (startTime - time) / (endTime - startTime)
var theValue = startVal + (endVal - startVal) * nt;
Now to apply a easing we need to modify the normalised time. A easing function simply takes a value from 0 to 1 inclusive and modifies it. So if you input 0.25 the easing function returns 0.1, or 0.5 return 0.5 and 0.75 returns 0.9. As you can see the modification changes the rate of change over the time.
An example of an easing function.
var easeInOut = function (n, pow) {
n = Math.min(1, Math.max(0, n)); // clamp n
var nn = Math.pow( n, pow);
return (nn / ( nn + Math.pow(1 - n, pow)))
}
This function takes two inputs, the fraction n (0 to 1 inclusive) and the power. The power determines the amount of easing. If pow = 1 then the is no easing and the function returns n. If pow = 2 then the function is the same as the CSS ease in out function, starts slowly speeds up then slows down at the end. if pow < 1 and pow > 0 then the ease start quickly slows down midway and then speeds up to the end.
To use the easing function in the above easing value example
// nt is normalised time
var nt = (startTime - time) / (endTime - startTime);
nt = easeInOut(nt,2); // start slow speed up, end slow
var theValue = startVal + (endVal - startVal) * nt;
That is how deterministic easing is done
An excellent easing function page Easing examples and code and another page for a quick visual easing referance
Non-deterministic easing
You may not know what the end result of the easing function is as at any time it may change due to new user input, if you use the above methods and change the end value mid way through the ease the result will be inconsistent and ugly. If you have ever done calculus you may recognise that the ease function above is a polynomial and is thus the anti derivative of a simpler function. This function simply determines the amount of change per time step. So for the non deterministic solution all we know is the change for the next time step. For an ease in function (start quick and slow down as we approch the destination) we keep a value to represent the current speed (the rate of change) and modify that speed as needed.
const ACCELERATION_COEFFICIENT = 0.3;
const DRAG_COEFFICIENT = 0.99;
var currentVal = 100;
var destinationVal = 200;
var currentSpeed = 0;
Then for each time step you do the following
var accel = destinationVal - currentVal; // get the acceleration
accel *= ACCELERATION_COEFFICIENT; // modify it so we are not there instantly
currentSpeed += accel; // add that to the speed
currentSpeed *= DRAG_COEFFICIET; // add some drag to further ease the function as it approaches destination
currentVal += currentSpeed; // add the speed to the current value
Now the currentVal will approch the destination value, if the destination changes than the rate of change (speed) also changes in a consistent way. The currentVal may never get to the destination if the destination is always changing, if however the destination stops changing the current val will approch and eventually stop at destination (by stop I mean the speed will get so small as to be pointless)
This methods behaviour is very dependent on the two coefficients so playing with those values will vary the easing. Some values will give you an over shoot with a bit of a wobble, others will be very slow like moving through molasses.
You can also make it much more complex by adding a second rate of change, thus you can have accelerating acceleration, this will simulate things like air resistance that changes the acceleration over time. You can also add maximums to the rate of change to set speed limits.
That should help you do your easing.
More info For more info see these answers How would I animate and How to scale between two points
Non-deterministic easing applied to your example
I have added the easing to your function but it has introduced a new problem that will happen when using cyclic values such as angle. As I will not go into it in this answer you can find a solution to that problem in Finding the smallest angle.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ACCELERATION_COEFFICIENT = 0.15;
const DRAG_COEFFICIENT = 0.5;
class Circle {
constructor(options) {
this.cx = options.x;
this.cy = options.y;
this.radius = options.radius;
this.color = options.color;
this.angle = options.angle;
this.angleSpeed = 0;
this.currentAngle = this.angle;
this.binding();
}
binding() {
const self = this;
window.addEventListener('mousemove', (e) => {
self.calculateAngle(e);
});
}
calculateAngle(e) {
if (!e) return;
let rect = canvas.getBoundingClientRect(),
vx = e.clientX - this.cx,
vy = e.clientY - this.cy;
this.angle = Math.atan2(vy, vx);
}
renderEye() {
// this should be in a separate function
this.angleSpeed += (this.angle - this.currentAngle) * ACCELERATION_COEFFICIENT;
this.angleSpeed *= DRAG_COEFFICIENT;
this.currentAngle += this.angleSpeed;
ctx.setTransform(1, 0, 0, 1, this.cx, this.cy);
ctx.rotate(this.currentAngle);
let eyeRadius = this.radius / 3;
ctx.beginPath();
ctx.arc(this.radius / 2, 0, eyeRadius, 0, Math.PI * 2);
ctx.fill();
}
render() {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.beginPath();
ctx.arc(this.cx, this.cy, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.strokeStyle = '#09f';
ctx.lineWidth = 1;
ctx.stroke();
this.renderMessage();
this.renderEye();
}
renderMessage() {
ctx.font = "18px serif";
ctx.strokeStyle = 'black';
ctx.fillText('Angle: ' + this.angle, 30, canvas.height - 40);
}
}
var rotatingCircle = new Circle({
x: 320,
y: 160,
radius: 40,
color: 'black',
angle: Math.random() * Math.PI * 2
});
function animate() {
rotatingCircle.render();
requestAnimationFrame(animate);
}
animate();