tschoessi tschoessi - 1 month ago 9
Less Question

Horizontally collapse input with display: flex

I have two search boxes appearing on the top of a (mobile) page:

start state

The desired behavior on focus (i.e. click inside one of the boxes) is:


  • the selected box expands

  • the other box collapses horizontally

  • a close icon expands on the right.



Here is what it looks like after the transition:

end state

This is the code:

<div class="navigation--mobile__search">
<div class="header-searchfield" id="kurssuche">
<input type="text" class="header-searchfield__input" placeholder="Search1">
<a href="">
<span class="icon icon--lupe">
<svg class="icon__svg">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/svg/svg-symbol.svg#lupe"></use>
</svg>
</span>
</a>
</div>
<div class="header-searchfield" id="volltextsuche">
<input type="text" class="header-searchfield__input" placeholder="Search2">
<a href="">
<span class="icon icon--lupe">
<svg class="icon__svg">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/svg/svg-symbol.svg#lupe"></use>
</svg>
</span>
</a>
</div>
<span class="icon icon--close-x header-searchfield__close-button" style="display: none;">
<svg class="icon__svg">
<use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="/assets/svg/svg-symbol.svg#close-x"></use>
</svg>
</span>
</div>


the corresponding LESS-Classes:

.navigation--mobile__search {
background: @common-quarks-color--cd-anthracite;
display: flex;
padding: .625rem .75rem;
position: relative;
.header-searchfield {
display: flex;
flex: 1;
&__input {
flex-grow: 1;
padding: .5625rem 2rem .5625rem .8125rem;
}
&__close-button {
flex: 0 0 1.8rem;
color: white;
display: none;
margin: .7rem 0 .7rem .5rem;
svg {
fill: @searchfield--border-color;
height: .8125rem;
width: .8125rem;
}
}
}
}


And the javascript:

$(function () {
$("#kurssuche")
.focusin(function() {
$("#volltextsuche").hide(500);
$(".header-searchfield__close-button").show(500);
$(".navigation--mobile__search-results").animate({height: "400px"}, 500);
})
.focusout(function() {
$("#volltextsuche").show(500);
$(".header-searchfield__close-button").hide(500);
$(".navigation--mobile__search-results").animate({height: "0"}, 500);
});
$("#volltextsuche")
.focusin(function() {
$("#kurssuche").hide(500);
$(".header-searchfield__close-button").show(500);
$(".navigation--mobile__search-results").animate({height: "400px"}, 500);
})
.focusout(function() {
$("#kurssuche").show(500);
$(".header-searchfield__close-button").hide(500);
$(".navigation--mobile__search-results").animate({height: "0"}, 500);
});
$(".header-searchfield__close-button").on('click', function() {
$(".header-searchfield__input").val("");
});
});


Problem

For the desired behaviour of horizontally collapsing, I usually would just grab the div with the input, that is not selected and animate width to 0. This does not work, because the wrapper div is
display: flex
- I tried to experiment with the
flex:
-Attribute but it didn't seem to work. Is there a trick to animate flex-divs to width 0?

Fiddle: https://jsfiddle.net/amear6x0/

Answer

You can actually use just CSS to achieve what you are attempting to do. The trick is the following:

  1. Use classes to indicate the shown/hidden states of individual components. We can still use jQuery to toggle these classes. We can then lose the cumbersome jQuery .aninmation() function ;)
  2. Toggle between width of 0 and 100% for the input elements and the close button depending on the state. The transition of the widths can be smoothly animated by the browser. I have chosen to use a duration of 500ms (as you have indicated in your jQuery code), and to tween just the width property using the ease-in-out timing function. You can of course define any bezier curves you want.
  3. Use focus and blur functions instead of focusin and focusout—but that's just my personal preference

Some extra tips:

  • Use box-sizing: 0 to prevent extra paddings of the input elements from taking up additional space
  • Use overflow: hidden to hide overflowing content, when element parent widths are collapsed to 0.
  • For extensibility, avoid repeating the same functions for elements that perform the same purpose. I can collapse the hide/show toggle for each input element you have into a single function, just by using a combination of .closest() and .siblings() to perform context-dependent filtering of elements.

Here's a proof-of-concept example (see updated fiddle here):

$(function() {
  $('.header-searchfield input')
    .on('focus', function() {
    
      // Context-dependent filtering to reduce code redundancy :)
      // Search for siblings of parent wrapper, and hide them
      $(this).closest('.header-searchfield').siblings('.header-searchfield').addClass('hide');
    
      // Show the close button
      $('.header-searchfield__close-button').addClass('show')
    })
    .on('blur', function() {
    
      // Context-dependent filtering to reduce code redundancy :)
      // Search for siblings of parent wrapper, and show them
      $(this).closest('.header-searchfield').siblings('.header-searchfield').removeClass('hide');
    
      // Hide the close button
      $('.header-searchfield__close-button').removeClass('show');
    });

  $('.header-searchfield__close-button').on('click', function() {
    $('.header-searchfield__input').val('');
  });
});
* {
  box-sizing: border-box;
}
.navigation--mobile__search {
  background: #28373c;
  display: flex;
  padding: .625rem .75rem;
  position: relative;
}
.navigation--mobile__search .header-searchfield {
  flex: 1 1 auto;
  width: 100%;
  overflow: hidden;
  transition: .5s width ease-in-out;
}
  .navigation--mobile__search .header-searchfield.hide {
    width: 0;
  }
.navigation--mobile__search .header-searchfield__input {
  padding: .5625rem 2rem .5625rem .8125rem;
}
.navigation--mobile__search .header-searchfield__close-button {
  flex: 0 0 auto;
  color: white;
  margin: .7rem 0 .7rem .5rem;
  width: 0;
  overflow: hidden;
  transition: .5s width ease-in-out;
}
  .navigation--mobile__search .header-searchfield__close-button.show {
    width: 1.8rem;
  }
.navigation--mobile__search .header-searchfield__close-button svg {
  fill: #fff;
  height: .8125rem;
  width: .8125rem;
}

.header-searchfield {
  position: relative;
  margin: 0 .3125rem;
}
.header-searchfield__input {
  background: none;
  border: 1px solid #fff;
  border-radius: 1rem;
  padding: .5rem 2rem .5rem .8125rem;
  color: #fff;
  font-size: .8125rem;
  width: 100%;
}
.header-searchfield__input:focus {
  outline: 0 none;
  box-shadow: none;
}
.icon {
  width: 0.875rem;
  height: 0.875rem;
  fill: currentColor;
  display: inline-block;
}
.icon__svg {
  height: 100%;
  vertical-align: top;
  width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="navigation--mobile__search">
  <div class="header-searchfield" id="kurssuche">
    <input type="text" class="header-searchfield__input" placeholder="Search1">
  </div>
  <div class="header-searchfield" id="volltextsuche">
    <input type="text" class="header-searchfield__input" placeholder="Search2">
  </div>
  <span class="icon header-searchfield__close-button">
    <svg class="icon__svg">
      <path xmlns="http://www.w3.org/2000/svg" d="M7.4 6l4.3-4.3a1 1 0 0 0 0-1.4 1 1 0 0 0-1.4 0L6 4.7 1.7.3A1 1 0 0 0 .3.3a1 1 0 0 0 0 1.4L4.6 6 .3 10.3a1 1 0 0 0 0 1.4 1 1 0 0 0 1.4 0L6 7.3l4.3 4.4a1 1 0 0 0 1.4-1.4z"/>
    </svg>
  </span>
</div>