Prototyping swipe and drag gestures with Framer 3
Update late 2014: This post is now over a year old. Framer Studio works with CoffeeScript (this post was written for Javascript) and certain syntax has changed. Head over to the draggable example on the Framer website for updated code.
Here at Potluck we recently started using Framer.js to prototype and try out interaction ideas we come up with for our iOS app. Framer is a Javascript framework for prototyping interactions and animations for mobile and desktop apps. Itâs a great alternative to Quartz Composer as it has a less steep learning curve, and prototypes easily run both on desktop and the iPhone. We found Framer to be the perfect step up from Keynote to make realistic, interactive prototypes.
Since itâs based on Javascript, Framer is especially helpful for complex gestures that involve dragging and swiping; where elements move or fade gradually depending to swipe distance or when certain actions are triggered when the user swipes past a certain threshold.
Framer is a really young project, so examples and documentation are sparse. We thought weâd share what weâve learned with you so far.
Before we begin, you can find the final version of all the examples below on CodePen, if you donât want to type them out.
Activate on release
Letâs start with a simple example where dragging table cells reveals different actions.
Iâll first create a container view representing the usable iPhone screen. Notice I went with 1096px height, this is because weâll turn our prototype into a web app, which will have a system status bar.
iphone = new Layer({
x: 0,
y: 0,
width: 640,
height: 1096
});iphone.style.background = '#111'
iphone.style.overflow = 'hidden'
Letâs then create a cell container to house the visible part of the cell, and the actions will slide from off-screen.
cell = new Layer({
x: 0,
y: 0,
width: 940, /* including off-screen areas */
height: 100
});cellContent = new Layer({
x: 0,
y: 0,
width: 640,
height: 100
})cellContent.style.backgroundColor = '#eee'cell.superLayer = iphone;
cellContent.superLayer = cell;
At this point we should have:
Letâs now make the cells draggable. In the new version of Framer, it is as easy as:
cell.draggable.enabled = true
When you do this, youâll be able to drag the cell around, even though this isnât what we want to do yet.
We can use drag event handlers to implement more interesting gestures. There are three kinds of events that fire as you are dragging something. Events.DragStart fires once as you start dragging, and Events.DragEnd fires once when you let go. Events.DragMove fires continuously as you move your mouse or finger.
Draggable has a âspeedâ attribute which lets you tweak how the mouse movement will map to the view coordinates. We can set it to 0 to make the object not move at all in a specific axis.
cell.draggable.speedY = 0
Letâs also define what happens when you finish dragging, in this case, the element snaps back into place.
cell.on(Events.DragEnd, function() {
cell.animate({
properties: { x: 0 },
time: 0.2
});
});
Now we can add an off-screen element that reveals itself upon dragging. This will be a child view of the cell container (so it will move along with the contents of the cell) and weâll position it just outside the screen.
cellAction = new Layer({
x: 640, /* just outside the screen */
y: 0,
width: 300,
height: 100
})cellAction.superLayer = cell;
cellAction.style.backgroundColor = '#e00';
After adding the action bar, when you pull the cell to the left, you should see it reveal from the right side of the screen.
Now the sweet part: since the drag handler runs every time you move the bar just a little bit, we can make a direct mapping that changes the color of the action bar depending on how far we move the cell.
To make it easier to map one range of numbers (how far you moved the cell) to another range of numbers (color of the bar), letâs use a function I adapted from the awesome Processing framework.
map_range = function(value, low1, high1, low2, high2) {
if (value < low1) { return low2; }
else if (value > high1) { return high2; }
else return low2 + (high2 - low2) * (value - low1) / (high1 - low1);
}
Update: This function and many more are included in Shortcuts for Framer.
This function, given any value within the range [low1, high1], will produce its equivalent value in the range [low2, high2]. What we will do is map the x coordinate of the cell to the opacity of the action bar.
cell.on(Events.DragMove, function() {
cellAction.opacity = map_range(cell.x, -300, 0, 1, 0);
// corresponds to:
// pulling the cell 300 px left: 1 opacity
// not pulling the cell: 0 opacity
});
Now when you drag the cell, the color of the Action bar should change accordingly.
Obviously if youâve pulled the bar sufficiently, it should remain open when you let go. To achieve this, weâll change the DragEnd handler to check how far the you dragged, and if itâs over a certain amount, keep it open.
cell.on(Events.DragEnd, function() {
if(cell.x < -200) {
/* sufficiently dragged to snap action bar open */
cell.animate({
properties: { x: -300 },
time: 0.2
});
cellAction.animate({
properties: { opacity: 1 },
time: 0.2
});
} else {
/* snap closed */
cell.animate({
properties: { x: 0 },
time: 0.2
});
cellAction.animate({
properties: { opacity: 0 },
time: 0.2
});
}
});
Activate on threshold
So far we looked into having items snap open when you let go of the tap. What about actions that trigger without needing you to let go? A good example is the iOS6 pull to refresh: when you pull a sufficient amount, the refresh triggers even before you let go of the tap.
Letâs initiate a second cell that moves up and down:
cell2 = new Layer({
x: 0,
y: 300,
width: 640,
height: 100
});cell2.style.background = '#00e'cell2.superLayer = iphone;
cell2.draggable.enabled = true;
cell2.draggable.speedX = 0;
I want the cell to shrink and disappear if itâs moved down 200px. My first attempt was this:
cell2.on(Events.DragMove, function() {
if(cell2.y > 500) {
cell2.animate({
properties: {scale: 0.1},
time: 0.2
})
}
});
Why the erratic animation? As the DragMove event fires repeatedly as you keep dragging, we are trying to activate the animation many times, over and over. So letâs try to fire the animation only once after the condition is satisfied.
cell2.superView = iphone;
cell2.draggable.enabled = true;
cell2.draggable.speedX = 0;
cell2.animated = false;cell2.on(Events.DragMove, function() {
if(!cell2.animated && cell2.y > 500) {
cell2.animate({
properties: {scale: 0.1},
time: 1
})
cell2.animated = true
}
});
Youâll notice that the animation doesnât run anymore. Dragging and animating the item both work by changing its position, so dragging will override the animation.
We can solve this by animating a child element, inside a draggable container.
cell2 = new Layer({
x: 0,
y: 300,
width: 640,
height: 100
});cell2.superView = iphone;
cell2.draggable.enabled = true;
cell2.draggable.speedX = 0;
cell2.animated = false;cell2Content = new Layer({
x: 0,
y: 0,
width: 640,
height: 100
})
cell2Content.style.background = â#00eâ
cell2Content.superLayer = cell2cell2.on(Events.DragMove, function() {
if(!cell2.animated && cell2.y > 500) {
cell2Content.animate({
properties: {scale: 0.1},
time: 1
})
cell2.animated = true
}
});
This gives us the desired effect.
Testing on the iPhone
We found Dropbox public folders to be the easiest way to try out Framer prototypes in the iPhone. Simply upload all the files to a Public folder, open the public URL in Safari in iPhone and add the page to your home screen. When you use the PSD exporter, Framer comes with all the default settings to make the prototype look and feel like a real app.
If youâre making a lot of prototypes, weâve found it helpful to make a âdirectoryâ file containing links to each prototype, and create a home screen icon for that instead.
Wrapping up
Hope these examples help you get a better understanding of prototyping drag and swipe gestures in Framer.
You can find the code for all examples here on CodePen.
Weâd love your feedback on this post, and topics youâd like us to write about next. Reach out to @gem_ray on Twitter and let us know!