Skip to content

Instantly share code, notes, and snippets.

@mbostock
Last active February 9, 2016 01:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mbostock/10342273 to your computer and use it in GitHub Desktop.
Save mbostock/10342273 to your computer and use it in GitHub Desktop.
Accelerate, Then Coast
license: gpl-3.0

This diagram demonstrates an easing function that first applies constant acceleration and then “coasts” at constant speed. This is useful to constrain the maximum speed of long slow-in transitions.

The constant acceleration curve is shown in brown, while the constant speed line is shown in blue. The combined curve is shown in black. The lines intersect at the point their derivatives are equal, providing C1 continuity.

With acceleration a = 1, this easing function is equivalent to quadratic easing: constant acceleration is applied continuously from t = 0 to t = 1. With infinite acceleration, this easing function is equivalent to linear easing: the acceleration is instantaneous at t = 0.

This easing function can be combined with reflection to incorporate slow-out decceleration.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
width: 960px;
height: 500px;
position: relative;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis text {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: #000;
stroke-linecap: round;
}
line {
stroke: #aaa;
shape-rendering: crispEdges;
}
#acceleration {
position: absolute;
bottom: 27px;
right: 50px;
}
#acceleration input {
width: 140px;
}
#acceleration span {
position: relative;
top: -3px;
}
#acceleration output {
display: inline-block;
width: 3.5em;
}
</style>
<body>
<div id="acceleration">
<input type="range" min="0" max="1" step=".01" value=".55">
<span><i>a</i> = <output name="acceleration"></output></span>
</div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 40, right: 340, bottom: 40, left: 200},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var color = d3.scale.category20c();
var formatNumber = d3.format(".4f");
var x = d3.scale.linear()
.domain([0, 1])
.range([0, width]);
var y = d3.scale.linear()
.domain([0, 1])
.range([height, 0]);
var continuousLine = (function() {
var samples = d3.range(0, 1 + 1e-6, 2 / width);
var discreteLine = d3.svg.line()
.x(function(d, i) { return x(samples[i]); })
.y(function(d) { return y(d); });
return function(f) {
return discreteLine(samples.map(f));
};
})();
var ease;
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis()
.scale(x)
.orient("bottom"))
.append("text")
.attr("x", width)
.attr("y", -3)
.attr("dy", "-.35em")
.style("font-weight", "bold")
.style("text-anchor", "middle")
.text("time");
svg.append("g")
.attr("class", "axis axis--y")
.call(d3.svg.axis()
.scale(y)
.orient("left"))
.append("text")
.attr("x", 6)
.attr("dy", ".35em")
.style("font-weight", "bold")
.text("ease(time)");
var lineEase = svg.append("path")
.attr("class", "line")
.style("stroke", "#000");
var lineAccelerate = svg.append("path")
.attr("class", "line")
.style("stroke", "brown")
.style("stroke-width", "2px")
.style("stroke-dasharray", "2,5");
var lineCoast = svg.append("path")
.attr("class", "line")
.style("stroke", "steelblue")
.style("stroke-width", "2px")
.style("stroke-dasharray", "2,5");
var timeReference = svg.append("line").attr("y1", height),
easeReference = svg.append("line");
var circle = svg.append("circle")
.attr("r", 4.5)
.attr("cx", 0)
.attr("cy", height);
var output = d3.select("output");
var inputScale = d3.scale.pow()
.exponent(4)
.domain([0, 1])
.range([1, 10]);
var input = d3.select("input")
.on("input", function() { reset(inputScale(this.value)); })
.each(function() { reset(inputScale(this.value)); });
!function loop() {
circle.transition()
.ease("linear")
.duration(5000)
.tween("transform", function() {
return function(t) {
circle.attr("cx", x(t)).attr("cy", y(ease(t)));
timeReference.attr("x1", x(t)).attr("x2", x(t)).attr("y2", y(ease(t)));
easeReference.attr("x2", x(t)).attr("y1", y(ease(t))).attr("y2", y(ease(t)));
};
})
.each("end", loop);
}();
function reset(acceleration) {
output.text(formatNumber(acceleration));
ease = easeAccelerateThenCoast(acceleration);
lineEase.attr("d", continuousLine(ease));
lineAccelerate.attr("d", continuousLine(function(x) { return ease.acceleration * x * x; }));
lineCoast.attr("d", continuousLine(function(x) { return ease.speed * x - ease.speed + 1; }));
}
// Constant acceleration followed by constant speed.
// If acceleration = 1, this is the same as quadratic easing.
// If acceleration = Infinity, this is the same as linear easing.
function easeAccelerateThenCoast(acceleration) {
if (acceleration < 1) throw new Error("unpossible");
if (!isFinite(ease.acceleration = acceleration)) return d3.ease("linear");
var speed = ease.speed = 2 * (acceleration - Math.sqrt((acceleration - 1) * acceleration)),
t0 = ease.t0 = speed / 2 / acceleration;
function ease(t) {
return t < t0 ? acceleration * t * t : speed * t - speed + 1;
}
return ease;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment