Reputation: 9830
I have a fairly simple segmented controller-like radio button setup. When a radio button gets selected, that button gets a background color applied to it.
How can I get the background color to animate to the selected radio button in css?
Like this:
input {
display: none;
}
input:checked + .label {
background-color: yellowGreen;
}
<label>
<input type="radio" name="radioBtn" checked><span class="label">First Option</span>
</label>
<label>
<input type="radio" name="radioBtn"><span class="label">Second Opetion</span>
</label>
<label>
<input type="radio" name="radioBtn"><span class="label">Third Option</span>
</label>
Update
Because of the answer shortage, I'm now open to JavaScript/JQuery. Although if you do have a pure css solution, please post it.
Upvotes: 16
Views: 8685
Reputation: 9664
Ok, Pure CSS, seems I came back late, still better than not coming back, JS Fiddle-Updated (1) (2)
Updated Code: added z-index
value to the container div#radios
(3)
body {
background: #EEE url('//www.dailyfreepsd.com/wp-content/uploads/2013/09/underwater-blurred-background.jpg');
background-size: cover;
}
#radios {
position: relative;
background-color: tomato;
z-index: 5;
width: 363px;
}
input {
display: none;
}
#bckgrnd,
.labels {
width: 120px;
height: 30px;
text-align: center;
display: inline-block;
padding-top: 10px;
margin-right: -3px;
z-index: 2;
cursor: pointer;
outline: 1px solid green;
}
#bckgrnd {
background-color: orange;
position: absolute;
left: 0;
top: 0;
z-index: -1;
}
#rad1:checked ~ #bckgrnd {
transform: translateX(0);
transition: transform 0.5s ease-in-out;
}
#rad2:checked ~ #bckgrnd {
transform: translateX(120px);
transition: transform 0.5s ease-in-out;
}
#rad3:checked ~ #bckgrnd {
transform: translateX(241px);
transition: transform 0.5s ease-in-out;
}
<div id="radios">
<input id="rad1" type="radio" name="radioBtn" checked>
<label class="labels" for="rad1">First Option</label>
<input id="rad2" type="radio" name="radioBtn">
<label class="labels" for="rad2">Second Option</label>
<input id="rad3" type="radio" name="radioBtn">
<label class="labels" for="rad3">Third Option</label>
<div id="bckgrnd"></div>
</div>
Edit:
(1) For smaller screens you can make a media query with a certain break point if below show these radios vertically, and instead of translateX()
use translateY()
.
(2) my below solution adds a div <div id="bckgrnd"></div>
as the last child of the container #radios
div, you can add by javascript/jquery instead, to do so you can add this jquery: JS Fiddle 2-Updated
$(document).ready(function(){
$('#radios').append('<div id="bckgrnd"></div>');
});
(3) The z-index:;
value was added just to ensure that the #bckgrnd
- which has z-index:-1
will not disappear behind the body
or whatever element contains the #radios
div. so now we can set a background image to the body and a background color to a container div without worrying about it.. Test JS Fiddle
Upvotes: 8
Reputation: 2733
Here is a version that uses JQuery. It is a little more complex than the other answers, but most of the complexity is due to the way I made the animation work. When the highlight changes, it stretches to its new location and then shrinks to fit. This isn't what your mockup does, but IMHO it is a smoother effect.
Its hard to explain but heres a JSFiddle that shows what I mean.
http://jsfiddle.net/mcgraphix/4qe8uz06/9/
The HTML is pretty much the same as yours except for the added highlight:
<div>
<label>
<input type="radio" name="radioBtn" ><span class="label">First Option</span>
</label>
<label>
<input type="radio" name="radioBtn" checked><span class="label">Second Option</span>
</label>
<label>
<input type="radio" name="radioBtn"><span class="label">Third Option with a long label</span>
</label>
<span class="highlight"></span>
</div>
JS:
$(document).ready(function() {
//handle to the highlight span
var hl = $('.highlight');
var initialLabel = $('input[name="radioBtn"]:checked').parent();
//highlight the correct one initially in case it isn't the first one
hl.css('width', initialLabel.css('width'));
hl.css('left', ( initialLabel.offset().left - $("label").first().offset().left) + 'px');
//add listeners
$("label").mouseup(function(event) {
//figure out what we clicked on
var selectedItem = $(this);
//figure out where the left edge of it is
var newLeft = (selectedItem.offset().left - $("label").first().offset().left);
//how much do we need to change the left coordinate
var changeAmount = Math.abs(parseInt(hl.css('left')) - newLeft);
//figure out which direction we're going
var direction = (parseInt(hl.css('left')) > newLeft) ? 'left' : 'right';
//remove all the classes to start
hl.removeClass('grow-left').removeClass('grow-right').removeClass('shrink');
//set up the new CSS
var newCss;
if (direction === 'right') {
//we're growing to the right
newCss = {
width: selectedItem.width() + selectedItem.offset().left - hl.offset().left + 'px'
};
hl.addClass('grow-right');
} else {
//we're growing to the left
newCss = {
width: hl.width() + changeAmount + 'px',
left: newLeft + 'px'
};
hl.addClass('grow-left');
}
//set the initial change
hl.css(newCss);
//wait for it to be done, then finish the change
hl.on('transitionend webkitTransitionEnd oTransitionEnd', function () {
if (direction === 'right') {
//we need to shrink to the right
newCss = {width: selectedItem.css('width'), left: newLeft + "px"};
} else {
//we need to shrink to the left
newCss = {width: selectedItem.css('width')}
}
//apply the right transition class
hl.removeClass('grow-left').removeClass('grow-right').addClass('shrink');
//apply the styles
hl.css(newCss);
//you could add a transitionend event listener to clean up the classes here
});
});
});
The most important CSS styles are the ones that apply the transition with the right easing and delay amounts:
span.highlight.grow-left {
transition: left 0.2s ease-in-out, width 0.2s ease-in-out;
}
span.highlight.grow-right {
transition: width 0.2s ease-in-out, left 0.2s ease-in-out 0.2s;
}
span.highlight.shrink {
transition: width 0.2s ease-in-out, left 0.2s ease-in-out;
}
The gist of this is that there is a "highlight" behind the labels. When you click one, it calculates the position of the one you clicked and through adding classes to apply the right animation at the right time, you get the correct animated "morphing"
Upvotes: 0
Reputation: 114599
[l1, l2, l3].forEach(function(x){
x.onclick = function() {
var r = x.getBoundingClientRect();
bg.style.left = r.left - 12 + "px";
bg.style.top = r.top - 10 + "px";
bg.style.width = r.width + 8 + "px";
bg.style.height = r.height + 4 + "px";
};
});
l1.onclick();
#container { position: relative; }
input[type="radio"] { display: none; }
input[type="radio"]+label { display: inline-block; }
#bg { background-color: #0F0;
position: absolute;
transition: all 0.25s ease-in-out;
border-radius: 1000px;
z-index: -1; }
<div id="container">
<div id="bg"></div>
<input type="radio" id="opt1" name="grp1"><label id="l1" for="opt1">Option1</label>
<input type="radio" id="opt2" name="grp1"><label id="l2" for="opt2">Option2</label>
<input type="radio" id="opt3" name="grp1"><label id="l3" for="opt3">Option3 with a very long text</label>
</div>
Upvotes: 0
Reputation: 12059
Here's something.
label{
position: relative;
height:100%;
display: block;
height: 50px;
}
[type="radio"]{
display: none;
z-index: 5;
position: relative;
}
[type="radio"] ~ span{
transition: background .3s;
height: 100%;
display: -webkit-flex;
display: flex;
-webkit-justify-content: center;
justify-content: center;
-webkit-align-items: center;
align-items: center;
padding: 0 25px;
z-index: 5;
position: relative;
}
.bg{
position: absolute;
width: 100%;
height: 100%;
background: #7df5a7;
top: 0;
z-index: 0;
left: 0;
width: 0;
transition: .3s width;
}
label:nth-child(1) .bg{
right: 0;
left: auto;
}
label:nth-child(2) .bg{
left: 0;
}
[type="radio"]:checked + .bg{
width: 100%;
}
.wrap{
border:1px solid #ddd;
display: inline-flex;
overflow:hidden;
border-radius: 50px;
}
<div class="wrap">
<label>
<input type="radio" name="radioBtn" checked>
<div class="bg"></div>
<span class="label">First Option</span>
</label>
<label>
<input type="radio" name="radioBtn">
<div class="bg"></div>
<span class="label">Second Option</span>
</label>
</div>
Upvotes: 5
Reputation: 12672
Probably will need some tweaking to look right and stuff, but the general idea is here:
input {
display: none;
}
#rdb1:checked ~ #back {
left:0 !important;
}
#rdb2:checked ~ #back{
left:15vw !important;
}
#rdb3:checked ~ #back{
left:30vw !important;
}
label{
width:15vw;
float:left;
}
#back{
background-color: yellowGreen;
left:0;
position:fixed;
width:15vw;
height:12pt;
z-index:-1;
transition:left 500ms
}
<div>
<input type="radio" name="radioBtn" id="rdb1" checked>
<label for="rdb1">First Option</label>
<input type="radio" name="radioBtn" id='rdb2'>
<label for="rdb2">Second Opetion</label>
<input type="radio" name="radioBtn" id='rdb3'>
<label for="rdb3">Third Option</label>
<div id="back"></div>
</div>
The built in snippet thing makes it look glitchier than it really is, it's better in the fiddle: http://jsfiddle.net/Lybmk9hd/1/
Upvotes: 1