Jessica
Jessica

Reputation: 9830

Create segmented control-like with animation

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:

enter image description here

JSFiddle

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

Answers (5)

Mi-Creativity
Mi-Creativity

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

mcgraphix
mcgraphix

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

6502
6502

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

pizzarob
pizzarob

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

markasoftware
markasoftware

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

Related Questions