On a Blind Date with React: Part 3 — Building a Radial Progress Indicator
In the series On a Blind Date with React, I’m taking you on a journey in building the online interactive documentary Blind Date with React. We’re coding a video player, overcoming animation challenges and incorporating user research feedback. We’ll transcode media files, hunt bugs and display translations in the browser along the way.
Today we’re going to create a Radial Progress Indicator: a circle filling up to show the progress of video playback. You can see it in action on the Blind Date Match screen (click on one of the foreigners or one of the natives to see the progress indicator fill up as the video plays). It is also used as a countdown between two videos, for example at the end of this video.
This is what we’ll be building:
We’ll be using:
- React, as this is a series on building an interactive documentary with React
- GreenSock, for animating
- SVG, as it suits our purposes perfectly
Laying the groundwork
The Radial Progress Indicator is a React component that renders an SVG
circle. We’ll pass in props for the width, the stroke width and the stroke color. With these we render a transparent circle with a stroke. The width will be the full width of the circle, including the stroke. Just like CSS
box-sizing: border-box. Also, because we only deal with perfect circles, the height will be equal to the width.
The stroke is drawn centered around the path it is applied to (source). In other words: the stroke is applied half inside and half outside the circle we’re drawing. We need to adjust the radius so that the stroke appears to be drawn on the outside of the circle.
This is what we got so far. This looks like the 100% progress case, doesn’t it?
Expressing progress on a circle
In order to show the progress on the SVG circle we need a way to influence how much of the stroke is drawn. Enter the
stroke-dasharray we can define the stroke to have gaps: just like
border-style: dashed in CSS. Unlike CSS’s
stroke-dasharray we can specify how large the gaps between the dashes should be.
stroke-dashoffset we can move the position of the dashes.
Here’s the crux: we need to set the
stroke-dasharray to equal the length of the stroke of the circle, so we’ll end up with one dash covering the entire circle stroke. Imagine this as a straight line. We then set
stroke-dashoffset to equal the length of the stroke of the circle, in effect positioning the dash to start at the end of the line.
How do we calculate the length of the stroke of a circle again? No worries, I had to look it up too. The length of the stroke of a circle is called the circumference and we can calculate it using the radius and Pi:
circumference = 2 * radius * Pi
We set both
stroke-dashoffset to the circumference. Then we can decrease the
stroke-dashoffset from this initial value to
0 to express progress on the line:
A little but important detail: we’ve rotated the circle by 270 degrees in order to let the stroke start at the top and increase clockwise.
Animating progress over time
Now that we’ve laid the groundwork and are able to express the progress on a circle, it’s time to animate it!
We’re using GreenSock because it allows us to pause and resume the animation playback later on. For starters, we’ll animate the progress when the
RadialProgressIndicator component mounts.
In order to animate progress over time we need to know the desired duration, so we’ll pass in a
duration prop. This is the time the progress is animated from 0% to 100%.
First, we add a
ref to the
circle, so we can pass it to GreenSock for animation. Then we create a new
TimelineLite and add a tween (animation) to it, telling it to animate
stroke-dashoffset from its initial value to
0 during the specified
duration. Also, we want to animate from o% to 100% in a linear fashion, so we define a linear easing.
Now when we render a
RadialProgressIndicator it animates:
In the Match screen we want to be able to play and pause the video of the selected person. Of course, the progress indicator reflect this state as well. After all, there is no fun in a progress indicator that keeps animating progress when the video is paused!
In good React fashion, we first add a prop to our
paused to indicate whether or not the progress is paused. We‘ll use this
paused prop to either play or pause the animation timeline.
And that’s all there is to it. Here you can see our
RadialProgressIndicator in full glory:
Next week, we’ll turn the page towards the Video screen. We’ll take a deep dive into the CSS
object-fit property to make sure our videos display without any letterboxing (black borders at the top/bottom or left/right).
In the mean time, be sure to visit the site, match a foreigner and a native and enjoy some of their conversations!