Canvas has no shortage of uses on the web, from procedurally drawn graphics to full games — a Solitaire build where every card and card movement lives inside <canvas> is one example. What canvas lacks is the animation vocabulary CSS gives you: there is no built-in "ease-in" or "ease-out" to reach for. Transition easing has to be written by hand.
Moving a card, then widening the easing out
Start from a static canvas holding a single playing card pulled out of the DOM. Even a simple translate means writing the machinery yourself, so the first pieces are coordinate and movement helpers:
function getX(params) {
let distance = params.xTo - params.xFrom;
let steps = params.frames;
let progress = params.frame;
return distance / steps * progress;
}
function getY(params) {
let distance = params.yTo - params.yFrom;
let steps = params.frames;
let progress = params.frame;
return distance / steps * progress;
}
The canvas then keeps re-rendering until the animation finishes, driven from the addImage() method:
if (params.frame < params.frames) {
params.frame = params.frame + 1;
window.requestAnimationFrame(drawCanvas);
window.requestAnimationFrame(addImage.bind(null, params))
}
That produces motion that is linear: one unit added per step, the same speed from start to finish. It works, but the abrupt start and stop look wrong. Real objects accelerate and decelerate under friction and gravity, and the linear version does neither.
Cubic, quadratic, quintic, sinusoidal, exponential
A cubic ease-in/ease-out gets the acceleration and deceleration. The equation below is adapted from Robert Penner's Flash easing functions for this use case:
function getEase(currentProgress, start, distance, steps) {
currentProgress /= steps/2;
if (currentProgress < 1) {
return (distance/2)*(Math.pow(currentProgress, 3)) + start;
}
currentProgress -= 2;
return distance/2*(Math.pow(currentProgress, 3)+ 2) + start;
}
Dropping it in gives a visibly smoother result — the card picks up speed toward the middle of the travel and slows on arrival.
If the acceleration should be gentler, use a quadratic or a sinusoidal ease instead:
function getQuadraticEase(currentProgress, start, distance, steps) {
currentProgress /= steps/2;
if (currentProgress <= 1) {
return (distance/2)*currentProgress*currentProgress + start;
}
currentProgress--;
return -1*(distance/2) * (currentProgress*(currentProgress-2) - 1) + start;
}
function sineEaseInOut(currentProgress, start, distance, steps) {
return -distance/2 * (Math.cos(Math.PI*currentProgress/steps) - 1) + start;
};
For a sharper ramp-up, reach for quintic or exponential easing:
function getQuinticEase(currentProgress, start, distance, steps) {
currentProgress /= steps/2;
if (currentProgress < 1) {
return (distance/2)*(Math.pow(currentProgress, 5)) + start;
}
currentProgress -= 2;
return distance/2*(Math.pow(currentProgress, 5) + 2) + start;
}
function expEaseInOut(currentProgress, start, distance, steps) {
currentProgress /= steps/2;
if (currentProgress < 1) return distance/2 * Math.pow( 2, 10 * (currentProgress - 1) ) + start;
currentProgress--;
return distance/2 * ( -Math.pow( 2, -10 * currentProgress) + 2 ) + start;
};
Handing the work to GSAP
Writing easing functions by hand is only one route. The GreenSock Animation Platform (GSAP) covers the same ground with more flexibility — an example with a bounce at the end is included below, with the GSAP library loaded in the demo.
Everything hangs off moveCard:
function moveCard() {
gsap.to(position, {
duration: 2,
ease: "bounce.out",
x: position.xMax,
y: position.yMax,
onUpdate: function() {
draw();
},
onComplete: function() {
position.x = position.origX;
position.y = position.origY;
}
});
}
gsap.to does the work: over a two-second duration it mutates the position object, and each mutation fires onUpdate, which redraws the canvas. Bounce is far from the only choice — GSAP ships a wide range of easing options of its own.
Beyond position
Easing is not limited to translation. Any attribute with a from and a to state can be eased the same way: opacity, rotation, scale, and whatever else your rendering needs. The non-GSAP easing in the Solitaire game drives cards between piles, easing out and back in as they travel.



