Activity Indicator with CSS3

Works on desktop Safari, desktop Google Chrome, Android, iPhone, iPod Touch and iPad.

You’ve seen them. The things on the screen that mean you’ve got to wait for some activity to complete. They’re called activity indicators. They’re usually several pieces arranged in a circle. They spin around. And if things go really bad, you could be watching them spin for quite some time. Hopefully not.
activity indicator

Usually people make these with images. There are even Web sites dedicated to making this spinning baubles for you in animated gif format. Except that gif stink. Pngs look better. I’m going to show you two ways to implement an activity indicator. One starts off with an image that has been converted into 64 bit data. We’re talking about dataurls here. Dataurls eliminate the need for separate images. It’s the same image but reduced to data which you can paste directly in your document. Heck, you can paste it directly into your CSS file so you never have to worry about losing an image or rewriting the image path when you move the CSS. Cool, huh? And if you start with a large image, you can scale it down for small screens and low resolution devices, and scale it up for hi res devices, like the iPhone and iPod Touch retina display. Cooler still, you can make the activity indicator completely out of HTML and CSS, no images, no dataurl. And by using a scale transform, you never have to worry about resolution. It will always render smoothly.

So, let’s look at dataurls. The concept is simple. Instead of having a resource reside as an external file, you convert it into a 64 bit data sample which you can include in your document. In the case of an image, it’s basically embedding it in your HTML/CSS. You can encode images as dataurls in many ways, using PHP, Python, etc., or you can simply upload a file to a Web site that will conveniently do it for you. A dataurl looks something like this (Note: I trimmed off the data to fit here):

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAATEAAAEwCA+

For an image, you would do something like this:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAATEAAAEwCA+...">

When you create your first dataurl you’ll probably be shocked at how big the resulting code is. Don’t worry. Somehow this winds up being more efficient for download and rendering than a traditional image. Besides being the source for an inline image, you could also embed the dataurl into your CSS as a background image. As I mentioned, my original image was large to allow for scaling up or down without loss of quality. I therefore add in some resizing to the CSS for the size of the element and the size of the background image:

.activityIndicator {
	/*
	The original height is:
	height: 304px;
	width: 305px;
	*/
	height: 40px;
	width: 40px;
	-webkit-background-size: 40px 40px;
	margin: 0px auto;
	background-image: url("data:image/png;base64,iVBORw0KGgoAAAANUb...");

This is what I used to produce the image above. Now we need to animate it. That’s actually quite easy, just a couple of lines of CSS:

.activityIndicator {
	/*
	The original height is:
	height: 304px;
	width: 305px;
	*/
	height: 40px;
	width: 40px;
	-webkit-background-size: 40px 40px;
	margin: 0px auto;
	background-image: url("data:image/png;base64,iVBORw0KGgoAAAANUb...");
	-webkit-animation-duration: 1s;
	-webkit-animation-iteration-count: infinite;
	-webkit-animation-timing-function: linear;
	-webkit-animation-name: spinnerAnim;
}
@-webkit-keyframes spinnerAnim {
	0% { -webkit-transform: rotate(0deg); }
	100% { -webkit-transform: rotate(360deg); }
}

By using the keyframe animation, we can implement the continuously spinning activity indicator without using JavaScript. But, as I mentioned, dataurls tend to be verbose and are rather unsightly. I therefore came up with a reproduction of the activity indicator using only HTML and CSS3. At the end of this post are links to the working file. Download or view source of the online document to see what the dataurl looks like. It’s not pretty, but it is efficient. However, by reproducing the same image using markup and CSS, we can achieve the same result with even less code. The markup isn’t terribly complex, two parent contains and twelve blades:

<div id="activityIndicator">
	<div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
		<div class="blade"></div>
	</div>
</div>

That’s really all we need, the rest is done with CSS3 properties. You’ll notice that all the blades have the same class “blade.” We don’t need to give each one anything special because we can use CSS3 selectors to indicate each blade individually. To do this we’ll use the :nth-child selector. We first give all the blades some generic styling. Then we start from the second blade using the CSS3 nth-child selector: .blade:nth-child(2), then .blade:nth-child(3), etc. Since there are twelve blades, we need to rotate each one 30 degrees more than the previous. We also need to change the color by approximately 21.25% to go from and rgb value of 0 to 255. Since rgb values expect positive integers between 0 and 255, we need to round the float off to the nearest whole number. As you can see, CSS3 selectors allow us to create complex structures with minimal markup and CSS.

#activityIndicator {
	position: relative;
	width: 130px;
	height: 130px;
	margin: 0 auto;
	-webkit-perspective: 500;
	-webkit-animation-duration: 1s;
	-webkit-animation-iteration-count: infinite;
	-webkit-animation-timing-function: linear;
	-webkit-animation-name: spinnerAnim2;
}
@-webkit-keyframes spinnerAnim2 {
	0% { -webkit-transform: rotate(0deg) scale(.29); }
	100% { -webkit-transform: rotate(360deg) scale(.29); }
}
#activityIndicator > div:first-of-type {
	margin-left: 58px;
	margin-top 0;
	width: 50%;
	height: 50%;
}
#activityIndicator .blade {
	position: absolute;
	height: 40px;
	width: 14px;
	background-color: rgba(234,234,234, .30);
	-webkit-border-radius: 10px;
	-webkit-transform-origin-x: 50%;
	-webkit-transform-origin-y: 165%;
}

#activityIndicator .blade:nth-child(2) {
	-webkit-transform: rotate(30deg);
	background-color: rgba(212,212,212, .70);
}
#activityIndicator .blade:nth-child(3) {
	-webkit-transform: rotate(60deg);
	background-color: rgba(191,191,191, .70);
}
#activityIndicator .blade:nth-child(4) {
	-webkit-transform: rotate(90deg);
	background-color: rgba(170,170,170, .70);
}
#activityIndicator .blade:nth-child(5) {
	-webkit-transform: rotate(120deg);
	background-color: rgba(149,149,149, .70);
}
#activityIndicator .blade:nth-child(6) {
	-webkit-transform: rotate(150deg);
	background-color: rgba(128,128,128, .70);
}
#activityIndicator .blade:nth-child(7) {
	-webkit-transform: rotate(180deg);
	background-color: rgba(106,106,106, .70);
}
#activityIndicator .blade:nth-child(8) {
	-webkit-transform: rotate(210deg);
	background-color: rgba(85,85,85, .70);
}
#activityIndicator .blade:nth-child(9) {
	-webkit-transform: rotate(240deg);
	background-color: rgba(64,64,64, .70);
}
#activityIndicator .blade:nth-child(10) {
	-webkit-transform: rotate(270deg);
	background-color: rgba(42,42,42, .70);
}
#activityIndicator .blade:nth-child(11) {
	-webkit-transform: rotate(300deg);
	background-color: rgba(21,21,21, .70);
}
#activityIndicator .blade:nth-child(12) {
	-webkit-transform: rotate(330deg);
	background-color: rgba(0,0,0, .70);
}

You can try this out online or download the source code.

Update: September 14th, 2010
I forgot to mention one thing. If you look at the styles on #activityIndicator .blade you’ll notice the last two property definitions:

	-webkit-transform-origin-x: 50%;
	-webkit-transform-origin-y: 165%;

By setting the transform origin x value to 50% we fix the horizontal rotation to the blade’s center. By setting the transform origin vertical value to 165% we define the turning point at that distance from the start of the blade. Together these values cause the blades to rotate around leaving and empty circular space in the center, thus reproducing the appearance of the png image.

Advertisements

CSS3 Page Flips

Works with Safari, iPhone, iPad.

In previous examples of page navigation we used various types of sliding effects to indicate to the use the transition from one section of a Web app to another. Now we’re going to look at how to indicate section transitions with various types of page flips. In order for these types of flips to work properly, we will need to enclose the items being rotated in a parent element that has the CSS3 property for transform style with a value of preserve-3d. We also need to define the perspective value. The higher the value, the further back from the element the viewer will be, reducing the perspective distortion. Similarly, the smaller the perspective value, the more pronounced the 3D effect will be. For something a little less pronounced I use a value of 1000, and for more pronounced: 500.

First we’re going to look at implementing a typical “card flip” effect. We’re going to implement this with the same basic layout that I created in earlier posts with backward and forwards navigation with a slide effect, except that the links will lead to pages with the various flips and rotations. So, the target of the menu items will contain a parent element, a div tag, which we’ll give a class of “card.” This will have the perspective values that will determine how the rotation looks with perspective distortion. Here’s the markup for the first section. The other section have the same markup, but a different identifier to allow for unique transforms on each section. One thing to bear in mind when implenting these types of transforms, the background of the body tag will show as the elements transition. So, you should make transforms elements’ backgrounds visually different from the background of your body tag so that you can see the elements as they are rotated easier.

<article id="Flip">
	<section class="card">
		<div class="face front">
			 <header>
				 <span class="button back"><span></span>Back</span>
				 <h1>Flip Page Over</h1>
				 <span  id="flipOverButton" class="button">Flip Over</span>
			 </header>
			 <div class="paddingWrapper">
				 <h2>This is the front</h2>
				 <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
		 </div>
		</div>
		<div class="face back">
			<header>
				<h1>Flip Page Over</h1>
				<span  id="flipBackButton" class="button">Flip Back</span>
			</header>
			<div class="paddingWrapper">
				<h2>This is the back</h2>
				<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
			</div>
		</div>
	</section>
</article>

Here’s the CSS we need to get the rotation flip to work:

#Flip {
	-webkit-perspective: 1000;
	position: absolute;
}
article .card {
	min-height: 100%;
	width: 100%;
	position: absolute;
}
.face {
	position: absolute;
	width: 100%;
	height: 100%;
	text-align: center;
	-webkit-backface-visibility: hidden;
	-webkit-transform-style: preserve-3d;
	-webkit-transition: all .5s ease-in-out;
}
#Flip .face.front {
	-webkit-transform: rotateY(0deg);
	background-color: #fffccf;
}
#Flip .face.back {
	-webkit-transform: rotateY(180deg);
	-webkit-box-sizing: border-box;
	background-color: rgba(0,0,0,0.125);
}
#Flip .face.front.flip  {
	-webkit-transform: rotateY(-180deg);	
}
#Flip .face.back.flip {
	-webkit-transform: rotateY(0deg);	
}

We define the perspective property -webkit-perspective: 1000 on the article tag with an id of #Flip. Then we set the initial rotation status of the two divs. For the frontmost div we give it a rotation of 0 degrees, and for the back one, a rotation of 180. We also give the div with the “face” class a CSS3 property to hide its rear side so that it doesn’t show when we rotate it: -webkit-backface-visibility: hidden;. Now, to initiate the flip effect all we have to do is use a little JavaScript to add and remove the “flip” class to the two elements.

Now let’s look at how to implement another flip effect that resembles a left page turn. This is basically the same as the card flip described above, except that we’re going to define the start position of the transform. By default the start position is at the center of the element’s y axis. We’re going to use -webkit-transform-origin: 0% 0%; If we used a value of -webkit-transform-origin: 100% 0%; the result would resemble doors swinging open and closed. Here’s the complete list of styles for the left page turn effect. Notice that we’ve decreased the perspective on the “card” class to 500:

#TurnLeft .card {
	height: 100%;
	-webkit-perspective: 500;
}
#TurnLeft {
	height: 100%;
	background: #cbd2d8;background-image: -webkit-gradient(linear, left top, right top, from(#c5ccd4), color-stop(0.75, #c5ccd4), color-stop(0.75, transparent), to(transparent)); 
	-webkit-background-size: 6px 100%;
}
#TurnLeft .face {
	position: absolute;
	width: 100%;
	height: 100%;
	-webkit-backface-visibility: hidden;
	-webkit-transform-style: preserve-3d;
	-webkit-transition: all .5s ease-in;
	-webkit-box-sizing: border-box
	text-align: center;
}
#TurnLeft .face.front {
	background-color: #fffccf;
	background-image: -webkit-gradient(linear, left top, right top, from(rgba(197, 204, 212, 0.5)), color-stop(0.75, rgba(197, 204, 212, 0.5)), color-stop(0.75, transparent), to(transparent)); 
	-webkit-background-size: 5px 100%;
	-moz-background-size: 7px 100%;
	-webkit-transform-origin: 0% 0%;
	-webkit-transform: rotateY(0deg);
}
#TurnLeft .face.back {
	-webkit-transform: rotateY(180deg);
	-webkit-box-sizing: border-box;
	background-color: rgba(122,137,212,0.25);
	-webkit-transform-origin: 0% 0%;
}
#TurnLeft .face.front.turnLeft  {
	-webkit-transform: rotateY(-180deg);	
}
#TurnLeft .face.back.turnLeft {
	-webkit-transform: rotateY(0deg);	
}

So, once again we can trigger the turn page left effect by adding and removing the “turnLeft” class to the necessary elements.

Lastly, we’ll look at how to do a swing back and down effect. Notice that we’re not rotating on the y-axis but on the x-axis. This causes the elements to rotate on their x-axis:


#SwingBack .card {
	min-height: 100%;
	-webkit-perspective: 500;
}
#SwingBack {
	height: 100%;
	background: #cbd2d8;background-image: -webkit-gradient(linear, left top, right top, from(#c5ccd4), color-stop(0.75, #c5ccd4), color-stop(0.75, transparent), to(transparent)); 
	-webkit-background-size: 6px 100%;
}
#SwingBack .face {
	position: absolute;
	width: 100%;
	height: 100%;
	-webkit-backface-visibility: hidden;
	text-align: center;
	-webkit-transform-style: preserve-3d;
	-webkit-transition: all .5s ease-in-out;
	-webkit-box-sizing: border-box
}
#SwingBack .face.front {
	background-color: #fffccf;
	background-image: -webkit-gradient(linear, left top, right top, from(rgba(197, 204, 212, 0.5)), color-stop(0.75, rgba(197, 204, 212, 0.5)), color-stop(0.75, transparent), to(transparent)); 
	-webkit-background-size: 5px 100%;
	-moz-background-size: 7px 100%;
	-webkit-transform-origin: 0% 0%;
	-webkit-transform: rotateX(0deg);
}

#SwingBack .face.back {
	background-color: #fffccf;
	background-image: -webkit-gradient(linear, left top, right top, from(rgba(197, 204, 212, 0.5)), color-stop(0.75, rgba(197, 204, 212, 0.5)), color-stop(0.75, transparent), to(transparent)); 
	-webkit-background-size: 5px 100%;
	-moz-background-size: 7px 100%;
	-webkit-transform: rotateX(180deg);
	-webkit-box-sizing: border-box;
	-webkit-transform-origin: 0% 100%;
}
#SwingBack .face.front.swingBack  {
	-webkit-transform: rotateX(-180deg);	
}
#SwingBack .face.back.swingBack {
	-webkit-transform: rotateX(0deg);	
}

And here’s a working example online, or you can download the source. Enjoy.

Create Back & Next Buttons with CSS3

Works with Safari, iPhone, iPad

Update: March 21, 2011

I’ve update the method of create “Back” and “Next” button without the need for any extra markup, such as the span tag used in post below. Instead I use a CSS technique to create the pointer through only CSS. Read the new post and checkout the new example.

In an earlier post about the iPad’s distinctive dropdown menus I devised a scheme for creating an angular pointer. Now I’m going to use the same technique to create back and next buttons using only CSS3. Unfortunately, Android and Google Chrome do not presently support image masks, so this only works with iPhone, iPad and the Safari desktop browser. If you wish to reproduce this look for Android or Chrome, you’ll need to use image pieces, or be satisfied with simpler button styles. Eventually the CSS3 image mask will be supported on Android and Chrome.

To make the backward/forward pointer on the buttons possible we will need to add some extra markup to the basic button. The markup will look like this:

<a href="#" class="button back black"><span></span>Back</a>
<a href="#" class="button next black"><span></span>Next</a>

You’ll notice the inclusion of the span in the two link buttons above. We’ll be styling these spans to be the left and right pointing part of our buttons. First we’ll start with the basic button styles. We’ll start with the styles for our button class:

.button {
	color: #fff;
	text-decoration: none;
	display: inline-block;
	padding: 4px 10px;
	-webkit-border-radius: 5px;
}

This class gives us some basic properties for all our button styles. I’m using a black class to enhance this with border and background styles. But I could just as easily create some other color scheme using a different class, like blue or gold. So, here’s the style definitions for the black class:

 .button.black {
	 background-image: -webkit-gradient(linear, left top, left bottom, 
		 from(#7d828c),
		 color-stop(0.5, #303749), 
		 color-stop(0.5, #121a2e), 
		 to(#121a2e));
	 border: solid 1px rgba(79, 79, 79, 0.75);
 }

We’ve given the button a border color that is slightly transparent using RGBA values. We also styled the background of the button using a CSS3 gradient. It starts at the top, stop half way and changes to another color, creating a chiseled or beveled look. We’ll also add in a bright blue hover effect for mouse-based systems:

.button.black:hover {
	background-image: -webkit-gradient(linear, left top, left bottom, 
		from(#4286f5), 
		color-stop(0.5, #4286f5),
		color-stop(0.5, #194fdb),
		to(#194fdb));
}

This will give us a button with a hover state as indicated in the image below:
How the basic button class look with the addition of the black class.

As you can see, this creates a fairly slick looking button with a minimum of markup and can easily be adjust for other color schemes. We could also get fancy with the text by beveling it with an offset text shadow or give the button some depth with a box shadow.

Now let’s see how to create the pointer part that we need for back and next buttons. Because we’re using a basic, empty span, we can use the same span for either a back or next button, we just need to flip its styles to achieve the two effects.

Let’s do the back button first. First we need to do a little prep to the parent button. We’ll give it relative positioning because we’ll want to use absolute position on the span to move it into place. We’ll also want to change the default padding from 10px on the left to just 8. This is to balance out the space created by the back pointing part of the button the finished button.

.button.back {
	position: relative;
	padding-left: 5px;
	margin-left: 8px;
}

Now we’ll give the span some formatting styles and a matching background gradient. And, since we plan on rotating the span to the right and clipping off the right half, we only need a border on the left and bottom part of the span. Here’s the styles:

.back.black > span {
       display: block;
       height: 20px;
       width: 20px;
       background-image: -webkit-gradient(linear, left top, left bottom, 
              from(#7d828c),
              color-stop(0.5, #303749), 
              color-stop(0.5, #121a2e), 
              to(#121a2e));
       border-left: solid 1px rgba(79, 79, 79, 0.75);
       border-bottom: solid 1px rgba(79, 79, 79, 0.75);
}

Our styled span now looks like this:
Pointer span with gradient and borders

Since we’re going to rotate the span, we might as well realign the direction of the gradient so that it appears to be vertical. To do that we need to make it flow from left top to right bottom, creating a diagonal dragient:

.back.black > span {
       display: block;
       height: 20px;
       width: 20px;
       background-image: -webkit-gradient(linear, left top, right bottom, 
              from(#7d828c),
              color-stop(0.5, #303749), 
              color-stop(0.5, #121a2e), 
              to(#121a2e));
       border-left: solid 1px rgba(79, 79, 79, 0.75);
       border-bottom: solid 1px rgba(79, 79, 79, 0.75);
}

Now the span looks like this:

Pointer span with angled gradient

Next we’re going to rotate the span using -webkit-transform: rotate.

.back.black > span {
       display: block;
       height: 20px;
       width: 20px;
       background-image: -webkit-gradient(linear, left top, right bottom, 
              from(#7d828c),
              color-stop(0.5, #303749), 
              color-stop(0.5, #121a2e), 
              to(#121a2e));
       border-left: solid 1px rgba(79, 79, 79, 0.75);
       border-bottom: solid 1px rgba(79, 79, 79, 0.75);
      -webkit-transform: rotate(45deg);
}

This give us a rotated span:
Pointer span rotated 45 degrees.

.back.black > span {
       display: block;
       height: 20px;
       width: 20px;
       background-image: -webkit-gradient(linear, left top, right bottom, 
              from(#7d828c),
              color-stop(0.5, #303749), 
              color-stop(0.5, #121a2e), 
              to(#121a2e));
       border-left: solid 1px rgba(79, 79, 79, 0.75);
       border-bottom: solid 1px rgba(79, 79, 79, 0.75);
      -webkit-transform: rotate(45deg);
}

To get rid of the right half we’ll use a CSS3 image mask. But instead of using an actual image, we’ll use a CSS3 gradient. The gradient needs to go from black to transparent. To create the cut off effect we go from solid black halfway, stop, then go the rest of the way with full transparency. The syntax is fairly straightforward: -webkit-mask-image.

.back.black > span {
       display: block;
       height: 20px;
       width: 20px;
       background-image: -webkit-gradient(linear, left top, right bottom, 
              from(#7d828c),
              color-stop(0.5, #303749), 
              color-stop(0.5, #121a2e), 
              to(#121a2e));
       border-left: solid 1px rgba(79, 79, 79, 0.75);
       border-bottom: solid 1px rgba(79, 79, 79, 0.75);
      -webkit-transform: rotate(45deg);
      -webkit-mask-image: -webkit-gradient(linear, left bottom, right top, 
              from(#000000), 
              color-stop(0.5,#000000), 
              color-stop(0.5, transparent), 
              to(transparent));
}

The final, masked span now looks like this:
Masked pointer span

However, if we look at it inside of our regular button, it looks awful because of its rotation:

Since the parent button already has relative positioning, we just need to give the span absolute position with a left and top value to move it into place:

Back button with unpositioned pointer

.back.black > span {
       display: block;
       height: 20px;
       width: 20px;
       background-image: -webkit-gradient(linear, left top, right bottom, 
              from(#7d828c),
              color-stop(0.5, #303749), 
              color-stop(0.5, #121a2e), 
              to(#121a2e));
       border-left: solid 1px rgba(79, 79, 79, 0.75);
       border-bottom: solid 1px rgba(79, 79, 79, 0.75);
      -webkit-transform: rotate(45deg);
      -webkit-mask-image: -webkit-gradient(linear, left bottom, right top, 
              from(#000000), 
              color-stop(0.5,#000000), 
              color-stop(0.5, transparent), 
              to(transparent));
       position: absolute;
       left: -8px;
       top: 2px;
}

And here’s the finished button:
Completed back button

To create a next button, we can use the same button, but switch out a “next” class with all the values flipped to produce a button pointing the opposite way:

.back.black > span {
       display: block;
       height: 20px;
       width: 20px;
       background-image: -webkit-gradient(linear, left top, right bottom, 
              from(#7d828c),
              color-stop(0.5, #303749), 
              color-stop(0.5, #121a2e), 
              to(#121a2e));
       border-left: solid 1px rgba(79, 79, 79, 0.75);
       border-bottom: solid 1px rgba(79, 79, 79, 0.75);
      -webkit-transform: rotate(45deg);
      -webkit-mask-image: -webkit-gradient(linear, left bottom, right top, 
              from(#000000), 
              color-stop(0.5,#000000), 
              color-stop(0.5, transparent), 
              to(transparent));
       position: absolute;
       left: -8px;
       top: 2px;
}

You can view the file or download the complete file here to see the “back” and “next” buttons with their hover states.

NOTE
Although this technique does not work properly in Google Chrome because it presently lacks the CSS3 image mask property, you could make it work by enclosing the text of the link in a second span tag, give that span tag relative positioning with a z-index of 2 or greater. This would effectively position the text over the rotated span tag used a the pointer.

Update for Chrome and Android – August 29/2010
OK. Some good news, image masks are coming to Google Chrome 6. It has support for image masks the same as desktop Safari, iPhone, iPod Touch and iPad. The bad news is that this will most like not trickle down to the many Android devices out there. If you want to have the back and next buttons work on iOS and be backward compatible with existing Android devices, you can makes some slight changes to how the buttons are put together. Enclose the button’s text in a span and rename the empty span that gets used as a pointer to something sensible. I’m giving it a class of “pointer.” What we’ll do is give the text span relative positioning. Then we’ll give the pointer span a z-index of 1 and the text span a z-index of 2. That way the text will sit on top of the pointer and for Android devices that don’t support image masks you’ll still get the same look. Here’s the new markup:

<a href="#" class="button back black"><span class="pointer"></span><span>Back</span></a>
<a href="#" class="button next black"><span class="pointer"></span><span>Next</span></a>

With the CSS we’ll give all spans in the button relative positioning with a z-index of 2, then we’ll give the pointer span absolute positioning with a z-index of 1:

.back.black > span {
    position: relative;
    z-index: 2;
}
.back.black > span.pointer {
       display: block;
       height: 20px;
       width: 20px;
       background-image: -webkit-gradient(linear, left top, right bottom, 
              from(#7d828c),
              color-stop(0.5, #303749), 
              color-stop(0.5, #121a2e), 
              to(#121a2e));
       border-left: solid 1px rgba(79, 79, 79, 0.75);
       border-bottom: solid 1px rgba(79, 79, 79, 0.75);
      -webkit-transform: rotate(45deg);
      -webkit-mask-image: -webkit-gradient(linear, left bottom, right top, 
              from(#000000), 
              color-stop(0.5,#000000), 
              color-stop(0.5, transparent), 
              to(transparent));
       position: absolute;
       left: -8px;
       top: 2px;
       z-index: 1
}

Update: October 5, 2010

I’ve update these buttons to use subpixel rendering. You can read about the technique here. You can try this out online or download the source code.