Saturday, December 29, 2012

Animating the DOM: A New Year's Fireworks Effect

It's the end of the year and I'm in the mood to celebrate with some fireworks. However, instead of spending the money on real fireworks, I prefer to challenge myself and simulate them in the browser. My initial idea was to construct them using CSS3 3D transforms. The effect is nice and works well, however, only in Chrome. While Firefox may support 3D transforms, it certainly can not handle several hundred DIVs being transformed and animated without severe choppiness. So, in addition to a 3D version, I faked the 3D by creating several layers - each representing part of the sphere created by the firework exploding outward. Both version are on my sandbox for comparison [CSS3 3D version | Layered Version].

In my next post, I'll delve into how the 3D version works. For now, enjoy the show!

Monday, December 17, 2012

Using Sass and Compass to Write Less CSS Better, Faster, and Smarter

One of my favorite perks of building web-based tools is not needing to compile anything. Just open a text editor, write some code, and refresh the browser - instant gratification. So, as I've learning new technologies that are designed to make it easier to build web pages, I've been reluctant to use anything that requires building or compiling code. However, as I've written more and more CSS styles, I've found myself thinking:

  1. Wouldn't it be great if I could just reuse this block of styles and swap out the color.

  2. I need this in several sizes...

  3. The vendor prefixes are driving me nuts!



It seemed time to try a CSS meta-language to reduce the amount of work required to handle those scenarios I kept running into.  Sass seemed like a good starting point - I've seen quite a few references to it and like its syntax.  It really only takes a few minutes to figure out the basics.  I figured if it helped write less CSS, it would be worth it even if it required a build step to get to the final style sheet.

The true tipping point came when I realized I'd need to write the following styles to make my CSS-only spinner work properly across all the browsers:


@-moz-keyframes clock-spinleft {
from {
-moz-transform: rotate(0deg);
transform: rotate(0deg);
}

to {
-moz-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}

@-webkit-keyframes clock-spinleft {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}

to {
-webkit-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}

@-o-keyframes clock-spinleft {
from {
-o-transform: rotate(0deg);
transform: rotate(0deg);
}

to {
-o-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}

@-ms-keyframes clock-spinleft {
from {
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}

to {
-ms-transform: rotate(-360deg);
transform: rotate(-360deg);
}
}

@keyframes clock-spinleft {
from {
transform: rotate(0deg);
}

to {
transform: rotate(-360deg);
}
}

@-moz-keyframes clock-spinright {
from {
-moz-transform: rotate(0deg);
transform: rotate(0deg);
}

to {
-moz-transform: rotate(360deg);
transform: rotate(360deg);
}
}

@-webkit-keyframes clock-spinright {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}

to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}

@-o-keyframes clock-spinright {
from {
-o-transform: rotate(0deg);
transform: rotate(0deg);
}

to {
-o-transform: rotate(360deg);
transform: rotate(360deg);
}
}

@-ms-keyframes clock-spinright {
from {
-ms-transform: rotate(0deg);
transform: rotate(0deg);
}

to {
-ms-transform: rotate(360deg);
transform: rotate(360deg);
}
}

@keyframes clock-spinright {
from {
transform: rotate(0deg);
}

to {
transform: rotate(360deg);
}
}

.spinner.clock {
position: relative;
height: 46px;
width: 46px;
}

.spinner.clock > div {
position: absolute;
border-style: solid;
background-color: transparent;
}

.spinner.clock .circle {
width: 36px;
height: 36px;
border-width: 5px;
border-color: #555;
-webkit-border-radius: 36px;
-moz-border-radius: 36px;
-ms-border-radius: 36px;
-o-border-radius: 36px;
border-radius: 36px;
}

.spinner.clock .wedge {
width: 0;
height: 0;
border-width: 23px;
border-color: #999 transparent transparent transparent;
-webkit-border-radius: 23px;
-moz-border-radius: 23px;
-ms-border-radius: 23px;
-o-border-radius: 23px;
border-radius: 23px;
-webkit-animation: clock-spinright 1250ms linear 0s infinite;
-moz-animation: clock-spinright 1250ms linear 0s infinite;
-ms-animation: clock-spinright 1250ms linear 0s infinite;
-o-animation: clock-spinright 1250ms linear 0s infinite;
animation: clock-spinright 1250ms linear 0s infinite;
}
.spinner.clock .wedge:before, .spinner.clock .wedge:after {
content: ' ';
position: absolute;
margin: 0;
top: -23px;
left: -23px;
width: 0;
height: 0;
border-style: solid;
border-width: 23px;
border-color: transparent #fff #fff #fff;
-webkit-border-radius: 23px;
-moz-border-radius: 23px;
-ms-border-radius: 23px;
-o-border-radius: 23px;
border-radius: 23px;
}
.spinner.clock .wedge:before {
-webkit-transform: rotate(-30deg);
-moz-transform: rotate(-30deg);
-ms-transform: rotate(-30deg);
-o-transform: rotate(-30deg);
transform: rotate(-30deg);
}
.spinner.clock .wedge:after {
-webkit-transform: rotate(20deg);
-moz-transform: rotate(20deg);
-ms-transform: rotate(20deg);
-o-transform: rotate(20deg);
transform: rotate(20deg);
}

.spinner.clock .arc {
width: 36px;
height: 36px;
border-width: 5px;
border-color: transparent transparent #bbb transparent;
-webkit-border-radius: 36px;
-moz-border-radius: 36px;
-ms-border-radius: 36px;
-o-border-radius: 36px;
border-radius: 36px;
-webkit-animation: clock-spinleft 750ms linear 0s infinite;
-moz-animation: clock-spinleft 750ms linear 0s infinite;
-ms-animation: clock-spinleft 750ms linear 0s infinite;
-o-animation: clock-spinleft 750ms linear 0s infinite;
animation: clock-spinleft 750ms linear 0s infinite;
}



Way too many vendor prefixes to deal with. I'd probably forget to change half of these if I wanted to tweak something. With Sass mixins, I should be able to reduce the amount of duplication due to the vendor prefixes. Sass is a Ruby gem so if you already have Ruby installed, get Sass is pretty straight forward:


gem install sass


Once installed, I just needed to create a SCSS file and wrap my existing CSS with some Sass magic:


@mixin animation($name: none, $duration: 0s, $timing: ease, $delay: 0s, $iteration: 1, $direction: normal, $fillmode: none) {
animation: $name $duration $timing $delay $iteration $direction $fillmode;
-moz-animation: $name $duration $timing $delay $iteration $direction $fillmode;
-webkit-animation: $name $duration $timing $delay $iteration $direction $fillmode;
-o-animation: $name $duration $timing $delay $iteration $direction $fillmode;
-ms-animation: $name $duration $timing $delay $iteration $direction $fillmode;
}

@mixin transform($params) {
transform: $params;
-moz-transform: $params;
-webkit-transform: $params;
-o-transform: $params;
-ms-transform: $params;
}

// https://gist.github.com/1607696
@mixin keyframes($name) {
@-webkit-keyframes #{$name} {
@content;
}
@-moz-keyframes #{$name} {
@content;
}
@-ms-keyframes #{$name} {
@content;
}
@keyframes #{$name} {
@content;
}
}

.spinner > div {
position: absolute;
background-color: transparent;
}

.spinner .circle {
border-color: #555;
width: 30px;
height: 30px;
border-radius: 30px;
border-width: 4px;
}

.spinner .wedge {
width: 0;
height: 0;
border-radius: 19px;
border-width: 19px;
border-color: #555 transparent transparent transparent;
opacity: 0.4;
@include animation(spinleft, 750ms, linear, 0s, infinite);
}

.spinner .arc {
width: 30px;
height: 30px;
border-width: 4px;
border-radius: 30px;
border-color: transparent transparent #999 transparent;
@include animation(spinright, 750ms, linear, 0s, infinite);
}

@include keyframes(spinleft) {
from { @include transform(rotate(0deg)); }
to { @include transform(rotate(-360deg)); }
}

@include keyframes(spinright) {
from { @include transform(rotate(0deg)); }
to { @include transform(rotate(360deg)); }
}


Much less code. A whole lot easier to read too. The next question that crossed my mind was that some helpful person out there probably already made a handy library that contains all those CSS mixins for vendor prefixes and other helpful short-cuts. And, to my delight, there is - Compass has mixins for a whole bunch of CSS3 styles so you don't have to make them all. Like Sass, Compass is a Ruby gem. Once installed, you simple create a project using:


compass create <myproject>


That will create several subdirectories with a config.rb file in the directory and some base Sass files. You simply add your Sass files to the indicated directory and start coding. To use one of the Compass libraries, just import them:


@import "compass/css3";


This pulls in all the available CSS3 mixins defined in Compass. Browse the Compass reference to see all the available functions. Once you want to generate your CSS file, you need to use Compass, not Sass to compile everything:


compass compile <myproject>


Now all the CSS files will be saved in the stylesheets directory in your project folder.

With Compass, I can now remove the mixins I defined because they are in the Compass library. With that change, I've created this final version:


@import "compass/css3";
@import "animation";

@mixin spinner-clock($name: null, $size: 35px) {

$class: "";
@if $name { $class: ".#{$name}"; };

// Need to work with whole numbers
// when divided by 2, otherwise the wedge
// will be offset from the center which is distracting
@if $size%2 > 0 { $size: $size+1; };

$border: ceil(10 * $size / 80);
$half: $size/2;

.spinner.clock#{$class} {
position: relative;
height: $size+$border*2;
width: $size+$border*2;
}

.spinner.clock#{$class} > div {
position: absolute;
border-style: solid;
background-color: transparent;
}

.spinner.clock#{$class} .circle {
width: $size;
height: $size;
border: {
width: $border;
color: #555;
}
@include border-radius($size);
}

.spinner.clock#{$class} .wedge {
width: 0;
height: 0;
border: {
width: $half+$border;
color: #999 transparent transparent transparent;
}
@include border-radius($half+$border);
@include animation(clock-spinright 1250ms linear 0s infinite);


// This does create a minor artifact in FF. The idea was
// to adjust the size of the wedge by sliding another semi-circle
// over it to make it look smaller. Comment it out or remove this section
// if you don't like it.
// or, even better, add a parameter to the mixin to not use it.

&:before, &:after {
content: ' ';
position: absolute;
margin: 0;
top: -1*($half+$border);
left: -1*($half+$border);
width: 0;
height: 0;
border: {
style: solid;
width: $half+$border;
color: transparent #fff #fff #fff;
}
@include border-radius($half+$border);
}

&:before {
@include transform(rotate(-30deg));
}

&:after {
@include transform(rotate(20deg));
}

}

.spinner.clock#{$class} .arc {
width: $size;
height: $size;
border: {
width: $border;
color: transparent transparent #bbb transparent;
}
@include border-radius($size);
@include animation(clock-spinleft 750ms linear 0s infinite);
}

}

@include keyframes(clock-spinleft) {
from { @include rotate(0deg); }
to { @include rotate(-360deg); }
}

@include keyframes(clock-spinright) {
from { @include rotate(0deg); }
to { @include rotate(360deg); }
}


That code will generate my spinner with all the different vendor prefixes with the additional perk of being able to specify an optional size. Since I was using Sass, I figured why not add some extra flexibility and reuse to the design of my spinner by making it a Sass mixin.

You might notice the @import "animation" at the beginning of the script. As it turns out, Compass does not have anything to generate the animation style or keyframes definition. However, there is a Compass extension that does add this support. Just install the gem:


gem install animation --pre


Open the Compass config.rb file in your project directory and add this line:


require 'animation'


Now that the required libraries are added, you can use the spinner mixin to generate all the required CSS for a specific size:


@import "spinner-clock";

@include spinner-clock("size15", 15px);
@include spinner-clock("size35", 35px);
@include spinner-clock("size55", 55px);


In this example, I named the spinner mixin code file _spinner-clock.scss and the above code css-spinner-clock-custom.scss. Once compiled, I'll be left with a css-spinner-clock-custom.css file with 3 distinct sets of spinner CSS style definitions each named differently (.size15, .size35, and .size55). In the HTML file, I just add the appropriate markup referencing the required styles:


<div class="spinner clock size15">
<div class="wedge"></div>
<div class="circle"></div>
<div class="arc"></div>
</div>

<div class="spinner clock size35">
<div class="wedge"></div>
<div class="circle"></div>
<div class="arc"></div>
</div>

<div class="spinner clock size55">
<div class="wedge"></div>
<div class="circle"></div>
<div class="arc"></div>
</div>


Here, the extra clock class is used to distinguish from other spinners I might make later. The most current version and full source, including an already generated CSS file, is available on GitHub. I also published a quick demo on the project page for the repository. In addition to the spinner described here, I've also made several other variations all using Sass/Compass as a basis for generating the required CSS.

Now these spinners don't work in all versions of IE (only version 10), but it illustrates the power of using a meta-language to define CSS style sheets. You can quickly appreciate the readability of the code and ability to organize and reuse common styles. I had to do a little extra work to get everything compiled and rolled out. However, the benefits were definitely worth the extra build step. Despite my attempts, it looks like I'll never be able to completely ditch the compiler.

Thursday, December 13, 2012

A CSS3-Only Animated Spinner

There was a time when animated GIFs dominated the web. Now, even using Javascript to animate objects on a page might be coming to an end. The CSS animation style has a lot of power available to create some nice animation sequences. I decided to test it out and make a spinner entirely from CSS.

To make this shorter and easier to read, I'm not adding the vendor-specific prefixes. Since most spinners are a circular shape, we're going to need some circles or parts of circles. By creatively using border-radius, we can turn a square DIV into a circle:


.circle {
width: 60px;
height: 60px;
border-style: solid;
border-radius: 35px;
border-width: 5px;
border-color: #999;
}


The height/width determines the size. The border-radius accounts for a half the width/height but must also include the border. In this case half of 60px is 30px plus the border of 5px to get to the correct value of 35px. Really, as long as its large enough, you'll have a circle - you could just error on the side of really big and use 60px - the browser will figure it out for you.


<div class="circle"></div>


Now, add the class to a DIV to get the following:

css3-rounded-corners-circle

A variation on the circle is to only include one side of the border to create an arc segment. Reusing the above circle definition, we just need to change the border-color style to use transparent borders for all the sides except the top:


.arc-top {
...
border-color: #999 transparent transparent transparent;
...
}



<div class="arc-top"></div>


css3-rounded-corners-arc

Another method to making shapes is to use a 0px height/width DIV and manipulate the borders to create triangles:


.triangle {
width: 0;
height: 0;
border-style: solid;
border-width: 35px;
border-color: #999 transparent transparent transparent;
}



<div class="triangle"></div>


This will only show the top border which will look like a triangle:

css-borders-triangle

Taking that concept and applying a border-radius will give the triangle a rounded edge.


.wedge {
width: 0;
height: 0;
border-style: solid;
border-radius: 35px;
border-width: 35px;
border-color: #999 transparent transparent transparent;
}



<div class="wedge"></div>


Which results in a wedge shape:

css3-rounded-corners-border-wedge

With those individual pieces created, I just need to make them move in a spinner like fashion. The animation style has a lot of options. Among them is the name of a keyframes definition which actually defines the path of the animation.


.spinner .wedge {
animation: spinleft 750ms linear 0s infinite normal none;
}

This definition provides a lot of flexibility for designing animation sequences. However, for my purposes, I just needed it to rotate a part of the spinner around in circles:

@keyframes spinleft {
from { transform: rotate(0deg); }
to { transform: rotate(-360deg); }
}


Now, just smash together a circle, arc, and wedge:


<div class="spinner">
<div class="circle"></div>
<div class="wedge"></div>
<div class="arc"></div>
</div>


And then apply the animations to infinitely perform the rotations and, without using an image or writing any Javascript, you have yourself a spinner. The full source and demo is on my sandbox. It won't work in IE since it does not support the animation style. However, it provides an interesting glimpse to the power provided in the CSS specification slowly making its way into the browsers.

Sunday, December 9, 2012

Creating a Perspective based Animated 3D Ribbon Effect

css3-ribbon-effect
It seems that I've seen a lot of uses of this ribbon effect springing up lately. Maybe they've been there and I just haven't really noticed. On RedHat's careers page, it has them every where. I even saw them in a Popular Science article I was reading the other day. The effect does make headings stand out better by offsetting them from the main page alignment. I figured someone probably found a way to do this with just CSS3 and sure enough, I found this article with a solution.

What's really nice about this approach is that it doesn't require any additional markup. However, there are some limits to using :before and :after to create the extra content required for the effect. If you look at the available variations, you need to create content for the wrapping part with either the :before or :after. This leaves you with one pseudo-element to either create a wrap on the other side or one of those tail effects (on either side). If you want a complete ribbon with left-tail, left-wrap, right-wrap, and right-tail, you'll need at least one more element in the markup to attach these styles to. Additionally, animating the styles created by the pseudo-elements would be a challenge. Notice that the image above has two different wrap effects for the top (Apples) and bottom (Bananas). If I wanted to transition from the top to the bottom, it would not be possible with content generated by :before and :after because I need to animate those little triangles that create the wrap effect.  

Those triangles are actually another CSS trick that creatively uses the border style to make the correct shape. Since I want to change the border style based on the position of the element to maintain the illusion of perspective, I'll need to add markup around my original block and style it similarly.  While this approach does add extra content, it does turn this solution into something that possibly more flexible and easier to debug.

Before digging into the calculations to position and size the border, we need to create the base markup and static styling:

<div class="wrapper">
<div id="move"></div>

<div id="mv1" class="banner">
<div class="wrap"></div>
<div class="main">Apples</div>
</div>
</div>


The wrapper element is the guide the banner will appear to be wrapping around. It can be styled with any kind of height/width/color you'd like. The important thing to note is the relative positioning and the padding. The banner element will need to be positioned accordingly based on that setup:


.wrapper {
position: relative;
width: 100px;
height: 360px;
margin: 30px;
padding: 30px;
background-color: #ddd;
border-radius: 5px;
}


Now, for the actual banner/ribbon element. Since our intention is to move this element around, it needs to be absolutely positioned. I setup the padding of the wrapper to be 30px above so I'll initialize the top to 30px to match. I want the banner to be 10px left of the wrapper but the padding is 30px so I need to move it -40px. Everything else needs to be moved around accordingly so it fits together properly. The .main class will hold the text and the .wrap class implements the triangle border trick to complete the ribbon effect.


.banner {
position: absolute;
top: 30px;
margin-left: -40px;
}

.banner .main {
cursor: pointer;
position: relative;
padding: 10px;
padding-left: 20px;
padding-right: 20px;
min-width: 75px;
font-weight: bold;
color: white;
background-color: gray;
border-radius: 0px 5px 5px 0px;
}

.banner .wrap {
position: absolute;
top: 0px;
left: 0;
width: 0;
height: 100%;
border-width: 0px 10px 5px 0px;
border-style: solid;
border-color: transparent #aaa transparent transparent;
}


The wrap class is set to the initial position of the banner which is at the top with the wrap appearing on the bottom of the banner (the Apples one in the image above). We only need the right border on this element and will adjust the top/bottom widths to manipulate the shape of the triangle formed by the visible corner of the border. Since the wrap element is behind the main element, we'll carefully move the wrap element to only show the triangle portion of the border.

As a test, I originally setup a slider control to see the result of my calculations at different positions. The top point is at 30px and the bottom extreme is at 330px (creating a 300px range). Over that 300px range, the wrap element needs to adjust the bottom triangle from 5px high to 0px and then adjust the top triangle from 0px to 5px tall. An important point to notice here is that both top and bottom borders do not change at the same time. At our fictitious eye level, we should not see the wrapping since it should be directly behind the main element. So the full range is really 10px that will be traveled from top to bottom. The first calculation is to scale the current position of the banner to the range of the wrap element. To simplify the logic, I move it so the range is from -5 to 5. The final adjustment is to shift the wrap element up and down 5px to properly expose either the top or bottom border triangle:



var p = Math.round(10 / 300 * (ui.value - 30)) - 5;

$('#mv1')
.css('top', ui.value)
.find('.wrap')
.css({
top: (p >= 0 ? -p : 0),
borderTopWidth: (p >= 0 ? p : 0),
borderBottomWidth: (p <= 0 ? -p : 0)
})
.end()
.find('.main')
.css({
boxShadow: '3px '+(-p/2)+'px 3px 0 #aaa'
});



After successfully figuring out that logic, I moved it to an animation which will occur when the banner is clicked. It will transition up and down from the top to the bottom of the wrapper on each click:



$(function() {

var dir = 1;
$('#mv2')
.click(function ( e ) {

// Cache these for use in the step function
var $w = $('#mv2 .wrap'),
$m = $('#mv2 .main');

// Animate the position of the whole banner element but
// use the step callback to manipulate the position of the
// wrap element based on the current position.
$(this)
.animate({top: (dir == 1 ? 330 : 30)}, {
step: function (now, fx) {

var p = Math.round(10 / 300 * (now - 30)) - 5;

$w.css({
top: (p >= 0 ? -p : 0),
borderTopWidth: (p >= 0 ? p : 0),
borderBottomWidth: (p <= 0 ? -p : 0)
});

$m.css('box-shadow', '3px '+(-p/2)+'px 3px 0 #aaa');
}
});

dir *= -1;

});

// jQuery makes this easy to apply for all browser flavors.
$('#mv2 .main').css('box-shadow', '3px 3px 3px 0 #aaa');

});


I setup a demonstration of this code on my sandbox with both the slider implementation and the animated version. Following along the same approach, you can add more elements to the wrapper and position their wrapper to maintain the correct perspective that occurs during the animation. Add some more logic and could build an accordion style menu or content container.

Saturday, December 1, 2012

Proxying Touch Events to Enable jQuery UI Mouse Interactions

All the jQuery mouse interaction widgets (draggable, droppable, etc) are great until you try them on a touch enabled device like a tablet or phone. Those browsers do not generate mousedown, mousemove, or mouseup events. Instead, they generate toushstart, touchmove, and touchend events. Since jQuery UI is only binding to the mouse events, it will never receive a touch event which results in nothing working. You might be tempted to just bind to the touch events and try to treat them as mouse events. However, because most devices allow multiple touch points, the event does not have a single pageX/pageY variable. Each touch point is represented in an array of touches so any code would have to detect the touch event and look at that array instead of the standard pageX/Y variables.

An alternative approach would be to simply capture the touch events, pull out one of the touches, and then fire the equivalent mouse events. Fortunately, someone already thought of this and created a little hack called Touch Punch to basically proxy touch events and convert them to mouse events. The nice thing about this little library is that it doesn't require to do anything other than including it on your page to use it. Once its added, it just works - all the normal mouse interactions in jQuery UI function on a touch device.

I added it to my sandbox so my demos would be useable on a touch enabled device. As I tested several pages, all the jQuery UI elements worked properly, however, anything I personally bound to mousedown did not work as intended. I created a page on my sandbox specifically to try out the Touch Punch functionality. The goal was to animate something while the mouse was held (mousedown) and animate back to the start when the mouse was released (mouseup). Using Touch Punch, the mousedown event didn't behave the same between my computer and tablet. On the computer, it worked as expected - the animation continued as long as I held the mouse down. On the tablet, it just skipped to the end and then animated back to the beginning as if I on clicked on the element.

I suspect the difference in behavior is due to how the jQuery UI mouse interactions bind to the different events. I spent some time reviewing the UI code and there are several things happening that my simple setup is not doing. Technically, in my code, I don't care where you click - just that you did. So I could bind to the mousedown and touchstart events simultaneously since I don't need the x/y coordinates. However, in other examples in my sandbox, I do need the x/y so I was hoping Touch Punch would be a simple all-in-one solution.

I will probably need to spend a little more time digging to find the solution to my mousedown problem. However, jQuery UI works quite well so if you're looking for something to get your UI interactions working on a tablet, Touch Punch will do the trick.