How to Create an App Prototype Using CSS and JavaScript
Animation really is one of the best features to hit CSS in a long time. After all, as we’ve come to realise, motion can enhance user experience and encourage actions where static contexts fall short. In this tutorial we’ll build a prototype for an app using CSS animations and a touch of JavaScript.
What We’re Creating
For this exercise we’ll develop a prototype for an app that lets subscribers follow one another. Initially we’ll load in a list of users with their names below each corresponding avatar. These avatars, when clicked, will trigger a modal containing a “follow” button and additional information pertaining to each individual user.
For demo purposes we’ll load in 30 users using a free service called Random User Generator. Their API not only provides random images of users, but it also provides random data such as names, addresses, emails and much more.
1. Set the Stage
I’ve discussed options previously for building prototype concepts, but for this article we’ll construct a prototype with real code. For this task we’ll be using Marvel Devices; an open source library that contains eleven pure CSS mobile devices to showcase prototypes typically made with Marvel App.



To get started include devices.min.css at the top of your document, go back to the demo and select the combination you want, then copy and paste the generated HTML into your project.
2. Load the Users
With the CSS obtained for our demo’s device (Nexus 5) it’s time to get to work on loading in our application’s user feed.
1 |
<div class="marvel-device nexus5"> |
2 |
<div class="top-bar"></div> |
3 |
<div class="sleep"></div> |
4 |
<div class="volume"></div> |
5 |
<div class="camera"></div> |
6 |
<div class="screen"> |
7 |
<h3 class="title">Results</h3> |
8 |
<ul class="users"></ul> |
9 |
</div>
|
10 |
</div>
|
The users will be loaded as list items and injected into the .users
unordered list. This is the initial foundation, but we need to use JavaScript to communicate with the random user API in order to insert these list items. To make this request we’ll use plain, vanilla JavaScript ES6 style.
1 |
let request = new XMLHttpRequest(); |
The request
is the variable that will contain our AJAX request, but it requires a URL to connect with.
1 |
request.open('GET', 'https://randomuser.me/api/?results=30', true); |
The open
method will retrieve the API data from the URL and parameters we set. There are plenty of other parameters to use, but for this demo we’ll just retrieve 30 results. Now that we have our URL in place and the request sent, let’s retrieve that data.
1 |
// Utility for DOM selection
|
2 |
function select(s) { |
3 |
return document.querySelector(s); |
4 |
}
|
5 |
|
6 |
// Store for referencing
|
7 |
const users = select('.users'), |
8 |
|
9 |
// AJAX Request
|
10 |
request.onload = function() { |
11 |
if (request.status >= 200 && request.status < 400) { |
12 |
|
13 |
let data = JSON.parse(request.responseText), |
14 |
l = data.results.length; |
15 |
|
16 |
for(var i = 0; i < l; i++) { |
17 |
// data results
|
18 |
console.log(data.results[i]); |
19 |
}
|
20 |
|
21 |
} else { |
22 |
alert('We reached our target server, but there was an error'); |
23 |
}
|
24 |
};
|
25 |
|
26 |
request.onerror = function() { |
27 |
alert('There was a connection error of some sort') |
28 |
};
|
29 |
|
30 |
// Send Ajax call
|
31 |
request.send(); |
The entire request is now in place. Opening up the console you’ll see all the results of the provided data listed. This is a good start, but we definitely need to do more than just log the output to the console.
3. Inject User Data
For this next part, we’ll inject markup using the data we have available from the random user API.
1 |
users.insertAdjacentHTML('beforeend', '<li><img src="'+ |
2 |
data.results[i].picture.medium +'" data-pic="'+ |
3 |
data.results[i].picture.large +'" data-name="'+ |
4 |
data.results[i].name.first + ' ' + |
5 |
data.results[i].name.last + '" data-email="'+ |
6 |
data.results[i].email +'"><span class="user-name">'+ |
7 |
data.results[i].name.first +'</span></li>'); |
The lines of code above will be placed in the loop created prior that was logging data to the console. As mentioned, we have quite a few options with regards to data attached to the user objects, such as first name, email and avatar sizes. The medium image size will be used for initial display, whereas the large size will be used for our modal that is triggered when the thumbnail avatar is clicked. All of these pieces of information will be stored inside data attributes so we can retrieve them as needed.
4. Detect Avatar Position
We are going to build another component for this demo; a modal that’s triggered from the point of execution (i.e. clicking on an avatar). We’ll need our dear friend math to actually determine where the modal will expand from.
1 |
var transOriginNames = { |
2 |
webkitTransformOrigin : 'webkitTransformOrigin', |
3 |
MozTransformOrigin : 'MozTransformOrigin', |
4 |
msTransformOrigin : 'msTransformOrigin', |
5 |
transformOrigin : 'transformOrigin' |
6 |
};
|
7 |
|
8 |
users.addEventListener('click', function(e) { |
9 |
let target = e.target, |
10 |
target_coords = target.getBoundingClientRect(); |
11 |
|
12 |
if(target.nodeName === 'IMG') { |
13 |
for(var name in transOriginNames) { |
14 |
modal.style[name] = (target.offsetLeft + (target_coords.width/2)) +'px ' + ((target.offsetTop + (target_coords.height/2)) - screen_scroll.scrollTop) + 'px'; |
15 |
}
|
16 |
}
|
17 |
});
|
To achieve this expansion of the modal from the point of execution we need to make sure our transform-origin
is correct in order to have the modal scale properly. I’m using an object
to hold all our transform-origin
prefixes since we’re using JavaScript to set them and we also need to ensure all bases are covered for browsers that don’t support the standard.
Take note of the math required to determine the transform-origin
values.
1 |
modal.style[name] = (target.offsetLeft + (target_coords.width/2)) +'px ' + ((target.offsetTop + (target_coords.height/2)) - screen_scroll.scrollTop) + 'px'; |



The diagram above explains visually how the math is being calculated. In order to determine the correct location we use offsetLeft
and offsetTop
plus half the width
and height
respectively to find the exact center of the avatar. scrollTop
is also very important because 30 users will overflow the boundary of the device screen and offsetTop
will need to have this value subtracted to determine the proper distance from the top of the screen. All of the values in the diagram above are provided by our friend getBoundingClientRect
.



Logging the target_coords
to the console you can see all the dimensions and values we require to make a proper assumption. These values with regards to the width
and height
will always change depending on the avatar’s position within the device screen.
5. Animate the Modal
With the avatar coordinates prepped and ready to receive our click event, it’s time to add the modal motion that will display the user’s profile.
1 |
.modal { |
2 |
transform: scale(0) translateZ(0); |
3 |
transition-duration: 320ms; |
4 |
transition-timing-function: cubic-bezier(.4, 0, .2, 1); |
5 |
transition-property: transform, opacity; |
6 |
opacity: 0; |
7 |
}
|
The above code is slimmed down from the live demo to showcase the exact motion properties we’re going to be using. We are initially hiding using opacity
and scale
.
Now it’s time to define the CSS properties that will handle the active state for the modal’s motion.
1 |
.screen.active .modal { |
2 |
transform: scale(1) translateZ(0); |
3 |
opacity: 1; |
4 |
}
|
Using JavaScript we’ll toggle an active
class on screen
. In the lines above we’re reversing what we set prior. This is how we create the expanding effect of the modal. It’s a very Google Material Design-esque style that helps from disrupting cognitive behavior and quickly responds to user input precisely making the motion responsive. This style also confirms user input by immediately expanding outward from the point of contact.
6. Give Avatars Motion
During load we’ll create a scaling effect making the sequence less static and more responsive. Each avatar will scale in at a different interval than the next and for that we’ll use CSS.
1 |
@keyframes fade-in { |
2 |
from { opacity: 0; } |
3 |
to { opacity: 1; } |
4 |
}
|
5 |
|
6 |
@keyframes expand-out { |
7 |
from { transform: scale(0); } |
8 |
to { transform: scale(1); } |
9 |
}
|
10 |
|
11 |
.users li { |
12 |
opacity: 0; |
13 |
}
|
Initially we’ll hide the users and then toggle a show
class once the AJAX request is good to go. I’ve also included our keyframes that will morph our properties into the correct values. We’ll use these keyframes in the following code snippet that sets the motion into play.
1 |
$user-count: 30; |
2 |
$duration: 200ms; |
3 |
$stagger_delay: 0.0125s; |
4 |
$easing: cubic-bezier(.66, .14, .83, .67); |
5 |
|
6 |
.users.show { |
7 |
li { |
8 |
animation-duration: $duration; |
9 |
animation-name: fade-in; |
10 |
animation-fill-mode: both; |
11 |
animation-timing-function: $easing; |
12 |
opacity: 1; |
13 |
|
14 |
img { |
15 |
animation-duration: $duration; |
16 |
animation-name: expand-out; |
17 |
animation-fill-mode: both; |
18 |
animation-timing-function: $easing; |
19 |
}
|
20 |
|
21 |
@for $i from 1 through $user-count { |
22 |
&:nth-of-type(#{$i}) { |
23 |
animation-delay: ($stagger_delay * $i); |
24 |
img { |
25 |
animation-delay: ($stagger_delay * $i); |
26 |
}
|
27 |
}
|
28 |
}
|
29 |
}
|
30 |
}
|
To help with the math I’m using Sass loops and variables to hold our user count that is also tied with our JS results from the random user API call. The loop is the key to the whole puzzle as it will loop over our user count and add the stagger value we defined in a variable.



Above is our result with the final avatar animation sequence. Remember we’re only using keyframe animations in CSS and using JavaScript to toggle the show
class once the avatars are retrieved from the random user API.
Closing Thoughts
Make sure to look over the live demo as there are also additional elements that add benefits, such as an animated loader that will display while users are loading and remove itself once avatars are loaded.
Thanks to Shaw for his insight and this Dribbble shot used for inspiration. As always leave any comments below for further discussion, and give the demo some hearts on CodePen if you want to see more like it!