Reputation: 421
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
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