block by mbostock 10343037

Accelerate, Then Coast II

Full Screen

This diagram takes the Accelerate, Then Coast easing function and reflects it to apply slow-out. The reflection center r is configurable. With r = .5, the reflection is equivalent to the standard “in-out” symmetric easing; with r = 0, only slow-out is applied, and with r = 1, only slow-in is applied.

index.html

<!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;
}

#form {
  position: absolute;
  bottom: 27px;
  right: 50px;
}

input {
  width: 140px;
}

span {
  position: relative;
  top: -3px;
}

output {
  display: inline-block;
  width: 3.5em;
}

</style>
<body>
<div id="form">
  <div id="acceleration">
    <input type="range" min="0" max="1" step=".01" value=".55">
    <span><i>a</i> = <output name="acceleration"></output></span>
  </div>
  <div id="reflection">
    <input type="range" min="0" max="1" step=".01" value=".5">
    <span><i>r</i> = <output name="reflection"></output></span>
  </div>
</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")
    .style("stroke-width", "1.5px");

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 accelerationInput = d3.select("#acceleration input").on("input", reset),
    accelerationOutput = d3.select("#acceleration output"),
    reflectionInput = d3.select("#reflection input").on("input", reset),
    reflectionOutput = d3.select("#reflection output");

var accelerationInputScale = d3.scale.pow()
    .exponent(4)
    .domain([0, 1])
    .range([1, 10]);

reset();

!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() {
  var acceleration = accelerationInputScale(accelerationInput.property("value")),
      reflection = reflectionInput.property("value");
  accelerationOutput.text(formatNumber(acceleration));
  reflectionOutput.text(formatNumber(reflection));
  ease = easeReflect(easeAccelerateThenCoast(acceleration), reflection);
  lineEase.attr("d", continuousLine(ease));
}

// 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>