How to reproduce jQuery effects with CSS

Dan Neame explains how to use CSS for UI animations and widgets, leading to smoother animation and cleaner degradation.

jQuery has been very successful in shaping the modern web: so much so that many of its features have fed back into the design of CSS. In this tutorial, we'll see how to reproduce common jQuery effects with CSS. You will need a basic understanding of HTML, CSS and jQuery, though even experts may learn new things to do with pseudo-classes.

There are a number of good reasons to use CSS in place of jQuery. First, CSS is often much faster than JavaScript. For example, when you use slideUp(), jQuery modifies the element's height property programmatically via the style attribute on the element. Modifying element properties is much slower than defining the animation via CSS: the browser can calculate what height to apply for you and doesn't need to modify the DOM, resulting in faster execution, which means smoother animation.

CSS is also both forward-compatible and degrades cleanly: if a CSS property doesn't apply then the content is still accessible; your user won't just see a particular animation or a rounded border. If there's an error in your JavaScript then everything stops.

Support for CSS properties varies between browsers. Everything here works in recent versions of Gecko/WebKit/Blink browsers and IE9+, but visit caniuse.com to check support for other properties.

Transitions

slideUp(), slideDown() and slideToggle() are some of the most-used functions in jQuery, often tied to a button press or mouseover. In order to replicate this effect, we'll need to use transition This enables us to specify which styles we'd like to animate, how long we'd like the animation to take and what sort of easing we'd like to use. The following CSS will animate just the height property for two seconds, with linear easing:

.slider {
	transition: height 2s linear;
}

When the height of slider is changed, the browser will animate between the old and new heights. The following code is all you need to try this for yourself:

HTML

<button class="toggler">Click me</button> 
<div class="slider"></div>

CSS

.slider {

transition: height 2s linear; 
height: 100px; 
background: red;
} 
.slider.down {
    height: 500px; 
}

jQuery

$('.toggler').click(function(){ 
   $('.slider').toggleClass('down');
});

This creates a nice smooth animation but we're still using jQuery to apply the class. Fortunately, it is possible to modify the state of the document without using jQuery via a pseudo-class.

Pseudo-class selectors

Pseudo-class selectors enable us to modify the styling of an element by matching against information outside the document; for example, if the user has hovered or clicked on an element. We'll take a look at three of these and see how they can be used to respond to user input in place of jQuery.

:focus

The :focus pseudo-class selector facilitates targeting elements that have been clicked on or tabbed to with the keyboard. By default, only form fields will be able to receive focus, but we can allow other elements to receive focus by giving them the tabindex attribute.

A good use for :focus is modal pop-ups. If the modal is placed inside the DOM tree of the element that triggers the pop-up, it will retain focus if you click inside the modal. Clicking outside the modal closes the pop-up:

HTML

<div class="modal-launcher" tabindex="1">
   
    Click here to open

    <div class="modal">

    <span class="close-button" tabindex="2">x</span> 
    Modal popup!
    </div> 
</div>

CSS

.modal {

    display: none;
    position: absolute; top: 25%;
    left: 25%;
    bottom: 25%; 
    right: 25%;
    border: 1px solid #ccc;
    background: white; 
}
.modal-launcher:focus { 
    outline: none;
}

.modal-launcher:focus .modal {
    display: block; 
}
.close-button { 
    float: right;
    cursor: pointer; 
}

When the 'Click here to open' text is clicked, its parent .modal-launcher receives focus, which causes the .modal inside to change from display: none to block . The close-button works by taking focus from the .modal-launcher. You could use opacity and transition in place of display for a modal that fades in.

:target

:target enables you to select elements via the hash in the URL. An element will match :target if the hash in the URL matches its ID. Like :hover and :focus, it provides the means to target a single element on the page, but the selection isn't lost when the user moves their mouse off the element or clicks elsewhere on the page. It also means your users can share a link that contains a state your CSS will respond to.

One use of the :target selector is as a router or tabs – you can use it to hide or display sections of your site depending on the URL given. The following would work well as a basic router:

HTML

<nav>
    <a href="#one">Page one</a>
    <a href="#two">Page two</a> 
    <a href="#three">Page three</a>
</nav>

<section class="page" id="one">
    Welcome to my site! 
</section>
<section class="page" id="two"> 
    Fascinating text here
</section>

<section class="page" id="three">
    Pictures of cats 
</section>

CSS

.page {
    display: none;
}
 .page:target {
    display: block; 
}

You could combine this with the translateY method for scrolling to have links that scrollTo places in the DOM, or have things transition onto the screen.

:checked

:checked facilitates selecting checkboxes that have been checked by the user. If you use named radio buttons, :checked will be exclusive; that is, selecting one of them will deselect the others. If you give them different name attributes or use checkboxes instead of radio buttons, you can target more than one element at a time.

Being a form field, checkboxes can be triggered remotely using a label element. So, if you hide the checkbox and use a sibling selector, you can trigger any part of the DOM from anywhere else in the DOM – just put a checkbox next to the DOM tree you'd like to manipulate and you can toggle it on and off from anywhere else.

The downside is that the DOM needs to be manipulated in order to satisfy CSS demands, which isn't really how the relationship is supposed to work. The following code will produce a slideshow, with no jQuery required:

HTML

<div class="slideshow">
     <input type="radio" name="slideshow" class="slide- trigger" id="one">
     <div class="slide">
            <img src="http://placekitten.com/500/200"> 
     </div>
    <input type="radio" name="slideshow" class="slide- trigger" id="two">
    <div class="slide">
            <img src="http://placekitten.com/500/200">
    </div>
    <input type="radio" name="slideshow" class="slide- trigger" id="three">

    <div class="slide">
            <img src="http://placekitten.com/500/200">
    </div> 
</div>
<nav>
    <label for="one">1</label> 
    <label for="two">2</label> 
    <label for="three">3</label>
</nav>

CSS

.slideshow {
    position: relative; 
    width: 500px; 
    height: 200px; 
    overflow: hidden;
} 
.slide-trigger {
    display: none; 
}
.slide {
    position: absolute;
    top: 0;
    left: -500px;
    transition: left 1s ease-in-out;
}
.slide-trigger:checked + .slide {
    left: 0; 
}

All the slides start outside (-500px) the overflow: hidden container, then when a radio input next to a slide is checked, that slide is positioned inside the container. The transition property smoothes the animation.

Conclusion

In this tutorial, we've seen how you can delegate more work to CSS instead of jQuery. Keeping JavaScript light creates fewer bugs and improves performance. We've only scratched the surface here, so explore other CSS properties for yourself: just be aware of the issue of browser support.

Words: Dan Neame

This article originally appeared in net magazine issue 254.