Search code examples
javascripthtmlcsscss-transitionscss-transforms

How do I remove the bounce effect when translating away from the screen with css transition?


It's kind of hard to explain in words what I'm looking for without a visual example, so here's the basic idea: https://codepen.io/jwiggiff/full/xxwvVbr

var cube = $('.cube');
var wrapper = $('.wrapper');

$(document).mousemove((e) => {
  if(!cube.hasClass('locked')) {
    var deltaX = e.pageX - (wrapper.offset().left+(wrapper.width()/2));
    var deltaY = e.pageY - (wrapper.offset().top+(wrapper.height()/2));
    var rotateY = deltaX/($(document).width()/2) * 90;
    var rotateX = deltaY/(($(document).height()/2)) * -90;
    cube.css("transform", " rotateY("+rotateY+"deg) rotateX("+rotateX+"deg)");
  }
});

$('.top-label').click((e) => {
  cube.toggleClass('locked locked-bottom');
  $('.labels div:not(.top-label)').fadeToggle();
});
$('.bottom-label').click((e) => {
  cube.toggleClass('locked locked-top');
  $('.labels div:not(.bottom-label)').fadeToggle();
});
$('.left-label').click((e) => {
  cube.toggleClass('locked locked-right');
  $('.labels div:not(.left-label)').fadeToggle();
});
$('.right-label').click((e) => {
  cube.toggleClass('locked locked-left');
  $('.labels div:not(.right-label)').fadeToggle();
});
/* Variables */
:root {
  --cube-scale: 60vh;
}


/* Styles */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: "Montserrat", sans-serif;
  overflow: hidden;
}

.cube div {
  padding: 10vh;
  border: 5px solid blue;
  transition: all 500ms ease-in-out;
}

.cube div h1 {
  font-family: "Carter One", cursive;
}

.cube div p {
  font-size: 14px;
}


/* Labels */
.labels {
  color: black;
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.labels div {
  transition: all 500ms ease-in-out;
}

.left-label {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translate(0, -50%);
  padding: 20px 10px;
}

.right-label {
  position: absolute;
  top: 50%;
  right: 0;
  transform: translate(0, -50%);
  padding: 20px 10px;
}

.top-label {
  position: absolute;
  top: 0;
  left: 50%;
  width: 100%;
  text-align: center;
  transform: translate(-50%, 0);
  padding: 10px 20px;
}

.bottom-label {
  position: absolute;
  bottom: 0;
  left: 50%;
  width: 100%;
  text-align: center;
  transform: translate(-50%, 0);
  padding: 10px 20px;
}


/* 3D Cube */
.wrapper {
  width: var(--cube-scale);
  height: var(--cube-scale);
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  perspective: calc(var(--cube-scale)*4);
}

.cube {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
}

.cube.locked.locked-bottom {
  transition: all 500ms ease-in-out;
  transform: rotateX(90deg) translateY(calc(var(--cube-scale) / -2)) !important;
}
.locked-bottom .bottom {
  height: 100vh;
  width: 100vw;
  transform: rotateX(-90deg) translateZ(calc(var(--cube-scale) / 2)) translate(calc(((100vw/2) - (var(--cube-scale)/2)) * -1), calc(((100vh/2) - (var(--cube-scale)/2)) * -1));
}

.cube.locked.locked-top {
  transition: all 500ms ease-in-out;
  transform: rotateX(-90deg) translateY(calc(var(--cube-scale) / 2)) !important;
}
.locked-top .top {
  height: 100vh;
  width: 100vw;
  transform: rotateX(90deg) translateZ(calc(var(--cube-scale) / 2)) translate(calc(((100vw/2) - (var(--cube-scale)/2)) * -1), calc(((100vh/2) - (var(--cube-scale)/2)) * -1));
}

.cube.locked.locked-right {
  transition: all 500ms ease-in-out;
  transform: rotateY(-90deg) translateX(calc(var(--cube-scale) / -2)) !important;
}
.locked-right .right {
  height: 100vh;
  width: 100vw;
  transform: rotateY(90deg) translateZ(calc(var(--cube-scale) / 2)) translate(calc(((100vw/2) - (var(--cube-scale)/2)) * -1), calc(((100vh/2) - (var(--cube-scale)/2)) * -1));
}

.cube.locked.locked-left {
  transition: all 500ms ease-in-out;
  transform: rotateY(90deg) translateX(calc(var(--cube-scale) / 2)) !important;
}
.locked-left .left {
  height: 100vh;
  width: 100vw;
  transform: rotateY(-90deg) translateZ(calc(var(--cube-scale) / 2)) translate(calc(((100vw/2) - (var(--cube-scale)/2)) * -1), calc(((100vh/2) - (var(--cube-scale)/2)) * -1));
}

.cube div {
  transform-origin: calc(var(--cube-scale)/2) calc(var(--cube-scale)/2);
	position: absolute;
	width: var(--cube-scale);
	height: var(--cube-scale);
}

.front {
  background: rgba(255,0,0,0.7);
  transform: rotateY(0deg) translateZ(calc(var(--cube-scale) / 2));
}

.right {
  background-color: rgba(0,255,0,0.7);
  transform: rotateY(90deg) translateZ(calc(var(--cube-scale) / 2));
}

.back {
  background-color: rgba(0,0,255,0.7);
  transform: rotateY(180deg) translateZ(calc(var(--cube-scale) / 2));
}

.left {
  background-color: rgba(255,165,0,0.7);
  transform: rotateY(-90deg) translateZ(calc(var(--cube-scale) / 2));
}

.top {
  background-color: rgba(128,0,128,0.7);
  transform: rotateX(90deg) translateZ(calc(var(--cube-scale) / 2));
}

.bottom {
  background-color: rgba(255,0,255,0.7);
  transform: rotateX(-90deg) translateZ(calc(var(--cube-scale) / 2));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="wrapper">
  <div class="cube">
    <div class="front"></div>
    <div class="back"></div>
    <div class="left"></div>
    <div class="right"></div>
    <div class="top"></div>
    <div class="bottom"></div>
  </div>
</div>
<div class="labels">
  <div class="left-label"><span>Right</span></div>
  <div class="right-label"><span>Left</span></div>
  <div class="top-label"><span>Bottom</span></div>
  <div class="bottom-label"><span>Top</span></div>
</div>

In the codepen, if you click the text on the edge of the screen, it moves the current face to cover the entire window. When you click the left or right text, it has the expected result. However, if you click the top or the bottom text, you can see the cube has a bit of an arc when translating away from the screen, whereas the left/right ones do not. Any ideas as to what is causing this bounce/arc and how to fix it would be much appreciated!


Solution

  • Add rotateY(0) to .cube.locked.locked-bottom and to .cube.locked.locked-top to avoid this issue.

    It's a bit tricky but this is due to the fact that you are applying a dynamic transformation using rotateY(a) rotateX(b) and this will not transition the same way to rotateX() translateZ() and to rotateY() translateZ(). If you make rotateX() translateZ() equal to rotateY(0) rotateX() translateZ() you will have the same transition:

    var cube = $('.cube');
    var wrapper = $('.wrapper');
    
    $(document).mousemove((e) => {
      if(!cube.hasClass('locked')) {
        var deltaX = e.pageX - (wrapper.offset().left+(wrapper.width()/2));
        var deltaY = e.pageY - (wrapper.offset().top+(wrapper.height()/2));
        var rotateY = deltaX/($(document).width()/2) * 90;
        var rotateX = deltaY/(($(document).height()/2)) * -90;
        cube.css("transform", " rotateY("+rotateY+"deg) rotateX("+rotateX+"deg)");
      }
    });
    
    $('.top-label').click((e) => {
      cube.toggleClass('locked locked-bottom');
      $('.labels div:not(.top-label)').fadeToggle();
    });
    $('.bottom-label').click((e) => {
      cube.toggleClass('locked locked-top');
      $('.labels div:not(.bottom-label)').fadeToggle();
    });
    $('.left-label').click((e) => {
      cube.toggleClass('locked locked-right');
      $('.labels div:not(.left-label)').fadeToggle();
    });
    $('.right-label').click((e) => {
      cube.toggleClass('locked locked-left');
      $('.labels div:not(.right-label)').fadeToggle();
    });
    /* Variables */
    :root {
      --cube-scale: 60vh;
    }
    
    
    /* Styles */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    
    body {
      font-family: "Montserrat", sans-serif;
      overflow: hidden;
    }
    
    .cube div {
      padding: 10vh;
      border: 5px solid blue;
      transition: all 500ms ease-in-out;
    }
    
    .cube div h1 {
      font-family: "Carter One", cursive;
    }
    
    .cube div p {
      font-size: 14px;
    }
    
    
    /* Labels */
    .labels {
      color: black;
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
    
    .labels div {
      transition: all 500ms ease-in-out;
    }
    
    .left-label {
      position: absolute;
      top: 50%;
      left: 0;
      transform: translate(0, -50%);
      padding: 20px 10px;
    }
    
    .right-label {
      position: absolute;
      top: 50%;
      right: 0;
      transform: translate(0, -50%);
      padding: 20px 10px;
    }
    
    .top-label {
      position: absolute;
      top: 0;
      left: 50%;
      width: 100%;
      text-align: center;
      transform: translate(-50%, 0);
      padding: 10px 20px;
    }
    
    .bottom-label {
      position: absolute;
      bottom: 0;
      left: 50%;
      width: 100%;
      text-align: center;
      transform: translate(-50%, 0);
      padding: 10px 20px;
    }
    
    
    /* 3D Cube */
    .wrapper {
      width: var(--cube-scale);
      height: var(--cube-scale);
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      perspective: calc(var(--cube-scale)*4);
    }
    
    .cube {
      width: 100%;
      height: 100%;
      position: relative;
      transform-style: preserve-3d;
    }
    
    /*here*/
    .cube.locked.locked-bottom {
      transition: all 500ms ease-in-out;
      transform:rotateY(0) rotateX(90deg) translateY(calc(var(--cube-scale) / -2)) !important;
    }
    .locked-bottom .bottom {
      height: 100vh;
      width: 100vw;
      transform: rotateX(-90deg) translateZ(calc(var(--cube-scale) / 2)) translate(calc(((100vw/2) - (var(--cube-scale)/2)) * -1), calc(((100vh/2) - (var(--cube-scale)/2)) * -1));
    }
    
    /* here */
    .cube.locked.locked-top {
      transition: all 500ms ease-in-out;
      transform:rotateY(0) rotateX(-90deg) translateY(calc(var(--cube-scale) / 2)) !important;
    }
    .locked-top .top {
      height: 100vh;
      width: 100vw;
      transform:rotateY(0) rotateX(90deg) translateZ(calc(var(--cube-scale) / 2)) translate(calc(((100vw/2) - (var(--cube-scale)/2)) * -1), calc(((100vh/2) - (var(--cube-scale)/2)) * -1));
    }
    
    .cube.locked.locked-right {
      transition: all 500ms ease-in-out;
      transform: rotateY(-90deg) translateX(calc(var(--cube-scale) / -2)) !important;
    }
    .locked-right .right {
      height: 100vh;
      width: 100vw;
      transform: rotateY(90deg) translateZ(calc(var(--cube-scale) / 2)) translate(calc(((100vw/2) - (var(--cube-scale)/2)) * -1), calc(((100vh/2) - (var(--cube-scale)/2)) * -1));
    }
    
    .cube.locked.locked-left {
      transition: all 500ms ease-in-out;
      transform: rotateY(90deg) translateX(calc(var(--cube-scale) / 2)) !important;
    }
    .locked-left .left {
      height: 100vh;
      width: 100vw;
      transform: rotateY(-90deg) translateZ(calc(var(--cube-scale) / 2)) translate(calc(((100vw/2) - (var(--cube-scale)/2)) * -1), calc(((100vh/2) - (var(--cube-scale)/2)) * -1));
    }
    
    .cube div {
      transform-origin: calc(var(--cube-scale)/2) calc(var(--cube-scale)/2);
    	position: absolute;
    	width: var(--cube-scale);
    	height: var(--cube-scale);
    }
    
    .front {
      background: rgba(255,0,0,0.7);
      transform: rotateY(0deg) translateZ(calc(var(--cube-scale) / 2));
    }
    
    .right {
      background-color: rgba(0,255,0,0.7);
      transform: rotateY(90deg) translateZ(calc(var(--cube-scale) / 2));
    }
    
    .back {
      background-color: rgba(0,0,255,0.7);
      transform: rotateY(180deg) translateZ(calc(var(--cube-scale) / 2));
    }
    
    .left {
      background-color: rgba(255,165,0,0.7);
      transform: rotateY(-90deg) translateZ(calc(var(--cube-scale) / 2));
    }
    
    .top {
      background-color: rgba(128,0,128,0.7);
      transform:  rotateX(90deg) translateZ(calc(var(--cube-scale) / 2));
    }
    
    .bottom {
      background-color: rgba(255,0,255,0.7);
      transform: rotateX(-90deg) translateZ(calc(var(--cube-scale) / 2));
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <div class="wrapper">
      <div class="cube">
        <div class="front"></div>
        <div class="back"></div>
        <div class="left"></div>
        <div class="right"></div>
        <div class="top"></div>
        <div class="bottom"></div>
      </div>
    </div>
    <div class="labels">
      <div class="left-label"><span>Right</span></div>
      <div class="right-label"><span>Left</span></div>
      <div class="top-label"><span>Bottom</span></div>
      <div class="bottom-label"><span>Top</span></div>
    </div>

    Related question to get more details about how interpolation is done between transform: Weird behavior when rotating an element on hover