iOS App Store card animation with Angular
As I was scrolling through the iOS App Store in an attempt to explore new apps and games, after opening an uncountable number of apps, I began to notice something that had previously escaped my attention: What a smooth and pleasant transition, I thought. After re-watching this animation, again and again, a question sparked in my mind: Could I create something similar in the browser?
To give you a bit more context, this is what the original animation looked like:
The card appears to have a piece of content behind and, after tapping, it smoothly expands to the edges.
Here is what I ended up having:
Before moving to the implementation details, let's take a brief look at the anatomy of the App Store's card component.
Card anatomy
When it comes to card anatomy, I spotted three main parts:
- Expandable container — the section which extends toward the edges of the screen.
- The section which hosts the card's content in the collapsed state — I'll refer to this section as the card cover.
- The section with initially hidden content that is shown after the card is expanded — I'll call it the card story.
(For brevity, I omitted some elements from the original component, such as the close button or the titles.)
Building the card's structure
I started by building a component with the same structure as the original one, which ended up looking like this:
Here is the Angular code for the card:
And the code for the AppComponent
with a container, to create a bit of contrast:
As you may have noticed, I've added theisExpanded
flag, so that I can store the card state. This flag will be handy for implementing the transition and hiding the content.
Alright, the elements are there, what's next?
Creating a trigger
Before moving to the animation itself, I created a trigger for the animation, called cardToggle
. Here is the code for it:
There are placeholders for both card states.
Height animation and the issues with it
I considered animating the height as a reasonable starting point. It goes from whatever the card's height is to full screen. To accomplish that, I updated the state to the following form:
Well, ok... The card's height gets expanded, but if we take a look at the original animation, the card moves to the top of the screen and it grows in width to take up the whole available space.
I'd spent a couple of hours trying to come up with a solution, searching through the Angular documentation for a clue.
Custom solution
Eventually, I gave up on making it work with Angular's transitions and created a custom solution. The idea is simple: I take the card's offsetTop
and offsetLeft
, and move the card to the top and left respectively, so it ends up in the top left corner.
The card makes use of the container's width to fill the entire view. To make it more organized, I came up with this directive, which would be attached to the card:
Attaching the coordinatesSpy directive
After moving the card's template to a separate file, I attached the directive and passed it to the toggleCard
method, alongside the cards div
element in the following way:
Inside the toggleCard
, I check if the card is expanded, and If so, I assign its default styles, so it returns to the original position. Otherwise, I take its offsets and the width of the container and assign them to the card's div
.
This is what it looks like in the browser:
After adding some transitions in the card styles, it ends up looking like this:
This looks pretty close already! I added the border-radius transition, and card initial position to the styles.
I stopped at this point and decided that it was time to move to the next part of the card. But before that, I had done a bit of style refactoring:
This separation makes future edits a bit easier to read and understand.
Working on the card cover
Since the cover needed some content, I'd came up with this code:
You might have noticed that I didn't cover the appCardCoverHeight
directive, but it just sets the right height of the container so the title can be positioned on the bottom of the cover.
I added a border-radius transition to the cover and a set of keyframes to animate the padding - this creates a similar title movement effect.
After replacing the gradient with an actual image, this is how it ended up looking:
I was already so happy with the animation! There is one more thing though - the card's story.
Building the card story
Doing it was pretty straightforward since most of the job was already done. I had to create a simple trigger with changing opacity.
Here is the code for the transition:
I added this transition to the div
with the .card-story
class from the card component's template.
I also had to update the cardToggleTrigger
to include the content animation:
Then, I separated the expanding and collapsing animations into two transitions:
expanded => collapsed
- I grouped the transitions here in such a way that the@cardContent
transition plays after a 460ms delay, which is 10ms after the card is expanded. This prevents the text's line-breaking effect when the container expands, which looks like this:
The achieved result looks as such:
Although it does not necessarily look like the original, it is the closest I managed to create in a reasonable amount of time.
Final words
While Angular does have a powerful animation system, it was still not solving the particular task I had, which proves that not everything can be done with standard tools.
Still, implementing this project allowed me to refine my knowledge of Angular animations - something I wouldn't have done during my day-to-day work.
If someone wants to try the code itself, the refactored version is available here.