Swipe places
In this example we will implement the card swipe control made famous by Tinder. Swipe to the right if you’ve visited a city, to the left if you have not.
To achieve the desired effect, we will use PointAttractor
and Draggable
to give elements the ability to respond to dragging gestures. PointAttractor
will create a zone that the elements are attracted to. We will use three of these to create the Tinder sorting effect.
Let’s start by looking at the JavaScript:
<JavaScript>
var Observable = require("FuseJS/Observable");
var resetting = Observable(false);
function City(name, imageKey, country, visitors, nonVisitors)
{
this.name = name;
this.imageKey = imageKey;
this.country = country;
this.visitors = Observable(visitors);
this.nonVisitors = Observable(nonVisitors);
this.degrees = -4 + (8 * Math.random());
}
function reset(x) {
resetting.value = true;
setTimeout(backToNormal, 300);
}
function backToNormal() {
resetting.value = false;
}
module.exports = {
cities : [
new City("Oslo", "Assets/Cities/Oslo.jpg", "NORWAY", 3127, 3943),
new City("Paris", "Assets/Cities/Paris.jpg", "FRANCE", 10250, 400),
new City("San Francisco", "Assets/Cities/San Francisco.jpg", "USA", 6700, 5421),
new City("Seoul", "Assets/Cities/Seoul.jpg", "KOREA", 5713, 4702),
new City("Tokyo", "Assets/Cities/Tokyo.jpg", "JAPAN", 4512, 657)
],
visited: function(x)
{
debug_log("Visited " + x.data.name);
},
notVisited: function(x)
{
debug_log("Not visited " + x.data.name);
},
addVisitor: function(x)
{
x.data.visitors.value = x.data.visitors.value + 1;
},
removeVisitor: function(x)
{
x.data.visitors.value = x.data.visitors.value - 1;
},
addNonVisitor: function(x)
{
x.data.nonVisitors.value = x.data.nonVisitors.value + 1;
},
removeNonVisitor: function(x) {
x.data.nonVisitors.value = x.data.nonVisitors.value - 1;
},
reset: reset,
resetting: resetting
};
reset();
</JavaScript>
Points of interest:
- We create a bunch of cities that are to be swiped left or right
- As the number of visitors will update on the cards, we make them
Observable
- We also begin implementing a
reset
function to allow us to swipe through the stack multiple times. This is implemented using anObservable
boolean which indicates whether the stack is currently in the process of resetting itself. We’ll look at how to implement this in UX later - The resetting
Observable
is flipped on a timer usingsetTimeout
Each
We create a bunch of panels to hold the card definitions. These are rotated according to the random value set when the city is initialized:
<Each Items="{cities}">
<Panel Margin="0" Height="400" Width="293">
<Rotation Degrees="{degrees}" />
This gives the stack a more organic look.
Point attractors
Outside the Each
-loop, we setup the point attractors:
<PointAttractor ux:Name="centerAttractor" Radius="800" Strength="250" />
<PointAttractor ux:Name="notVisitedAttractor" Offset="-400,0,0" Radius="380" Strength="600" />
<PointAttractor ux:Name="visitedAttractor" Offset="400,0,0" Radius="380" Strength="600" />
The center attractor has its parameters governed by the resetting Observable
:
<WhileTrue Value="{resetting}">
<Change centerAttractor.Radius="8000" />
<Change centerAttractor.Strength="10000" />
</WhileTrue>
When the stack is resetting, the attractor becomes bigger and stronger. When it is not resetting, it gives a gentle attraction towards the center of the screen, covering a large area.
Overlays
To give some visual indication that the city has been visited or not visited, we add some image overlays over the city picture with Opacity
of 0.
<Image ux:Name="visitedOverlay" StretchMode="UniformToFill" File="Assets/Emblems/VisitedOverlay.png" Opacity="0" Dock="Fill" />
<Image ux:Name="notVisitedOverlay" StretchMode="UniformToFill" File="Assets/Emblems/NotVisitedOverlay.png" Opacity="0" Dock="Fill" />
<Rectangle Dock="Fill">
<ImageFill File="{imageKey}" StretchMode="UniformToFill" WrapMode="ClampToEdge" />
</Rectangle>
The Opacity
can then be increased using an InForceFieldAnimation
:
<InForceFieldAnimation ForceField="visitedAttractor" From="0.1" To="0.3">
<Change visited.Opacity="1" Duration="1" />
<Change visitedOverlay.Opacity="1" Duration="1" />
</InForceFieldAnimation>
Rotation
We can also add a slight rotation to the card as it is being swiped, also using an InForceFieldAnimation
:
<InForceFieldAnimation ForceField="visitedAttractor">
<Rotate Degrees="-8" />
</InForceFieldAnimation>
Visitor updating
To update the visitor count correctly as the card is swiped, we use EnteredForceField
and ExitedForceField
<EnteredForceField ForceField="visitedAttractor" Threshold="0.05" Handler="{addVisitor}" />
<ExitedForceField ForceField="visitedAttractor" Threshold="0.05" Handler="{removeVisitor}" />
<EnteredForceField ForceField="notVisitedAttractor" Threshold="0.05" Handler="{addNonVisitor}" />
<ExitedForceField ForceField="notVisitedAttractor" Threshold="0.05" Handler="{removeNonVisitor}" />
Draggable behavior
To make the cards draggable, we add the Draggable
behavior:
<Draggable />
We can also animate the city in response to being dragged:
<WhileDragging>
<BringToFront />
<Scale Factor="1.1" Duration="0.5" Easing="BackOut" />
<Change shadow.Distance="20" Duration="0.5" Easing="BackOut" />
<Change shadow.Size="20" Duration="0.5" Easing="BackOut" />
</WhileDragging>
Reset button
Finally, we hook up the reset icon to call the reset JavaScript function:
<Panel>
<Image Clicked="{reset}" Height="64" Width="64" File="Assets/Icons/Refresh.png" />
</Panel>