Draw and Animate an SVG Circle in Framer

Henrique Gusso
4 min readMar 30, 2016

--

A few weeks ago I started prototyping circular on/off buttons, and it has been a very handy exercise in helping me learn a lot of what’s possible with Framer. I’m going to document my findings over some posts here, starting with a fan favorite: the circular progress bar.

Even though there are so many examples around, I always see the value in breaking down the essential parts of the code and coming out of it being sure about what every line means. So let’s go for it.

We start by defining the circle’s radius, stroke width, and summing them into the viewBox value, which we’ll also use as the circle size, even though it doesn’t have to be. I’ll get into what the viewBox actually is soon enough.

radius  = 150
stroke = 12
viewBox = (radius * 2) + stroke

Now we create the layer that will contain the circle, givings its dimensions and removing the default background color.

circle = new Layer
width: viewBox
height: viewBox
backgroundColor: ''

We’ll also set the length of the circle path, which will be essential when relating it to a percentage later. A closed path’s length is nothing more than its perimeter, and I’m sure everyone remembers that a circle’s perimeter is 2πr and no one had to look it up. Because we remember! Great, so we can write the JavaScript equivalent of this and move on.

circle.pathLength = 2 * Math.PI * radius

And now we draw the thing:

circle.html = """
<svg viewBox='-#{stroke/2} -#{stroke/2} #{viewBox} #{viewBox}'>
<circle fill='none' stroke='#fc245c'
stroke-width = '#{stroke}'
stroke-dasharray = '#{circle.pathLength}'
stroke-dashoffset = '0'
cx = '#{radius}'
cy = '#{radius}'
r = '#{radius}'>
</svg>"""

Here we defined the content of the layer’s raw html as an SVG. Quick rundown of possibly confusing attributes if you’re not familiar with SVG:

  • viewBox: The first 2 parameters define the starting point (x,y) and the last 2 define the width and height of the canvas where the path is drawn. Current browsers render SVG strokes centered on the path, so we need to recede half the stroke’s width on x and y to be able to see the full shape.
  • stroke-dasharray: By making our dash array the same length as the path’s, we’re creating a dash that fills the whole stroke. It looks just like a regular stroke for now, but we can change its position with the next attribute.
  • stroke-dashoffset: This controls the dash offset, which when animated looks a lot like a progress bar filling up.
  • cx, cy: Coordinates for the circle center.
  • r: Radius.

And finally, we’ll need a way to access the path itself, which is inside the html of the circle layer. But we should wait for the DOM to be fully rendered, so as to avoid the SVG not having been completely drawn.

Utils.domComplete ->
circle.path = document.querySelector('svg circle')

If you put all those pieces of code together, you should have a circle like this:

To animate it we need to gradually change the stroke’s offset so it seems like the circle is filling up. While stroke-dashoffset is an animatable property in CSS, we want to maintain all animation within Framer’s control, so we’re going to animate something else and monitor its changes to modify our offset.

For this we create a proxy layer, one whose sole purpose is to animate other elements. Then we watch a random attribute for changes, like x, and use Framer’s modulate (if you’re not familiar with modulate, read all about it) to translate its value to the proportional equivalent dash offset.

proxy = new Layerproxy.on 'change:x', ->
offset = Utils.modulate(@.x, [0, 100], [circle.pathLength, 0])
circle.path.setAttribute 'stroke-dashoffset', offset

So from now on, if our proxy moves from x:0 to x:100, the circle should go from a dash offset that’s the length of the path (meaning it’s fully offset, therefore away from visibility), to one that’s 0, which is our initial value. Let’s see what happens when we animate the x of our proxy to 100 (this should also only be called after we know the DOM is ready).

Utils.domComplete ->
proxy.animate
properties:
x: 100

Now we have a fully controlable circular stroke that we can animate to whatever percentage we want by controlling the proxy’s x value.

I added some tweaks to the code so that our final prototype looks a bit nicer:

  • Added a rounded stroke linecap on the circle.
  • Centered the circle layer.
  • Rotated the circle 90 degrees so that the animation starts at the top.
  • Set the dash offset’s starting value to the path’s length, so it starts unfilled.
  • Hid the proxy element.
  • Made the proxy x start at .1 so that the circle never fully disappears.
  • Added an animation loop.

Download this project: http://share.framerjs.com/8quev4una33g

Thanks to the very smart people over at the Framer JS Facebook Group for the insights and investigation. Here is some extra valuable reading about the subject:

--

--