Creating an Apple tvOS Style App Icon
I got a new Apple TV for my birthday this year, after our old one kept annoyingly crashing right in the middle of the third season of Orange is the New Black. It’s got an all new UI and a touch sensitive remote.
In typical Apple fashion, apps are displayed in a grid, but unlike iOS, there are some very cool subtle effects as you navigate through the app grid. I was pretty sure that these effects were possible in CSS and supported in all modern browsers these days, so I gave it a go.
The HTML structure is really simple. The
.app-icon element is a container for all the elements (which in this case are layers) that build up the app icon. This element has the
perspective CSS property applied to it, which will create the 3D effect. It’s important to understand that this property affects the children of the element and not the element itself. The layers that build up the icon is in another container with the class of
The Sass (CSS)
I created the styles for the app icon by using a CSS pre-processor called Sass. Since I started using it a couple of years back, I cannot imagine ever using a project without it. If you haven’t checked it out yet, I recommend you do!
.app-icon element is pretty standard stuff. The
display is set as
inline-block so that when multiple icons are displayed on screen, they are rendered nicely as a grid. The most important rule here is
perspective. This puts the element in a 3D space. You can see this effect when rotating the element on the X or Y axis. The greater the value of this property, the further the user is from the Z plane and the less pronounced the effect is. I experimented with this property a bit to get the effect right.
.layers element is the container of all the elements that make up the app icon. There are a few rules here that are key to creating the tvOS icon style. First off, the
position: relative rule allows us to position the different layers of the icon accurately. The parallax effect requires us to sometimes have to make the layer element larger than its parent (explained later) in order to create a safe zone. In order for the layers to not overflow the boundaries of the container, we add the rule
overflow: hidden. The
border-radius has a small value, just to modestly enhance the look of the icon. The two most crucial rules that create the 3D effect is the
box-shadow and the
box-shadow creates the effect that this icon is lifted from the page. The
transform: translateZ(-3rem) rule pushes the icon backwards slightly. This seems to change the origin of where the icon is rotated from, which is slightly “in front” of the icon. This gives a tilting effect that is more in line with that of tvOSs.
.light-effect element should be one of the standard elements within
.layers. This provides the lighting effect that is present on all the tvOSs icons. The “shine” that appears on the app icon is simply a radial gradient background going from a slightly transparent white to fully transparent. When looking at the example in Apple’s Human Interface guidelines, you can see that the shine effect is actually quite big. So the
width of this element is actually bigger than the icon itself (the
overflow: hidden rule of its parent element ensures that the shine isn’t visible outside the app icon boundaries). The effect doesn’t appear until the top of the icon is tilted away, so the position of the shine (by default) is
top: -30rem placing it above the icon. The
z-index value ensures that the shine is always in front of the other layers so that it is always visible.
Each element that makes up the app icon has a class of
layer, which has a single rule of being absolutely positioned, so that it can be placed relative to the
.layers container. Generally, the
.layer elements will probably also have a second class, which will specify additional rules for the layer.
In this example, the first layer of the icon is the background. You may notice that there is padding on this element when the height and width of the element are already 100% of it parent, meaning that its size is greater than the element it is contained in. This is something that Apple calls the safe zone. During parallax, the elements inside the icon will move with the motion of the icon, so an extra bit padding will ensure that the outer edges of the background are not visible inside the icon when the element is moved.
The app icon should always have at least one layer, but you could theoretically have as many layers as you want. However, if you want a parallax effect, you will need multiple layers. The CSS classes you see defined above are styles specific to each layer.
So here it is!
We do this by binding an event handler to the target. In this case we are using the
mousemove event on any element with the class of
layers. The handler is an anonymous function which is executed every time the event is triggered (whenever the mouse moves over the element). The function takes an
Event object as a parameter which can be used to determine the X and Y coordinates of the mouse. Here, I use it in conjunction with the coordinates of the parent element relative to the document to determine what part of the icon that the mouse is moving over.
Next, we adjust the CSS of the of the
.layers element (we can use
$(this) which refers to the current selected object in an event). We can use the
.css() method to manipulate the value of the
transform property in real-time, using
rotateY to rotate the icon on both X and Y axises based on the position of the X and Y coordinates of the mouse cursor. We also have to use
translateZ(-3rem) in order to preserve the icons position in the Z-axis so that is does not jump forward when the mouse moves over it. There is some cool looking maths going on in these lines of code. I wish I could explain the logic behind behind the maths used to determine how many degrees to rotate on the X and Y axis, but to be totally honest, it was a case of tweaking, random guesswork and trial and error! I plan to rewrite this section and make sense of it very soon!
For the lighting effect, I used the
.find() method to find the element with a class of
light-effect and used the
.css() method again to change the position of the lighting effect depending on the position of the cursor. The lighting effect does not appear on the icon until the cursor is about halfway down the icon, and it appears in the part of the icon which is tilted closest to the user. I had to tweak the maths to work out the the pixel value for the
translateY rule, and again, it was a case of trial and error and a bit of guesswork. The X and Y coordinates of the mouse is multiplied so that the lighting effect moves quicker than the mouse position.
Now it’s time to create the parallax effect. Each layer that makes up the icon has a class of
layer. This allows us to iterate over each layer in the icon and adjust the position of them by different amounts depending on its position on the z-axis. The parallax effect is quite subtle so the X and Y coordinates of the mouse cursor is divided by 20 so that the movement of the layer is small. This is also divided by the amount of layers in the icon minus the current iteration (plus one, as it starts at zero). This makes the layers move at different rates, achieving the parallax effect.
Finally, when the mouse cursor leaves the icon, it is time to reset everything back to the way it was. We use the
mouseleave() event on the
.layers element for this. Here, I simply set any rotation and positioning back to zero. :-)