Pergunta

All,

I'd like to be able to use translateX to animate a child element 100% of the way across it's parent (i.e., from the left edge to the right edge).

The challenge is that percentages in translateX refer to the element itself, not the parent.

So, for example, if my html looks like this:

<div id="parent">
    <div id="child">
</div>

And my CSS like this (vendor-prefixes omitted):

#parent {
    position: relative;
    width: 300px;
    height: 100px;
    background-color: black;
}
#child {
    position: absolute;
    width: 20px;
    height: 100px;
    background-color:red;
    transform: translateX(100%);
}

This doesn't work - the child only moves 20px (100% of itself), not all the way across the parent. (You can see this on jsfiddle):

enter image description here

I can do this:

#child {
    position: absolute;
    width: 20px;
    height: 100px;
    background-color:red;
    -webkit-transform: translateX(300px) translateX(-100%);
    transform: translateX(300px) translateX(-100%);
}

This works (seen here again on jsfiddle), because it first moves the child 300px (the full width of the parent), minus 20px (the width of the child). However, this depends on the parent having a fixed, known pixel dimension.

enter image description here

However, in my responsive design - I don't know the width of the parent, and it will change.

I know that I can use left:0 and right:0, but the animation performance of left/right is much worse than translateX (Thanks Paul Irish!).

Is there a way to do this?

Thanks in advance.

Foi útil?

Solução

I didn't post my idea originally, because it involves creating an additional HTML layer, and expected better solutions to come.

Since that hasn't happened, I explain my comment. What I meant was this:

#parent {
    position: relative;
    width: 300px;
    height: 100px;
    background-color: black;
}
#wrapper {
    position: absolute;
    width: 100%;
    height: 100px;
    border: solid 1px green;
    transition: all 1s;
}
#wrapper:hover {
    -webkit-transform: translateX(100%);
    transform: translateX(100%);
}
#child {
    position: absolute;
    width: 20px;
    height: 100px;
    background-color:red;
}
#wrapper:hover #child {
    -webkit-transform: translateX(-100%);
    transform: translateX(-100%);
}

Since the wrapper is 100% width of the parent, translating it 100% works as expected.

fiddle

Note that the wrapper is being translated 100% as you stated. However, seems that what you really want is to move the element 100% - width. To achieve this, you have to translate the child also 100% (now this applies to the child width) in the opposite direction.

Correction: the child should share the transition property of the wrapper:

#parent {
    position: relative;
    width: 300px;
    height: 100px;
    background-color: black;
}
#wrapper {
    position: absolute;
    width: 100%;
    height: 100px;
    border: solid 1px green;
    transition: all 5s;
}
#wrapper:hover {
    transform: translateX(100%);
}
#child {
    position: absolute;
    width: 50px;
    height: 100px;
    background-color:red;
    transition: inherit;
}
#wrapper:hover #child {
    transform: translateX(-100%);
}
<div id="parent">
    <div id="wrapper">
        <div id="child"></div>
    </div>
</div>

Outras dicas

There's a pretty cool solution to this problem using Flexbox. The key is to take advantage of the flex-grow property.

Say you have some HTML that looks like this:

<div class="flex-container">
  <div class="flex-spacer"></div>
  <div class="slider"></div>
</div>

First, give .flex-container the basic display: flex property, and set its flex-direction to row. Set the positioning of the child elements to relative, so they will sit next to each other inside .flex-container.

By default, the flex-grow property is set to 0, which is exactly what we want at the beginning. This means that .flex-spacer and .slider will only have their normal dimensions to begin with. We simply keep .flex-spacer empty, and it will have a width of 0.

Now for the animation. We only need two CSS rules to make it work: add a transition to .flex-spacer and set flex-grow to 1 on .flex-spacer during some event. The second rule gives all of the unused width inside .flex-container to the width of .flex-spacer, and the first rule animates the change in width. The .slider element gets pushed along to the edge of .flex-container.

The CSS looks something like this - I added a background to .flex-spacer to make its presence a little more obvious, and set flex-grow to 1 when the user hovers over .flex-container:

body * {
  box-sizing: border-box;
}

.flex-container {
  cursor: pointer;
  display: flex;
  flex-flow: row nowrap;
  width: 100%;
  border: 2px solid #444;
  border-radius: 3px;
}

.flex-spacer,
.slider {
  flex-grow: 0;
  position: relative;
}

.slider {
  padding: 25px;
  background-color: #0DD;
}

.flex-spacer {
  background-color: #DDD;
  transition: all .4s ease-out;
}

.flex-container:hover .flex-spacer {
  flex-grow: 1;
}
<div class="flex-container">
  <div class="flex-spacer"></div>
  <div class="slider"></div>
</div>

Flexbox makes this pretty configurable, too. For example, say we want .slider to move from right to left, instead. All we have to do is switch the flex-direction property in .flex-container to row-reverse, and we're done!

Feel free to play with it in this pen.

Keep in mind that things can get a little trickier if we want animations for different types of events. For example, I came across this issue when trying to animate a label when a user types in an input element. A little more HTML and CSS is needed to make it work (I used some JS, as well), but the concept is the same.

Here's another pen, this time in the context of a form with input.

I implemented this using wrapper and flex-grow:1.
Here are two animations at the same time with the same duration: 1) the container (green) moves with the car at 100% of the parent's width; 2) the car moves back -100% of its width (to stay on the track at the finish line). The duration can be taken separately and distributed to the container (.track-inner) and the car (.car)

 const goBtn = document.querySelector('.go');
    const inner = document.querySelector('.track-inner');
    const car = document.querySelector('.car');
    const durationFromServerMS = '3000ms'; 
    
    goBtn.addEventListener('click', ()=>{
      inner.classList.add('drive');
      inner.style.animationDuration = durationFromServerMS;
      car.classList.add('backShift');
      car.style.animationDuration = durationFromServerMS;
    })
    
    const backBtn = document.querySelector('.back');
    backBtn.addEventListener('click', ()=>{
      inner.classList.remove('drive');
      car.classList.remove('backShift');
    })
html, body {
      padding: 2rem;
    }
    
    .track{
      width: 50%;
      display:flex;
      position: relative;
      background-color: gray;
      width: auto;
      margin-bottom: 1rem;
      border: 5px dashed blue;
      overflow:hidden;
    }
    
    .track-inner{
      width: 100%;
      border: 5px dotted green; 
    }
    
    .car{
      width: 3rem;
      height: 1.5rem;
      border: 1px solid black;
      background-color: salmon;
    }
    
    .finish-line{
      position:absolute;
      top:0;
      right: 0.5rem;
      width: 3rem;
      height: 1.5rem;
      border-left: 6px dotted yellow;
    }
    
    button{
      padding: 0.5rem 1rem;
      background-color: lightblue;
      outline: none;
      border:none;
      margin: 0.2rem
    }
    
    button:hover{
      pointer:cursor;
      background-color: salmon;
    }
    
    
    .backShift {
      animation-name: car-back;
      /* animation-duration: 5s; */
      animation-timing-function: ease-in;
      animation-fill-mode: forwards;
    }
    
    .drive {
      animation-name: driving;
      /* animation-duration: 5s; */
      animation-timing-function: ease-in;
      animation-fill-mode: forwards;
    }
    
    
    @keyframes driving {
      0% {
        transform: translateX(0%);
      }
      100% {
        transform: translateX(100%);
      }
    }
    
    @keyframes car-back {
      0% {
        transform: translateX(0%);
      }
      100% {
        transform: translateX(-100%);
      }
    }
    
    p{
      padding:0;
    }
<div class="track">
        <div class="track-inner">
          <div class="car ">car</div>
        </div>
        <div class="finish-line">finish</div>
      </div>
    
    <div>
      <button class='go'>Go</button>
      <button class='back'>Back</button>
    </div>
    
    
    
    
    
   

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top