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();
});