A better toggle button, with jQuery and CSS3

One of my more popular articles on this blog has been my jQuery toggle button article. However, my thinking has changed a lot since writing that post and, approaching the problem now, I’d likely do things differently. There were a few key flaws in my approach:

  • Using an additional image for the border. border-radius was still new at the time, but even so I think it would have been better to develop for the bleeding edge, and then deal with fallback for older tech.
  • Using jQuery animate. While I like Javascript as a control mechanism for animations, Javascript-based animations are another matter entirely. CSS3 transitions and transformations are a much better solution, they’re easier to handle and performance is, almost always, much better. Again, this was because I was avoiding the bleeding edge and focusing too much on backward compatibility, as transition and transform were still very new at the time.
  • Not making it a jQuery plugin. There was so much of a dependency on jQuery for selection, modifying the DOM, etc. that wrapping everything in a jQuery plugin made sense. My thinking was always that by not being bound too closely to jQuery, you can always just rip-out the jQuery bits and have a slim, pure JS component. However, there are 2 problems with this idea:
    • it’s not always that easy to rip-out jQuery, and doing so usually means re-creating much of the abstraction and utility functions offered by jQuery
    • plugin or not, you can still end up coupling heavily with jQuery
  • Widgetizing the HTML/CSS. The flaw here is writing out HTML and inline CSS from a Javascript string to create the button, which is usually not a good idea. It’s usually better to leave markup and styling in the document, where they can be readily manipulated or re-style. This is in line with my rant about Enyo (and widget frameworks in general), where I mentioned that widgets lead to a significant loss of flexibility. There is a case to be made for a toggle button widget, as it enables you to automatically replace checkboxes with toggle buttons without modifying any HTML but, in the general, I don’t think it’s worth it.

Below is an updated toggle button accompanied by the code that creates it. As in my previous post, the button itself is a single image, applied as a background-image to an anchor tag, and state transitions are done by shifting the background-position of the element.

Live Demo

HTML

<!-- toggle button markup -->
<a id="btn-toggle" class="btn-toggle" href="#">
    <
input name="yesno" type="checkbox" checked="checked" />
</
a>

CSS

/* toggle button style, styled for initial state (off) */
a.btn-toggle { margin:0; padding:0; display:block; border-radius:32px; background:url(base.png) -57px 0px no-repeat transparent; width:98px; height:64px; }

/* toggle button active state (on) */
a.btn-toggle.active { background-position:-7px 0px; border-color:#40A1EC; }

/* hide the underlying input checkbox of the toggle button */
a.btn-toggle input { display:none; }

Javascript

// jQuery plugin for toggle button
(function( $ ){

    $.fn.makeToggleButton =
function() {
        
    
this.each(function() {
            
            
var elem = $(this);
                    
            
// get the state of the underlying input checkbox
            
elem.val = function() {
                
return elem.find('input').is(':checked');
            }
                    
            
// function to toggle button state
            
elem.toggle = function() {
                    
                
var chkbx = elem.find('input');                    
                
if (!chkbx.is(':checked')) { // not check, switch on
                        
                    
elem.addClass('active');
                    chkbx.attr(
'checked', true);
                }
                
else {

                    elem.removeClass(
'active');
                    chkbx.attr(
'checked', false);
                }
            }
                    
            
// click handler
            
elem.click(function(e) {                    
                elem.toggle();
                e.preventDefault();                    
            });
                    
            
// adjust state to initial value of input checkbox
            
if(elem.find('input').is(':checked'))
            {
                elem.addClass(
'active');
            }
                    
            
// setTimeout to prevent transition upon setting initial state to active
            
setTimeout(function() {
                elem.css(
'transition', 'background-position 0.4s');
                elem.css(
'-webkit-transition', 'background-position 0.4s');
                elem.css(
'-moz-transition', 'background-position 0.4s');
                elem.css(
'-o-transition', 'background-position 0.4s');
                elem.css(
'-ms-transition', 'background-position 0.4s');
            }, 50 );
                
        });


    
return this;
            
    };
})( jQuery );        


// make toggle button when document is ready
$(document).ready(function() {
    $(
'#btn-toggle').makeToggleButton();
});