For More Realistic FramerJS Prototypes, Just Add Data

As a designer in today‘s world of digital products, prototyping is an essential tool in our workflow. Prototypes allow us to quickly test and validate design ideas, gather user feedback, and iterate towards an optimal solution before investing significant engineering resources.

Over the past decade, prototyping has evolved from simple paper sketches and clickable wireframes to fully interactive, high-fidelity experiences that closely mimic real products. Tools like InVision, Sketch, and Figma have become mainstays in the designer‘s toolkit, allowing us to quickly build out interfaces and animations.

However, there‘s one key ingredient still missing from many prototypes that‘s critical for creating a truly convincing experience: real data.

The Power of Real Data in Prototypes

Think about the last prototype you built or interacted with. Chances are, it was filled with idealized, "lorem ipsum" style placeholder content. The profile pictures were all perfectly smiling headshots, the blog posts were titled "Article Headline Goes Here", and the dashboard charts displayed unconvincingly perfect upwards trends.

We‘ve grown so accustomed to this fake data that we almost don‘t notice it anymore. But to the users interacting with these prototypes, it stands out like a sore thumb. It‘s an instant reminder that what they‘re looking at isn‘t "real".

Now imagine if your prototype pulled in actual, live data. The profiles would have the user‘s real name, photo, and bio. The blog would display the latest posts published seconds ago. The dashboard would be populated with the real revenue numbers and analytics for the company.

Suddenly, the illusion of interacting with a real product is maintained. The user can suspend disbelief and provide genuine reactions and feedback. The insights gathered are much more valuable, as they‘re based on realistic scenarios and data rather than fictional abstractions.

This is the power of incorporating real data into your prototypes. And with the advent of robust APIs and frameworks like FramerJS, it‘s never been more achievable.

Enter FramerJS

FramerJS is a powerful coding framework built specifically for creating interactive prototypes. With an extensive library of interface components, pre-built interactions and animations, and an intuitive coding language (CoffeeScript), FramerJS allows designers to build prototypes ranging from simple click-throughs to complex, data-driven experiences.

While FramerJS has gained popularity for its ability to create fluid micro-interactions and realistic motion, I believe its most powerful capability is integrating with APIs to pull in live data. By writing a few lines of code, you can fetch data from almost any web-based API and seamlessly display it in your prototype.

To illustrate this, let‘s walk through a real example of building a subway tracking prototype in FramerJS that pulls live data from Boston‘s MBTA API.

The Problem: Where‘s My Train?

Boston‘s subway system, known as the "T", is the oldest underground transit system in North America. While historic, it‘s not exactly known for punctuality. Delays and schedule changes are a daily occurrence, making it difficult for riders to plan their commutes.

While the MBTA does have an official app that shows train locations and predicted arrival times, it‘s not the most user-friendly or visually appealing. I saw an opportunity to design a better experience using FramerJS and the MBTA‘s real-time data API.

Accessing Live Location Data

The first step in building this prototype was getting access to the user‘s live location data. We need to know where they are in order to find nearby subway stations and show relevant train information.

Fortunately, modern web browsers make this fairly simple with the Geolocation API. We can use the getCurrentPosition() method to retrieve the user‘s current latitude and longitude coordinates:

getCurrentLocation = () ->
  if navigator.geolocation
    navigator.geolocation.getCurrentPosition(onLocationSuccess, onLocationError)
  else
    alert("Geolocation is not supported by this browser.")

onLocationSuccess = (position) ->
  latitude = position.coords.latitude
  longitude = position.coords.longitude
  # Do something with the location data

onLocationError = (error) ->
  alert("Error getting location: #{error.message}")

Here we first check if the browser supports geolocation. If so, we call getCurrentPosition() which will prompt the user to allow access to their location. If they accept, the onLocationSuccess callback is invoked with a position object containing the latitude and longitude.

One gotcha I ran into here: some browsers (looking at you, Chrome) don‘t allow geolocation requests from insecure origins. So if you‘re just loading your FramerJS prototype locally from your computer via the file:// protocol, the request will be blocked and the onLocationError callback will fire instead. The solution is to either host your prototype on a secure https:// domain or use a browser like Firefox that allows local geolocation (for development purposes only).

With the user‘s live location data in hand, we can now use it to find nearby subway stations.

Finding Nearby Stations

The MBTA provides a public-facing API with a plethora of endpoints for accessing data about routes, stops, schedules, and vehicle locations. The one we‘re interested in here is the /stops endpoint, which allows querying for stops based on location.

The API conveniently accepts latitude and longitude parameters and returns a list of stops sorted by distance to that location. Perfect for our use case! Here‘s how we can make the request in FramerJS using the Utils.domLoadDataAsync() method:

requestNearbyStops = (latitude, longitude) ->
  url = "https://api-v3.mbta.com/stops?filter[latitude]=#{latitude}&filter[longitude]=#{longitude}&sort=distance"

  Utils.domLoadDataAsync(url, (data) ->
    stops = data.data
    if stops.length > 0
      closestStop = stops[0]
      stopName = closestStop.attributes.name
      stopId = closestStop.id
      # Display the stop name and use the ID for further requests

    else
      alert("No nearby stops found.")

  , (error) ->
    alert("Error fetching stops: #{error}")
  )

We construct the URL for the API request, passing in the user‘s latitude and longitude coordinates as query parameters. We also specify sort=distance to return the stops in ascending order of distance from that location.

The Utils.domLoadDataAsync() method takes that URL and makes an asynchronous HTTP request. If the request succeeds, the callback function is invoked with the response data parsed as a JSON object.

We check if the data array contains any stops. If so, we grab the first one (which will be the closest) and extract its name and id attributes. The name is what we‘ll display in our prototype‘s interface, while the id will be used to fetch upcoming train arrival times for that specific stop.

If no stops are found (which should only happen if the user is quite far from any subway lines), we display an alert message. We also handle any errors that may occur during the API request.

With the nearest stop determined, we can now fetch and display the upcoming train arrival times.

Displaying Real-Time Train Predictions

To get the predicted arrival times for a specific stop, we can use the MBTA API‘s /predictions endpoint. This returns an array of predictions for each route serving that stop, with the estimated arrival time and other details.

Here‘s how we can request and parse that data in FramerJS:

requestPredictions = (stopId) ->
  url = "https://api-v3.mbta.com/predictions?filter[stop]=#{stopId}"

  Utils.domLoadDataAsync(url, (data) ->
    predictions = data.data
    if predictions.length > 0
      # Clear any existing predictions
      predictionList.html = ""

      for prediction in predictions
        # Extract relevant data from the prediction object
        route = prediction.relationships.route.data.id
        arrivalTime = prediction.attributes.arrival_time
        departureTime = prediction.attributes.departure_time
        direction = prediction.attributes.direction_id

        # Calculate minutes until arrival
        now = Date.now()
        minutesUntilArrival = Math.ceil((Date.parse(arrivalTime) - now) / 60000)

        # Append prediction to the list
        predictionList.html += "<li>#{route} #{direction}: #{minutesUntilArrival} min</li>"

      # Make the predictions visible
      predictionList.visible = true

    else
      predictionList.html = "<li>No predictions available.</li>"
      predictionList.visible = true

  , (error) ->
    alert("Error fetching predictions: #{error}")
  )

As before, we construct the URL for the /predictions endpoint, this time passing the stopId as a filter parameter to only return predictions for that specific stop.

In the success callback, we first check if the returned data array contains any predictions. If not, we display a "No predictions available" message.

If there are predictions, we iterate over each one and extract the relevant bits of data:

  • route – The ID of the route (e.g. "Red", "Green-C")
  • arrivalTime – The predicted arrival time as an ISO 8601 timestamp string
  • departureTime – The predicted departure time (if available)
  • direction – The direction of travel (0 for outbound, 1 for inbound)

To make the arrival time more human-friendly, we parse the ISO 8601 timestamp and compare it to the current time to calculate the minutes until arrival.

Finally, we format this data into an HTML string and append it to our predictions list element. The predictions are now displayed in the prototype‘s interface!

Putting it All Together

With the nearby stops and arrival predictions loading, the final step was to design an interface to display all this data in a user-friendly way.

Using FramerJS‘s Layer component, I created a full-screen container and added child layers for the header, stop name, predictions list, and map.

The map layer uses Mapbox to display an interactive map centered on the user‘s location, with the nearest subway stop plotted as an annotation. (Note: you‘ll need to sign up for a free Mapbox account and replace YOUR_ACCESS_TOKEN with your own token).

# Set up map layer
mapLayer = new Layer
  x: 0, y: 0, width: Screen.width, height: Screen.height
  image: "https://api.mapbox.com/styles/v1/mapbox/streets-v11/static/#{userLocation.longitude},#{userLocation.latitude},15,0,0/#{Screen.width}x#{Screen.height}@2x?access_token=YOUR_ACCESS_TOKEN"

# Add the stop as a map annotation  
stopAnnotationLayer = new Layer
  parent: mapLayer
  x: Align.center, y: Align.center
  width: 64, height: 64
  backgroundColor: "rgba(255,255,255,0.8)"
  borderRadius: 32
  html: "<img src=‘stop-icon.png‘/>"

For the header and predictions list, I used simple TextLayer components:

# Header
headerLayer = new TextLayer
  x: 0, y: 0, width: Screen.width, height: 128
  backgroundColor: "#333"
  color: "white"
  fontSize: 32
  text: "MBTA Subway Tracker"
  textAlign: "center"
  padding:
    top: 64

# Stop name  
stopNameLayer = new TextLayer
  x: 32, y: headerLayer.maxY + 32
  width: Screen.width
  color: "#333"
  fontSize: 24
  text: stopName or "Finding nearest stop..."

# Predictions list
predictionListLayer = new TextLayer
  parent: stopNameLayer
  y: stopNameLayer.maxY 
  width: Screen.width
  color: "#666"
  fontSize: 20
  visible: false
  html: ""  

With all the visual components in place, the final prototype interface looks like this:

[GIF of the final prototype in action]

And there you have it! A FramerJS prototype pulling in live MBTA data to display nearby subway stops and upcoming train arrivals.

You can play with the final prototype yourself here: [Live prototype link]

Key Takeaways

Building this seemingly simple prototype surfaced a number of valuable insights that are broadly applicable to designing with real data:

  1. Working with APIs is unpredictable – Unlike static design comps where you control the data, APIs are living, changing things. Fields might be renamed or removed. Endpoints can go down. Responses may be slow. Building in robust error handling is crucial.

  2. Real data is messy – Transit data is notoriously complex, with hundreds of routes, stops, directions, and service changes. I had to wade through dozens of API fields to find the few pieces of data actually relevant to my use case. Don‘t underestimate the data cleaning and parsing work involved.

  3. But real data is powerful – Once you have it flowing, using live API data instantly makes a prototype feel more genuine and engaging. Stakeholders and test users will interact with it differently when the information is real and relevant to them.

  4. FramerJS is a capable data manipulation tool – While known primarily as a motion design tool, FramerJS‘s JavaScript underpinnings give you a ton of flexibility to work with data. Constructing URLs, making HTTP requests, and parsing JSON all felt very familiar coming from a web development background.

  5. The prototype is a living, evolving thing – Since this prototype pulls live data every time it‘s loaded, it is always showing the latest information. If the MBTA API changes or has issues, that will be immediately reflected. A prototype built on real data must be actively maintained, just like a real product.

Ideas to Expand This Concept

While this prototype focuses on the narrow use case of subway arrival times, the same approach of combining FramerJS with live data could be applied to all sorts of other domains:

  • A weather app that displays the real forecast for the user‘s current location
  • A stock trading app that shows real-time market data and portfolio performance
  • A travel booking app that searches actual flight or hotel availability
  • A restaurant reservation app that checks table inventory from a real backend system
  • A fitness tracker that pulls in a user‘s real activity, heart rate, and sleep data

The possibilities are truly endless. Any data that is available via an API can be pulled into FramerJS and displayed in a realistic, interactive prototype.

Final Thoughts

As designers, we are perpetually seeking ways to make our creations feel more "real". To cross that uncanny valley from obvious fiction to convincing reality.

Incorporating live data can be a significant step towards that goal. Even if the interface is not fully polished or the interactions are not totally refined, the mere presence of genuine, relevant information can make a prototype feel infinitely more authentic and engaging.

FramerJS, with its powerful set of interface components and data fetching capabilities, is the ideal tool for this new frontier of data-driven prototyping. It allows us to bridge the gap between the static designs of yesterday and the dynamic products of tomorrow.

So go forth and build! Find an API relevant to your product‘s domain, pull in that data, and craft an experience around it. The more real, the better.

The age of lorem ipsum is over. Long live real data in design.

Similar Posts