A better toggle button, with jQuery and CSS3
Feb 17 2013 · Web Design
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();
});