daniel.szaniszlo
daniel.szaniszlo

Reputation: 421

Transition speeds up after pause/resume

I am trying to create a mediabar like component which has functions like pause and resume. The red vertical line goes from left to right, and when I click the pause button it will stop the transition, and when I click the play button it will resume the transition.

I've created a codepen also for this problem.

I appreciate all your help;

const ANIMATIONLENGTH = 10000;

let pauseValues = {
  lastT: 0,
  currentT: 0,
  currentPos: 0
};

const svg = d3
  .select("#mediabar")
  .append("svg")
  .attr("width", 640)
  .attr("height", 18);

svg.append("rect")
  .attr("width", "100%")
  .attr("height", "100%")
  .attr("fill", "#D3D3D3");

const line = svg
  .append("rect")
  .attr("height", 18)
  .attr("width", 2)
  .attr("fill", "red")
  .attr("class", "slider");
const scaleTimeline = d3.scale
  .linear()
  .domain([0, 1])
  .range([0, 640]);
const play = () => {
  line
    .transition()
    .duration(ANIMATIONLENGTH * (1 - pauseValues.lastT))
    .ease("linear")
    .attrTween("x", () => t => {
      const time = t + pauseValues.lastT;
      pauseValues.currentT = time;
      pauseValues.currentPos = scaleTimeline(time);
      return pauseValues.currentPos;
    })
    .each("end", () => {
      pauseValues = {
        lastT: 0,
        currentT: 0,
        currentPos: 0
      };
      play();
    });
};

const pause = () => {
  line.transition().duration(0);
  setTimeout(() => {
    pauseValues.lastT = pauseValues.currentT;
  }, 100);
};
.buttons {
  display: flex;
  flex-direction: row;
  margin-bottom: 4px;
}

.buttons-pause {
  padding: 2px;
  border: 1px solid black;
  border-radius: 3px;
  cursor: pointer;
}

.buttons-play {
  padding: 2px;
  border: 1px solid black;
  border-radius: 3px;
  margin-right: 4px;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class='buttons'>
  <div class="buttons-play" onclick="play()">play</div>
  <div class="buttons-pause" onclick="pause()">pause</div>
</div>

<div id='mediabar'></div>

Upvotes: 3

Views: 59

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

It seems to me that using a transition is not only an overkill here, but also that you'll have to bend over backwards (as you are!) just to make things work. Personally, I'd rather use a D3 interval or a D3 timer.

Back to the question:

The problem is just the factory inside the attrTween method. As you know, that t goes from 0 to 1, and this makes time going beyond the limit (which is 1, given the domain of your scaleTimeline scale) when you pause the animation:

const time = t + pauseValues.lastT;

So, the last value for time is always bigger than 1 (because pauseValues.lastT is greater than 0), and the closer to the end you pause the transition the higher that last value will be (and, therefore, the higher the speed of the line).

The simple fix is:

const time = pauseValues.lastT + (1 - pauseValues.lastT) * t;

As you can see, by using (1 - pauseValues.lastT) * t we make sure that the last value of time (when t reaches 1) is 1.

Here is the code with that change:

const ANIMATIONLENGTH = 10000;

let pauseValues = {
  lastT: 0,
  currentT: 0,
  currentPos: 0
};

const svg = d3
  .select("#mediabar")
  .append("svg")
  .attr("width", 640)
  .attr("height", 18);

svg.append("rect")
  .attr("width", "100%")
  .attr("height", "100%")
  .attr("fill", "#D3D3D3");

const line = svg
  .append("rect")
  .attr("height", 18)
  .attr("width", 2)
  .attr("fill", "red")
  .attr("class", "slider");
const scaleTimeline = d3.scale
  .linear()
  .domain([0, 1])
  .range([0, 640]);
const play = () => {
  line
    .transition()
    .duration(ANIMATIONLENGTH * (1 - pauseValues.lastT))
    .ease("linear")
    .attrTween("x", () => t => {
      const time = pauseValues.lastT + (1 - pauseValues.lastT) * t;
      pauseValues.currentT = time;
      pauseValues.currentPos = scaleTimeline(time);
      return pauseValues.currentPos;
    })
    .each("end", () => {
      pauseValues = {
        lastT: 0,
        currentT: 0,
        currentPos: 0
      };
      play();
    });
};

const pause = () => {
  line.transition().duration(0);
  setTimeout(() => {
    pauseValues.lastT = pauseValues.currentT;
  }, 100);
};
.buttons {
  display: flex;
  flex-direction: row;
  margin-bottom: 4px;
}

.buttons-pause {
  padding: 2px;
  border: 1px solid black;
  border-radius: 3px;
  cursor: pointer;
}

.buttons-play {
  padding: 2px;
  border: 1px solid black;
  border-radius: 3px;
  margin-right: 4px;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<div class='buttons'>
  <div class="buttons-play" onclick="play()">play</div>
  <div class="buttons-pause" onclick="pause()">pause</div>
</div>

<div id='mediabar'></div>

Upvotes: 4

Related Questions