Ring-a-Ding-Ding — How I created a custom layer class in Framer and learnt to draw SVGs with code.

Giles Perry
Prototypr
Published in
10 min readOct 7, 2016

--

In the last year or so, there’s been a massive explosion of prototyping tools for UX designers. Of these, Framer is arguably the most powerful and versatile. But Framer prototypes are created in code and, for a designer, getting started can feel intimidating.

I recently set myself the challenge of tackling Framer. A few weeks in, and I’ve written my first module, created a custom layer class and learnt how to manipulate SVGs. I want to walk you through the module code, and share some of the things I’ve figured out along the way.

I started my journey with no more than a basic understanding of the fundamentals of coding. Once you’ve read through the Programming page on the Framer website you’ll know at least as much as I did.

If you’re new to Framer and want to learn, I recommend starting with a small part of a real-world example — recreate something that already exists or choose something you’re designing currently. You’ll be driven forward by the desire to solve your own problems, and it’s easier to learn new concepts once you move beyond cutting and pasting code examples. My first prototype looked at parts of the new BleepBleeps app. Take a look.

My knowledge of Framer is only a few weeks old. I’ve never written CoffeeScript before, the coding language used in Framer, or JavaScript for that matter. I’ll explain things as I understand them. If there’s any here that’s incorrect, or if there’s a better way to do something, please let me know. Most of what follows would not exist if I hadn’t discovered John Marstall’s article ‘Developing a Framer Module’. Many thanks John!

The module I’ve written allows you to create circular SVG rings with properties that can be animated in the same way you would animate any other layer in Framer.

If that’s all you want then you probably don’t need this module. As an easier approach would be to create a square transparent layer with appropriate border and border radius settings.

layerA = new Layer
width: 300
height: 300
borderRadius: 150
borderWidth: 50
borderColor: “blue”
backgroundColor: “transparent”

But once you’ve got your head round how to manipulate an SVG circle you can apply the ideas to something more complex.

The code above shows how easy it is to create a layer in Framer, and define its properties. It would be nice to be able to create a new ring shape in the same way:

ringA = new Ring
outerRadius: 200
innerRadius: 50

That’s where classes come in. The class defines a ring’s properties and how it behaves. We want our ring to be a special kind of layer so we need to extend the Layer class:

class Ring extends Layer

The next step is to write a constructor function, that will be called automatically when a new instance of your class is created. Like any function, you can specify arguments that will be passed to the function. The layer class expects a single argument which is an object defining the layer options, such as height and width etc.

constructor: (options) ->

In our case, we need to add two things. First we’re going to prefix the options argument with @. @ is shorthand in CoffeeScript for this.

this is a tricky idea to get your head around if you are new to coding. Its meaning can vary depending on the context in which it’s used. You can read more about this here.

In the context of the class constructor, this means the new ring instance we are creating. If our new ring is ringA, then this.options means ringA.options. this.options, @options, and @.options all mean the same thing.

When we pass information to options, we are creating a variable of that name that can only be access inside the constructor function. If we write @options instead, a property is added to the class instance, allowing us to access the options elsewhere in the class.

Also, we’ll add ={} after @options. {} is an empty object. Writing @options={} makes {} the default value if no argument is passed.

So there we have it:

constructor: (@options={}) ->

It’s worth noting that the Layer class already has an options property. Since a ring is an extension of the Layer class, by using @options in our class, we are manipulating a property used by the parent class, and there is potential for conflict. Some people recommend keeping any custom properties separate, by typing @ringOptions where I have typed @options. My code seems to work fine, so I will leave it as it is.

Writing a Constructor function

So, what does our constructor need to do? Before I explain the rest of the code, I should say more about how I want a ring to behave.

The ring will be an SVG element in the layer’s HTML. Its shape is defined by outerRadius and innerRadius properties. All other options match those of a standard layer.

I want the size of the layer to be based on the outerRadius so height and width are redundant, although they still exist as layer properties. The class includes code to set the layer’s height and width to twice the outerRadius when a ring is created, or if the outerRadius changes. Direct height and width manipulation is not handled and is likely to cause unpredictable results, so don’t specify or make changes to height or width when using the Ring class.

Layers already have a color option which sets the colour of HTML text. I’m going to use that for the SVG fill colour. Remember, color is white by default. If your screen background is also white you’re not going to have much to look at!

Finally, I want to be able to animate both outer and inner radii, with the ring staying centred if the outer radius changes. The class needs to handle this behaviour too.

Inside the constructor we need to

  • Set default option values
  • Create the new layer
  • Overwrite the height and width to match the radius
  • Create the SVG accordingly

Steps one to three look like this:

class Ring extends Layer
constructor: (@options={}) ->

# Set default radius values and background color
@options.outerRadius ?= 100
@options.innerRadius ?= @options.outerRadius / 2
@options.backgroundColor ?= “transparent”
super(@options)

# Overwrite height and width
@width = @height = @options.outerRadius * 2

?= is the existential operator. As it is used here, it means “if the option exists fine, otherwise set it to the value that follows”.

super is used to call the function in the parent class corresponding to the current function. Ring is a subclass of the Layer class, so super(@options) will call the Layer constructor, creating the new layer using the specified options.

A quick intro to SVG syntax

I’m not going to explain SVG syntax in great detail, sorry! The key points are these:

The visible area of an SVG is called the viewport. Its size can be set using height and width attributes. If you don’t specify height and width, then the SVG will scale to fit its parent layer.

The viewbox attribute can be used to redefine the coordinate system inside the viewport. I’ve set my SVG to be 200 units across and 200 units down (to match my default layer size). Any elements with absolute units will be sized relative to this scale. I could just go ahead and use pixels but this way the diameter is set by sizing the layer, and the inner circle’s relative radius is the only other variable.

<defs> allows you to define an element and use it within another element by referencing its ID attribute. I’m defining the inner circle and using it to mask out a hole inside the outer circle. It’s important to make the element’s ID unique. Otherwise changes to one ring will affect all instances! I use @uniqueID to set a unique ID property.

Here’s the code:

# Create the SVG
@uniqueID = Math.random().toString(36).substr(2)
innerRadiusValue = @options.innerRadius / @options.outerRadius * 100 @html =
“””
<svg viewBox=”0 0 200 200">
<defs>
<mask id=”mask_#{@uniqueID}”>
<rect width=”100%” height=”100%” fill=”white”/>
<circle id = “inner_#{@uniqueID}”
r=”#{innerRadiusValue}”
cx=”50%”
cy=”50%”
fill=”black”/>
</mask>
</defs>
<circle
r=”50%”
cx=”50%”
cy=”50%”
mask=”url(#mask_#{@uniqueID})”
fill=”currentColor”/>
</svg>”””

How to select an SVG when you need to modify it

We want to be able to change the radius of our SVG’s inner circle. So, after the SVG is created, we need to find the relevant element and add it to an instance property, allowing us to access and modify its radius attribute later.

There are a couple of HTML DOM methods that will help us: querySelector() allows us to find an element and setAttribute() lets us change its attributes. But watch out! There are a couple of gotchas:

First, we can’t use querySelector() until after the DOM has fully loaded. To get round this, we use the Utils.domComplete function. Our code ought to look something like this:

Utils.domComplete ->
@innerCircle = this.querySelector(“#innerCircle_#{@uniqueID}”)

In other words, wait for the DOM to fully load, then select the element in this layer instance based on a unique ID, and save it in an instance property called innnerCircle.

Here’s where the second issue comes in:

this refers to something different when used inside Utils.domComplete. It refers to the window object not the layer instance.

To get round this, we need to store the layer instance itself in a variable, so we can access it from inside Utils.domComplete via the variable:

self = @
Utils.domComplete ->
self.innerCircle = self.querySelector(“#innerCircle_#{self.uniqueID}”)

That’s better! The constructor is complete. Next up, how do we handle changes to our custom properties?

Getting and setting custom class properties

It might look like we’re done. We can create a new ring and access any properties that we’ve defined in the constructor:

ringA = new Ring
print ringA.uniqueID

This will draw a ring and print its uniqueID (“764sp5or47i” or something like it). If that’s all we want, the constructor code is sufficient.

But when we try

print ringA.outerRadius

… it comes back undefined!

The difference is that we haven’t actually set the outerRadius property anywhere in the constructor. All we’ve done is store its value in the options property. We could use:

print ringA.options.outerRadius

but it would be much better to have innerRadius and outerRadius as properties. One solution is to add the following in the constructor:

@outerRadius = @options.outerRadius
@innerRadius = @options.innerRadius

But that doesn’t help us if we want to change their values later? And how do we update the SVG? We need to add code to the class that defines what happens if we get or set these properties: a getter and setter. I haven’t found much documentation on how to write a getter and setter, other than in John Marstall’s article, but trust me it works. The bones of the code will look like this:

@define ‘innerRadius’,
get: ->
@options.innerRadius
set: (value) ->
@options.innerRadius = value

@define appears to be a function in the Layer class. You pass it two parameters. The name of the property you want to define and an object containing get and set functions.

What we have so far is simple. get returns the corresponding property in the options object. Similarly, in the set function, we pass the value we want to set and use it to update the options object.

@define ‘innerRadius’,
get: ->
@options.innerRadius
set: (value) ->
@options.innerRadius = value
if @innerCircle isnt undefined
@.innerCircle.setAttribute ‘r’, value / @options.outerRadius * 100

It’s also where we run into our final gotcha. The first time the set function is called, @innerCircle (which holds our SVG element) hasn’t been defined yet. The element doesn’t exist. It’s set much later once the DOM is fully loaded. The obvious solution is to check to see if it’s undefined first.

Other than code to adjust the layer’s height and width, and the x and y properties (to keep the ring centred when its radius changes), outerRadius is treated in exactly the same way.

Animation

Now that the custom ring properties have getter and setter functions, we are able to animate them as we do other properties. For example:

ringA = new Ring
outerRadius: 80
innerRadius: 50

expandRingA = new Animation
layer: ringA
properties: outerRadius: 150
curve: ‘spring(50,24,0)’
ringA.onTap (event, layer) ->
expandRingA.start()

Making a module

With the Ring class coded and working, all that’s left is to turn it into a module.

Modules allow you to store a piece of code in a separate text file and then include it in as many projects as you like.

To make this work for the Ring class, we take all of our class’s code and save it as a text file in the /modules folder of a new prototype, using .coffee as the file extension. Mine is called ringModule.coffee.

The prototype can use anything in the module file if it’s prefixed with exports. We want to use the Ring class, so we update the class’s first line to:

class exports.Ring extends Layer

Finally, we need a line of code to include the module in the new prototype.

{Ring} = require “ringModule”

Hope you’ve found the article helpful. I’ve hacked together a prototype where you can see it all working. Download it and take a look at the finished module:

A few acknowledgements

I spent a lot of time reading two excellent articles:

Here are a few links I also found very useful:

https://framerjs.com/getstarted/programming/

http://arcturo.github.io/library/coffeescript/03_classes.html

http://tutorials.jenkov.com/svg/svg-viewport-view-box.html

Finally, I’m very grateful for all the help I’ve had from the Framer community. Koen Bok, Dan Bartley, Jordan Robert Dobson, Sigurd Mannsaker, Øyvind Nordbø. Many thanks!

Was this article helpful?

If you liked this article, please tap the little 💚 icon at the bottom of the page to help spread the word.

If you use it to make something nice in Framer, or you have any thoughts, let me know by leaving a comment. Thanks!

A bit about me

My name’s Giles Perry. I’m a freelance UX Designer based in London, UK. I’ve been creating digital experiences for the last 17 years. Check out my website or find me on LinkedIn, and thank you for reading.

--

--