Will Thomson
Will Thomson

Reputation: 885

CSS Transition item in and out of flexbox smooth animation

I'm building basic navigation for a website, and the graphical/animated functionality that I would like is this:

1: When in the 'HOME' state, the site logo/icon is large and centered and above the rest of the navigation/page links.

2: When on any other page than home, the logo/icon is smaller, and sits proportionally in a horizontal flexbox with the other elements.

3: There is a smooth transition between these two states.

I've implemented this already as you can see below, but the transition is jittery when page is full sized. I am new to both CSS and Angular, so forgive my ignorance in advance.

My question:

Is there a better way to implement a into/out-of flexbox animation than i've provided here, and/or is there a CSS animation optimization that I am missing to smooth this transition further?

The end product will all have a similar background color, but I have chosen bright colors in this illustration to show what the divs are doing in the background.

Note: I've tried using 'will-change' to no real effect, and I'm aware that I can do this without using angular, but there are features beyond this simple animation that will utilize angular down the road.

Here is a fiddle for it too: https://jsfiddle.net/willcthomson/f6wk4Lpq/

/* angular */ 
function mainCtrl($scope) { 

  		$scope.ng_div_home = 'div_home_scale_up';
      $scope.ng_div_logo = 'div_logo_scale_up';
      $scope.ng_div_logo_upper_container = 'div_logo_upper_container_scale_up';
      $scope.ng_logo = '';
      $scope.ng_ISHOME = true;

  		$scope.f_scale = function()
  				{
            if(!$scope.ng_ISHOME)
              {
              $scope.ng_div_home = "div_home_scale_down";
              $scope.ng_div_logo = "div_logo_scale_down";
              $scope.ng_div_logo_upper_container = 'div_logo_upper_container_scale_down';
              }
            else
              {
              $scope.ng_div_home = "div_home_scale_up";
              $scope.ng_div_logo = "div_logo_scale_up";
              $scope.ng_div_logo_upper_container = 'div_logo_upper_container_scale_up';
              }
            console.log("ishome is:"+$scope.ng_ISHOME);
  				};


}
/* css */
html{
  position: relative;
  min-height: 100%;
  background-color: #222222;
  }

body{
  background-color: #00000000;
  }


/*a container that sits on top of the nav bar and grows vertically to make room fo the expanding logo-icon*/
.div_logo_upper_container 
  {
    /* background-color: #111111; */
    background-color: magenta;
    width: 80%;
    margin: auto;
    transition: height .5s linear;
    margin-top: 5vw;
  }

/*a flex container to hold nav elements*/
.nav_container 
  {
    /* background-color: #111111; */
    background-color: yellow;
    width: 80%;
    
    margin: auto;
    display: flex;    
    
    align-items: stretch;
    align-items: flex-end;
 
  }
  

/*a div that scales forcing the other flex elements respond */
.div_home
    {
      background-color: #00000000;
      background-color: cyan;
      margin: auto;
      transition: .5s linear;
      will-change: transform, width, flex,;
    }

/*a div that holds the logo, and does the actual scaling and movement*/
.div_logo    
    {
      padding: 3px;
      margin: 3px;
      margin-left: 2vw;
      transition:  .5s linear;
      will-change: width;
    }

.img_logo   
    {
    max-width:100%;
    max-height: 100%;
    min-width: 8%;
    min-height: 8%;
    will-change:width;
    will-change:height;
    }

/*expands the home container amongst the other flex elements to make room for incoming logo*/
.div_home_scale_down
  {
    width: 10vw;
    flex: .5 1 40px;
  }    

/*shrinks the home container to allow flex elements to expand into the created gap*/
.div_home_scale_up
  {
    width: 3px;
    transform: translate(25vw,-10vh);
    align-self: center;
  }    

/*expands the logo container*/
.div_logo_scale_up
  {
    width: 30vw;
    margin-top: 5vh;
  }

/*shrinks the logo container*/
.div_logo_scale_down
  {
    margin: 1vh;
    width: 10vw;  
    position: static;

  }

/*expands the area above the nav container to contain the larger icon*/
.div_logo_upper_container_scale_up
  {
    height: 10vh;
  }  

/*shrinks the upper container as the icon files in with other elements  */
.div_logo_upper_container_scale_down
  {
    height: 1px;
  }  

.nav_items
    {
    flex: 1 1 10px;
    margin-bottom: 2vh;

    font-family: verdana;
    font-size: 30px;
    color:grey;
    text-align: center;
    margin: 3px;
    transition: font-size, color, transform, .5s linear;
    }  
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!--html-->
<div ng-app>
<div ng-controller="mainCtrl">

<div class="div_logo_upper_container" ng_class="ng_div_logo_upper_container"></div>
<div class="nav_container">
    <div class = "div_home" ng-class="ng_div_home">
      <div class = "div_logo" ng-class="ng_div_logo" ng-click="ng_ISHOME=true;f_scale();">
        <img  class = "img_logo" src="https://avatars1.githubusercontent.com/u/579414?s=200&v=4" > 
      </div>
    </div>
  	<div  class = "nav_items">  	
  		<p ng-click="ng_ISHOME=false;f_scale();">Two</p>
  	</div>
  	<div class = "nav_items">
  		<p ng-click="ng_ISHOME=false;f_scale();">Three</p>
  	</div>
  	<div class = "nav_items">
  		<p ng-click="ng_ISHOME=false;f_scale();">Four</p>
  	</div>
    
  </div> <!-- end nav_container -->
  </div> <!-- end ctrl -->
  </div> <!-- end app -->

asdf

Upvotes: 1

Views: 3004

Answers (1)

tao
tao

Reputation: 90188

The animation is "jittery" because you are animating too many properties at once and they affect more than Paint and Composite layers.

If you find the above statement vague, please refer to these two articles:

The generally accepted solution for this type of animations is to have one element in DOM for each of the two animation states. They should both be positioned (any position value other than static will do). You'll use transform to animate the starting state element towards the position and size of the target state element. By getting .getBoundingClientRect() on each, you can determine the required transforms needed to make the exact transition).

By far, the biggest advantage of this method is that the element being animated remains at the same position in document flow and, by not being resized or moved, it doesn't trigger reflow on subsequent DOM elements or parents. Only its rendered projection is moved.

Once the animation ends, you toggle both elements's visibility and remove the transforms from the one you just animated (now hidden - you want to set animation-duration:0s; for this change), in order to reset the rendered projection back to its normal place in DOM.

Upvotes: 1

Related Questions