Using CSS transitions in CSS Grid Layout

CssCss TransitionsCss Grid

Css Problem Overview


I am trying to get my sticky header to have a transition effect so it eases in rather than just a snap movement.

What am I doing wrong?

Here's my a working version:

http://codepen.io/juanmata/pen/RVMbmr

Basically the following code adds the class tiny to my wrapper class, this works fine.

$(window).on('load', function() {
    $(window).on("scroll touchmove", function () {
        $('.wrapper').toggleClass('tiny', $(document).scrollTop() > 0);
    });
});

Here's the CSS part:

.wrapper {
    grid-template-rows: 80px calc(75vh - 80px) 25vh 80px;
    -o-transition: all 0.5s;
    -moz-transition: all 0.5s;
    -webkit-transition: all 0.5s;
    transition: all 0.5s;
}
.tiny {
    grid-template-rows: 40px calc(75vh - 40px) 25vh 80px;
}

So the header does shrink as it should but there is no animation, have I missed something or does transitions simply not work with grid?

Here's a link to the css-grid docs.

https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Grid_Layout

$(window).on('load', function() {
  $(window).on("scroll touchmove", function() {
    $('.wrapper').toggleClass('tiny', $(document).scrollTop() > 0);
  });
});

* {
	margin:0;
	padding:0;
}

.wrapper {
	display: grid;
	grid-gap: 0px;
	grid-template-columns: 1fr 1fr 1fr 1fr;
	grid-template-rows: 80px calc(75vh - 80px) 25vh 80px;
	grid-template-areas:
		"head head head head"
		"main main main main"
		"leader leader leader leader"
		"foot foot foot foot";
	background-color: #fff;
	color: #444;
}
.tiny {
	grid-template-rows: 40px calc(75vh - 40px) 25vh 80px;
}
.box {
	background-color: #444;
	color: #fff;
	border-radius: 5px;
	font-size: 150%;
}
.box .box {
	background-color: lightcoral;
}

.head {
	grid-area: head;
	background-color: #999;
	z-index: 2;
	display: grid;
	grid-gap: 0px;
	grid-template-columns: 20% 1fr;
	-o-transition: all 0.5s;
	-moz-transition: all 0.5s;
	-webkit-transition: all 0.5s;
	transition: all 0.5s;
	position: sticky;
	top: 0;
}

.logo{
        height: inherit;
        grid-column: 1;
        grid-row: 1;
        background-color:red;
        position: relative;
        overflow: hidden;
    }
.logo img {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        display: block;
        max-width: 100%;
        height: auto;
    }
.main {
	grid-area: main;
	/* CSS Grid */
	z-index: 1;
	grid-column: head-start / head-end;
	grid-row: head-start / leader-start;
	background-color: red;
}
.leader {
	grid-area: leader;
	z-index:1;
	display: grid;
	grid-gap: 0px;
	grid-template-columns: repeat(4, 1fr  );
}
.foot {
	grid-area: foot;
	z-index:1;
}

<div class="wrapper">
  <div class="box head">
    <div class="box logo">
      <a href="#"><img src="https://unsplash.it/200/300/?random" /></a>
    </div>
    <div class="box nav">nav</div>
  </div>
  <div class="box main">main</div>
  <div class="box leader">
    <div class="box leader-1">1</div>
    <div class="box leader-2">2</div>
    <div class="box leader-3">3</div>
    <div class="box leader-4">4</div>
  </div>
  <div class="box foot">foot</div>
</div>

Css Solutions


Solution 1 - Css

According to the spec, transitions should work on grid-template-columns and grid-template-rows.

> 7.2. Explicit Track Sizing: the grid-template-rows and > grid-template-columns > properties > > Animatable: as a simple list of length, percentage, or calc, provided > the only differences are the values of the length, percentage, or calc > components in the list

So, if my interpretation is correct, as long as the only changes are to the values of the properties, with no changes to the structure of the rule, transitions should work. But they don't.


UPDATE

> This does work but is so far only implemented in Firefox. Follow here > for other browser updates. > https://codepen.io/matuzo/post/animating-css-grid-layout-properties

~ a contribution in the comments by @bcbrian


Here's a test I created:

grid-container {
  display: inline-grid;
  grid-template-columns: 100px 20vw 200px;
  grid-template-rows: repeat(2, 100px);
  background-color: black;
  height: 230px;
  transition: 2s;
  
  /* non-essential */
  grid-gap: 10px;
  padding: 10px;
  box-sizing: border-box;
}

grid-container:hover {
  grid-template-columns: 50px 10vw 100px;
  grid-template-rows: repeat(2, 50px);
  background-color: red;
  height: 130px;
  transition: 2s;
}

grid-item {
  background-color: lightgreen;
}

<grid-container>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
  <grid-item></grid-item>
</grid-container>

jsFiddle demo

In the test, the transition works on the height and background color, but not on grid-template-rows or grid-template-columns.

Solution 2 - Css

As a workaround, you can work with the size of the grid items instead with grid-template-columns or grid-template-rows.

You could do:

grid-container {
  display: inline-grid;
  grid-template-columns: 100px 20vw 200px;
  background-color: black;
  height: 230px;
  transition: 2s;
  
  /* non-essential */
  grid-gap: 10px;
  padding: 10px;
  box-sizing: border-box;
}

grid-container:hover {
  background-color: red;
  height: 130px;
}

grid-item {
  height: 100px;
  transition: height 2s;
  background-color: lightgreen;
}

grid-container:hover .first-row {
  height: 25px;
}

grid-container:hover .last-row {
  height: 75px;
}

<grid-container>
  <grid-item class='first-row'></grid-item>
  <grid-item class='first-row'></grid-item>
  <grid-item class='first-row'></grid-item>
  <grid-item class='last-row'></grid-item>
  <grid-item class='last-row'></grid-item>
  <grid-item class='last-row'></grid-item>
</grid-container>

Here's another 2x2 example: https://codepen.io/erik127/pen/OvodQR

and here a more extensive example where you can add more columns or rows: https://codepen.io/erik127/pen/rdZbxL

Unfortunately it's a lot of javascript, it would be nice if grid-template-columns and grid-template-rows would be animatable.

Another alternative which might work in some usecases (if your grid items don't span multiple rows) is the use of flexbox together with a grid.

Solution 3 - Css

I used GSAP to animate the grid-template-columns and grid-template-rows properties:

function changeGridTemplateColumns(pattern) {
  TweenMax.to(".container",
    1, {
      gridTemplateColumns: pattern
    }
  );
}

function changeGridTemplateRows(pattern) {
  TweenMax.to(".container",
    1, {
      gridTemplateRows: pattern
    }
  );
}

$(document).ready(
  function() {
    $(".el-a,.el-b,.el-c").mouseenter(
      function() {
        changeGridTemplateRows("2fr 1fr");
      }
    );
    $(".el-d,.el-e,.el-f").mouseenter(
      function() {
        changeGridTemplateRows("1fr 2fr");
      }
    );

    $(".el-a,.el-d").mouseenter(
      function() {
        changeGridTemplateColumns("2fr 1fr 1fr");
      }
    );

    $(".el-b,.el-e").mouseenter(
      function() {
        changeGridTemplateColumns("1fr 2fr 1fr");
      }
    );

    $(".el-c,.el-f").mouseenter(
      function() {
        changeGridTemplateColumns("1fr 1fr 2fr");
      }
    );

    $(".container").mouseleave(
      function() {
        changeGridTemplateColumns("1fr 1fr 1fr");
        changeGridTemplateRows("1fr 1fr");
      }
    );
  }
);

.container {
  width: 50vw;
  height: 50vw;
  margin: auto;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
  grid-template-areas: "a b c" "d e f";
}

.el-a {
  grid-area: a;
  background-color: skyblue;
}

.el-b {
  grid-area: b;
  background-color: darkseagreen;
}

.el-c {
  grid-area: c;
  background-color: coral;
}

.el-d {
  grid-area: d;
  background-color: gold;
}

.el-e {
  grid-area: e;
  background-color: plum;
}

.el-f {
  grid-area: f;
  background-color: beige;
}

<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="container">
  <div class="el-a"></div>
  <div class="el-d"></div>
  <div class="el-b"></div>
  <div class="el-e"></div>
  <div class="el-c"></div>
  <div class="el-f"></div>
</div>

Solution 4 - Css

For now the transition on the grid template is not working. But you can use transform like this:

jsFiddle

var button = document.querySelector("#btnToggle");
button.addEventListener("click",function(){
	var content = document.querySelector(".g-content");
  if(content.classList.contains("animate"))
    content.classList.remove("animate");
  else
	  content.classList.add("animate");
});

html,body{
  width:100%;
  height:100%;
  padding:0;
  margin:0;
}

.g-content{
  display:grid;
  height:100%;
  grid-template-columns: 200px 1fr;
  grid-template-rows:60px 1fr 30px;
  grid-template-areas: 
    "g-header g-header"
    "g-side g-main"
    "g-footer g-footer";
 overflow-x:hidden;
}

.g-header{
  grid-area:g-header;
  background:#2B4A6B;
  display:grid;
  grid-template-columns: max-content 1fr;
}

.g-header button{
  align-self:center;
  margin:0 5px;
}

.g-side{
  grid-area:g-side;
  background:#272B30;
  transition:all 0.5s;
}

.g-main{
  grid-area:g-main;
  background:#FFFFFA;
  transition:all 0.5s;
}

.g-footer{
  grid-area:g-footer;
  background:#7E827A
}

.animate .g-main{
  width:calc(100% + 200px);
  transform:translateX(-200px);
}

.animate .g-side{
  transform:translateX(-200px);
}  

<div class="g-content">
  <div class="g-header">
    <button id="btnToggle">
    Toggle
    </button>
  </div>
  <div class="g-side">
  </div>
  <div class="g-main">
  test
  </div>
  <div class="g-footer">
  </div>
</div>

Solution 5 - Css

In my case I was trying to open sidebar-menu by using this code:

.wrapper{
  display: grid;
  grid-template-columns: 0 100%;
  transition: all 1s;
  .sidebar{
    background-color: blue;
  }
  .content{
    background-color: red;
  }
}
.wrapper.sidebar-open{
  grid-template-columns: 300px 100%;
  transition: all 1s;
}

but transition was not working for grid-template-columns. This is my solution:

.wrapper{
  display: grid;
  grid-template-columns: auto 100%;
  .sidebar{
    width: 0;
    background-color: blue;
    transition: all 1s;
  }
  .content{
    background-color: red;
  }
}
.sidebar.sidebar-open{
  width: 300px;
  transition: all 1s;
}

Perhaps, it helps someone.

Solution 6 - Css

Another approach is to use transform. It actually might even be better because transform along w/ opacity can achieve 60fps because it's GPU accelerated instead of CPU accelerated (browser has to do less work).

here's an example: https://codepen.io/oneezy/pen/YabaoR

.sides {
  display: grid;
  grid-template-columns: 50vw 50vw;
}

.monkey { animation: 0.7s monkey cubic-bezier(.86,0,.07,1) 0.4s both; }
.robot { animation: 0.7s robot cubic-bezier(.86,0,.07,1) 0.4s both; }

@keyframes monkey {
  0% { transform: translate(-50vw); }
  100% { transform: 0; }
}

@keyframes robot {
  0% { transform: translate(50vw); }
  100% { transform: 0; }
}

Solution 7 - Css

I found a workaround, but it requires that at least one of the other columns fill up the width. The column to be transitioned must be auto in the grid-template-columns. And another one must be 1fr, otherwise the auto column will use this missing space. Also, the column to be transitioned must have absolute widths in both states.

Here it is:

CodePen

document.querySelector('.js-button').addEventListener('click', function() {
  document.querySelector('.js-grid').classList.toggle('grid--full')
})

.grid {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  grid-template-rows: 100px;
  grid-gap: 20px;
  transition: all 1s;
}

.item {
  background-color: #D73B38;
  color: #ffffff;
  padding: 10px;
  border-radius: 5px;
  border: none;
  transition: 1s;
}

.grid .item:nth-child(3n + 2) {
  width: 200px;
  background: blueviolet; 
}
.grid--full .item:nth-child(3n + 2) {
  width: 300px;
  background: cadetblue;
}

button {
  background-color: #123456;
  color: #ffffff;
  margin: 2rem 0;
  padding: 1.4rem;
  border: none;
  border-radius: 5px;
  text-transform: uppercase;
  font-size: 1.2rem;
}

<p>Fork from <a href="https://codepen.io/matuzo/pen/rmQvMG">Manuel Matuzovic</a></p>

<button class="js-button">Animate</button>

<div class="grid js-grid">
  <article class="item">
    <h2>Element 1</h2>
  </article>
  <article class="item">
    <h2>Element 2</h2>
  </article>
  <article class="item">
    <h2>Element 3</h2>
  </article>
  <article class="item">
    <h2>Element 4</h2>
  </article>
  <article class="item">
    <h2>Element 5</h2>
  </article>
  <article class="item">
    <h2>Element 6</h2>
  </article>
</div>

It uses the selector :nth-child(3n + 2) to do the trick in all columns of the grid. But also works if you want to change only one block of your grid.

Solution 8 - Css

After hours of trial and error, I finally found a way to animate grid columns. This causes the column gaps to go to 0 and the hovered field to expand by its width.

Here is what i got so far:

$('.cards').each(function(){
  let cols = window.getComputedStyle($(this).get(0));
  let colGapAmount = cols.getPropertyValue("grid-template-columns").split(" ").length - 1;
  let card = $(this).find('.card');
  let standardColWidth = parseInt(card.css('width'), 10);
  let colGapSpace = colGapAmount * parseInt($(this).css('column-gap'), 10);
  let newSize = (standardColWidth + colGapSpace) + 'px';
  //Set Values
  $(this).closest('.cards').get(0).style.setProperty('--scaledSize', newSize);
  $(this).closest('.cards').get(0).style.setProperty('--normalSize', standardColWidth + 'px');
});

$('.card').hover(function(){
  $(this).closest('.cards').addClass('noGap');
},
function(){
  $(this).closest('.cards').removeClass('noGap');
});

.cards {
	 --normaleSize: 100%;
	 --scaledSize: unset;
	 display: grid;
	 grid-template-columns: 1fr 1fr 1fr 1fr 1fr;
	 column-gap: 20px;
	 transition: column-gap 1s ease-in-out;
}
 .cards .card {
	 transition: width 1s ease-in-out;
	 border: 1px solid black;
	 width: var(--normalSize);
}
 .cards .card:hover {
	 width: var(--scaledSize);
}
 .cards.noGap {
	 column-gap: 0px;
}
 

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section class="cards">
  <div class="card">1</div>
  <div class="card">2</div>
  <div class="card">3</div>
  <div class="card">4</div>
  <div class="card">5</div>
</section>

<section class="cards" style="margin-top: 3rem">
  <div class="card">1</div>
  <div class="card">2</div>
  <div class="card">3</div>
  <div class="card">4</div>
  <div class="card">5</div>
</section>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDanView Question on Stackoverflow
Solution 1 - CssMichael BenjaminView Answer on Stackoverflow
Solution 2 - CssErikView Answer on Stackoverflow
Solution 3 - CssPep SantacruzView Answer on Stackoverflow
Solution 4 - CssEmre HIZLIView Answer on Stackoverflow
Solution 5 - CssDmitry GrinkoView Answer on Stackoverflow
Solution 6 - CssOneezyView Answer on Stackoverflow
Solution 7 - CssAlisson NunesView Answer on Stackoverflow
Solution 8 - CssDeadApeView Answer on Stackoverflow