Skip to content

Instantly share code, notes, and snippets.

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

An example of Accelerate, Then Coast easing. In comparison to cubic-in-out easing, accelerate-then-coast easing restricts the maximum angular speed of the gears. Linear easing also uses constant speed, but lacks slow-in and slow-out. Use the radio buttons to compare easing functions.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: auto;
position: relative;
width: 960px;
}
label {
display: block;
}
form {
position: absolute;
left: 20px;
top: 20px;
}
</style>
<form>
<label><input type="radio" name="mode" value="atc" checked> accelerate-then-coast</label>
<label><input type="radio" name="mode" value="cubic"> cubic-in-out</label>
<label><input type="radio" name="mode" value="linear"> linear</label>
</form>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 960,
height = 500;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var gearBig = svg.append("g")
.datum({teeth: 32, radius: height * 2 / 5})
.attr("transform", "translate(" + -height * 1 / 5 + ",0)")
.append("g")
.attr("class", "gear");
gearBig.append("path")
.style("fill", "gray")
.attr("d", pathGear);
gearBig.append("path")
.style("fill", "black")
.style("fill-opacity", .3)
.attr("d", d3.svg.arc()({
innerRadius: 20,
outerRadius: height * 2 / 5 - 8,
startAngle: 0,
endAngle: Math.PI
}));
var gearSmall = svg.append("g")
.datum({teeth: 16, radius: -height * 1 / 5})
.attr("transform", "translate(" + height * 2 / 5 + ",0)")
.append("g")
.attr("class", "gear");
gearSmall.append("path")
.style("fill", "brown")
.attr("d", pathGear);
gearSmall.append("path")
.style("fill", "black")
.style("fill-opacity", .3)
.attr("d", d3.svg.arc()({
innerRadius: 20,
outerRadius: height * 1 / 5 - 8,
startAngle: -Math.PI,
endAngle: 0
}));
var gear = svg.selectAll(".gear");
d3.selectAll("input")
.data([ease = easeReflect(easeAccelerateThenCoast(1.5), .5), d3.ease("cubic-in-out"), d3.ease("linear")])
.on("change", function(d) { ease = d; loop(); })
.call(loop);
function loop() {
d3.transition().duration(10000).ease(ease).each(function() {
gear.transition().attrTween("transform", function(d) { return tweenRotate(360 * 32 * 3 / d.teeth * (d.radius < 0 ? -1 : +1)); });
}).transition().duration(500).each("end", loop);
}
function tweenRotate(angle) {
var i = d3.interpolateNumber(0, angle);
return function(t) { return "rotate(" + i(t) + ")"; };
}
function pathGear(d) {
var n = d.teeth,
r2 = Math.abs(d.radius),
r0 = r2 - 8,
r1 = r2 + 8,
r3 = d.annulus ? (r3 = r0, r0 = r1, r1 = r3, r2 + 20) : 20,
da = Math.PI / n,
a0 = -Math.PI / 2 + (d.annulus ? Math.PI / n : 0),
i = -1,
path = ["M", r0 * Math.cos(a0), ",", r0 * Math.sin(a0)];
while (++i < n) path.push(
"A", r0, ",", r0, " 0 0,1 ", r0 * Math.cos(a0 += da), ",", r0 * Math.sin(a0),
"L", r2 * Math.cos(a0), ",", r2 * Math.sin(a0),
"L", r1 * Math.cos(a0 += da / 3), ",", r1 * Math.sin(a0),
"A", r1, ",", r1, " 0 0,1 ", r1 * Math.cos(a0 += da / 3), ",", r1 * Math.sin(a0),
"L", r2 * Math.cos(a0 += da / 3), ",", r2 * Math.sin(a0),
"L", r0 * Math.cos(a0), ",", r0 * Math.sin(a0));
path.push("M0,", -r3, "A", r3, ",", r3, " 0 0,0 0,", r3, "A", r3, ",", r3, " 0 0,0 0,", -r3, "Z");
return path.join("");
}
// Like in-out reflection, except around the specified center.
// If center = .5, this is the same as in-out reflection.
function easeReflect(ease, center) {
return function(t) {
return t < center ? center * ease(t / center) : 1 - ease((1 - t) / (1 - center)) * (1 - center);
};
}
// 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(acceleration)) return d3.ease("linear");
var speed = 2 * (acceleration - Math.sqrt((acceleration - 1) * acceleration)),
t0 = speed / 2 / acceleration;
return function(t) {
return t < t0 ? acceleration * t * t : speed * t - speed + 1;
};
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment