Search code examples
htmlcssflexboxcss-transitionscss-grid

css width transition gives parent full width before animation begins


I'm trying to transition the width of a search box (pink) such that it smoothly displaces the search icon beside it (the blue box, .header__icon__search). However, it seems the search box begins the transition by filling up the available width of its parent, then animates its own width, so it's a disjointed experience all around. Curiously, the margin of the parent (green area on the left side of the input) gets animated smoothly. If this explanation isn't clear, another way to think of it is that I want the green and pink areas to smoothly grow and push the blue area to the left as they grow, but right now only the green pushes the blue area smoothly, the pink pushes it "instantly". How can I achieve the desired smooth effect for the pink area too?

let searching = true;

setInterval(() => {
  searching = !searching;
  document.querySelector('.header__searchBox').classList[searching ? 'remove' : 'add']('searching');
}, 1500);
* {
  box-sizing: border-box;
}

.grid {
  display: grid;
  grid-template-areas: empty animated;
  grid-template-rows: 1fr;
  grid-template-columns: repeat(2, 1fr);
}

.header__searchContainer {
  grid-area: animated;
  display: flex;
    justify-content: space-between;
    align-items: center;
    background-color: lightgreen;
    width: max-content;
}

.header__icon__search {
  display: flex;
    justify-self: center;
    justify-content: center;
    align-items: center;
    width: 1.75rem;
    height: 1.75rem;
    background-color: lightblue;
}

.header__searchBox {
  transition: all 0.75s;
  margin-left: 0;
    background-color: pink;
}

.header__searchBox.searching {
  margin-left: 1.5rem;
}

.header__searchBox input {
  --verticalPad: 0.25rem;
    border-radius: 0.5rem;
    border: none;
    width: 0;
    padding: var(--verticalPad) 0;
    transition: all 0.75s;
}

.header__searchBox.searching input {
  padding: var(--verticalPad) 0.25rem;
    width: 100%;
}
<div class="grid">
  <div class="header__searchContainer">
    <div class="header__icon header__icon__search"></div>
    <div class="header__searchBox"><input type="search" placeholder="search"></div>
  </div>
</div>


Solution

  • You're already using CSS variables for changing width and implementing animations.

    See the example below for using them in the correct way in order to achieve the animation you wanted.

    const input = document.querySelector("input");
    
    setInterval(() => {
      input.parentElement.classList.toggle("expanded");
      input.classList.toggle("expanded");
    }, 1000);
    @import "https://cdn.jsdelivr.net/gh/KunalTanwar/normalize/css/normalize.inter.min.css";
    
    body {
      height: 100%;
      display: grid;
      place-items: center;
      background-color: #131417;
    }
    
    .container {
      --base: 16px;
      --btn-size: 56px;
      --duration: 500ms;
      --max-width: calc(var(--btn-size));
      width: 100%;
      display: flex;
      overflow: hidden;
      box-shadow: 0 0 0 1px red;
      max-width: var(--max-width);
      transition: max-width var(--duration);
    }
    .container.expanded {
      --max-width: calc(var(--base) * 30);
    }
    .container button {
      border: 0;
      aspect-ratio: 1/1;
      width: var(--btn-size);
      height: var(--btn-size);
      background-color: #373c49;
    }
    .container input {
      width: 0%;
      border: 0;
      color: white;
      padding: var(--base);
      background-color: black;
      transition: width var(--duration);
    }
    .container input.expanded {
      width: 100%;
    }
    <div class="container">
      <button type="button"></button>
      <input type="text" placeholder="Search" />
    </div>