CSS animations and z-index - css

I'm working on a JavaScript game similar to "2048." It has a tabular display of "tiles" made up of several layers of divs. I'd like to make the selected tile "bounce" using a CSS animation. (The bouncing needs to be able to happen in each of the four cardinal directions: bounce up, down, left, or right, depending on the state of the game.)
My problem is that when I attach an animation, I see the content correctly bounce "in front of" tiles up and left of the selected one, but it incorrectly bounces "behind" the tiles to its right and below it. Here's a JSFiddle illustrating the problem:
https://jsfiddle.net/atqwc8er/2/
I'd like the bouncing blue tile "2" to show up in front of tile "3", the same way it already shows up in front of tile "1". Can anyone help me make it do that?
.tile {
position: absolute;
width: 50px;
height: 50px;
background-color: red;
}
.tile-inner {
text-align: center;
z-index: 10;
}
.tile-1-0 {
transform: translate(0px, 0px);
}
.tile-1-1 {
transform: translate(55px, 0px);
}
.tile-1-2 {
transform: translate(110px, 0px);
}
.selected .tile-inner {
background-color: blue;
animation: back-and-forth 0.5s infinite alternate;
z-index: 99; /* not working */
}
#keyframes back-and-forth {
from {
transform: translateX(-25px);
}
to {
transform: translateX(25px);
}
}
<div class="tile-container">
<div class="tile tile-1-0">
<div class="tile-inner">1</div>
</div>
<div class="tile tile-1-1 selected">
<div class="tile-inner">2</div>
</div>
<div class="tile tile-1-2">
<div class="tile-inner">3</div>
</div>
</div>

increase the z-index of .selected instead
.tile {
position: absolute;
width: 50px;
height: 50px;
background-color: red;
}
.tile-inner {
text-align: center;
z-index: 10;
}
.tile-1-0 {
transform: translate(0px, 0px);
}
.tile-1-1 {
transform: translate(55px, 0px);
}
.tile-1-2 {
transform: translate(110px, 0px);
}
.selected .tile-inner {
background-color: blue;
animation: back-and-forth 0.5s infinite alternate;
}
.selected {
z-index: 99;
}
#keyframes back-and-forth {
from {
transform: translateX(-25px);
}
to {
transform: translateX(25px);
}
}
<div class="tile-container">
<div class="tile tile-1-0">
<div class="tile-inner">1</div>
</div>
<div class="tile tile-1-1 selected">
<div class="tile-inner">2</div>
</div>
<div class="tile tile-1-2">
<div class="tile-inner">3</div>
</div>
</div>
Related question to get more details and understand why it cannot work with .title-inner: Why can't an element with a z-index value cover its child?

The z-index attribute should be added to the element at the same level, so pls add the z-index attribute to .title-1-1 instead of .title-inner

Related

why is my css animation not staying in the same place?

When my page is loading, the logo animation starts in the bottom right corner of the screen, even though my logo element is positioned absolute. I really don't understand why this is happening and what to do about it.
Here's my code:
HTML:
<div class="intro position-relative">
<header class="company-name border border-5 p-2 p-sm-4 position-absolute start-50 translate-middle">
<h1 class="lh-1">BAKKES</h1>
<h2 class="lh-1">herenkappers en baardkwekers</h2>
</header>
</div>
CSS:
.company-name {
top: 45%;
animation: fade-names 1s linear forwards;
animation-delay: 0.5s;
opacity: 0;
}
#keyframes fade-names {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
Here's a link so you can see what happens: https://amazing-austin-69ee4a.netlify.app/
You can do these codes.
I looked at your website.
.intro .company-name {
top:45%; // remove this
}
.start-50 {
left: 50% !important; // remove this
}
.translate-middle {
-webkit-transform: translate(-50%, -50%) !important; // remove this
transform: translate(-50%, -50%) !important; // remove this
}
.intro {
display: flex; // add this
justify-content: center; // add this
align-items: center; // add this
}

css animation transform - keep arbitrary rotation

I have a CSS animation, for example, like this:
#keyframes my-animation {
0% { opacity: 0; visibility: visible; transform: scale(0,0); }
50% { transform: scale(1.15, 1.15); }
100% { transform: none; }
}
And I want to apply it to a DIV that has an arbitrary rotation e.g. like this:
<div style="width:100px; height:100px; transform: rotate(45deg)"/>
When I apply the CSS animation, keyframes have another transform attribute that only sets scale. As a result, my DIV is rotated back to 0 during the animation and, at the end, it is restored back to 45 degree rotation.
But I want it to keep its arbitrary original rotation. So the question is: is there a way to specify in transform property of the keyframes that it should keep existing (arbitrary) rotation?
Something like transform: scale(1.15, 1.15) rotate(keep) ?
Use CSS variables
.x {
transform: rotate(var(--r,0deg));
height: 50px;
width: 50px;
display:inline-block;
background: green;
animation: my-animation 5s;
margin: 20px;
}
#keyframes my-animation {
0% {
opacity: 0;
transform: scale(0) rotate(var(--r,0deg));
}
50% {
transform: scale(1.15) rotate(var(--r,0deg));
}
}
<div class="x" style="--r:80deg"></div>
<div class="x" ></div>
<div class="x" style="--r:60deg"></div>
Or like below so you can append any transformation to the one defined in the keyframes
:root {
--r: rotate(0deg); /* Use any null transform (ex: translate(0), skew(0deg), etc)*/
}
.x {
transform: var(--r);
height: 50px;
width: 50px;
display:inline-block;
background: green;
animation: my-animation 5s;
margin: 20px;
}
#keyframes my-animation {
0% {
opacity: 0;
transform: scale(0) var(--r);
}
50% {
transform: scale(1.15) var(--r);
}
}
<div class="x" style="--r:rotate(80deg) skew(20deg)"></div>
<div class="x" ></div>
<div class="x" style="--r:rotate(60deg) translate(20px,20px)"></div>
Here's a simple solution without variables - I would just wrap your div and do the scaling on the wrapper, keeping the inner div rotated arbitrarily. Trivial, but does the trick I think.
.box {
height: 50px;
width: 50px;
background: green;
margin: 50px;
}
.scale-me {
animation: my-animation;
animation-duration: 10s;
}
#keyframes my-animation {
0% {
opacity: 0;
transform: scale(0);
}
50% {
transform: scale(1.15);
}
}
<div class="scale-me">
<div class="box" style="transform: rotate(45deg)"></div>
<div class="box" style="transform: rotate(60deg)"></div>
<div class="box" style="transform: rotate(120deg)"></div>
</div>

CSS slider with arrows using animation

I am trying to achieve a CSS only slider.
When hovering left and right arrows, the slider has to slide. Of course.
I tried something using animation-play-state, animation-fill-mode (to keep the positions) and animation-direction but I'm not able to fully make it work.
Starting with animation-play-state: paused, hovering the arrows changes it to running.
On hover of the right arrow, everything is fine. We can hover, leave, hover again.
But, as soon as I hover the left arrow (that changes the animation-direction to reverse), it's broken.
Simplified snippet:
.wrapper {
overflow: hidden;
position: relative;
width: 500px;
}
.arrows {
z-index: 2;
position: absolute;
top: 0;
height: 100%;
background: #ddd;
opacity: 0.66;
}
.arrows:hover {
opacity: 1;
}
.arrow-l {
left: 0;
}
.arrow-r {
right: 0;
}
.sliding {
height: 160px;
width: 2000px;
background: linear-gradient(to bottom right, transparent 49.9%, gray 50.1%);
animation: slide 2s linear;
animation-play-state: paused;
animation-fill-mode: both;
}
.arrows:hover~.sliding {
animation-play-state: running;
}
.arrow-l:hover~.sliding {
animation-direction: reverse;
}
#keyframes slide {
0% {
transform: translate(0px, 0);
}
100% {
transform: translate(-1500px, 0);
}
}
<div class="wrapper">
<div class="arrows arrow-l">[ ← ]</div>
<div class="arrows arrow-r">[ → ]</div>
<div class="sliding"></div>
</div>
Can someone help me understand what is happening, and correct this unwanted behaviour?
The main issue here is that changing the direction will keep the current state of the animation BUT it will consider the new direction. Let's take an easy example:
Suppose you have an animation from left:0 to left:100%. If you first run the animation untill left:80% and then you change the direction to reverse you will have left:20%!
Why?
Because with the default direction you reached the 80% (left:80%) of the animation and 80% of the same animation with reverse direction is simply left:20%.
Hover on reverse and you will see that the position of the box is jumping to switch to the new state considering the new direction. It's obvious when the animation ends and you will be switching between the first and last state:
.sliding {
width:100px;
height:100px;
background:red;
left:0%;
position:relative;
animation:slide 5s linear forwards;
animation-play-state:paused;
}
.arrows {
margin:20px;
}
.arrow-r:hover~.sliding {
animation-play-state: running;
}
.arrow-l:hover~.sliding {
animation-direction: reverse;
}
#keyframes slide {
0% {
left: 0%;
}
100% {
left: 100%;
}
}
<div class="wrapper">
<div class="arrows arrow-r">move normal</div>
<div class="arrows arrow-l">reverse !!</div>
<div class="sliding"></div>
</div>
There is no fix for this since it's the default behavior of animation, but instead you can rely on transition to obtain a similar effect. The trick is to play with the duration that you increase/decrease to create the needed effect.
Here is an idea:
.wrapper {
overflow: hidden;
position: relative;
width: 500px;
}
.arrows {
z-index: 2;
position: absolute;
top: 0;
height: 100%;
background: #ddd;
opacity: 0.66;
}
.arrows:hover {
opacity: 1;
}
.arrow-l {
left: 0;
}
.arrow-r {
right: 0;
}
.sliding {
height: 160px;
width: 2000px;
background: linear-gradient(to bottom right, transparent 49.9%, gray 50.1%);
transition:all 2000s linear; /*This will block the current state*/
}
.arrow-r:hover ~ .sliding {
transform: translate(-1500px, 0);
transition:all 2s;
}
.arrow-l:hover ~ .sliding {
transform: translate(0px, 0);
transition:all 2s;
}
<div class="wrapper">
<div class="arrows arrow-l">[ ← ]</div>
<div class="arrows arrow-r">[ → ]</div>
<div class="sliding"></div>
</div>

Transition right after animation

On my element, I have a CSS animation running as long as it has a certain class (wiggle), and a transition as soon as it has a different one (right):
<div class="outer">
<div class="inner wiggle"></div>
</div>
#keyframes wiggle {
from {
left: 10%;
}
50% {
left: 30%;
}
to {
left: 10%;
}
}
.inner {
left: 0;
&.wiggle {
animation: wiggle 2s infinite;
}
&.right {
left: 90%;
transition: left 2s;
}
}
Now, if remove the wiggle class and add right at the same time, the transition doesn't play out; left: 90% applies immediately. However, if there's a delay between removing the former and adding the latter, the transition will happen as expected.
Here's a JSFiddle illustrating the issue.
It looks like when coming from an animation, values (such as left in this case) don't have an explicit value to transition from, so they're just rendered to their final state.
Is this expected behavior, i.e. is it part of a specification? Or are browsers free how to handle that case?
I've tested on the lastest versions of Firefox and Chromium.
Clarification: I'm not mainly looking for workarounds, especially not complicated ones, but more for a reason why exactly browsers behave like they do.
I think this may be a bug in browser rendering or so, but If you want a solution I can give you one alternative method with transform
A working fiddle for you:
$('#toggle').click(() => {
$('.inner').removeClass('wiggle').addClass('right');
});
#keyframes wiggle {
from {
left: 10%;
}
50% {
left: 30%;
}
to {
left: 10%;
}
}
.outer {
height: 5em;
background-color: black;
position: relative;
}
.inner {
height: 100%;
width: 10%;
position: absolute;
transform: translate(0%);
background-color: green;
transition: transform 2s ease-in-out;
}
.inner.wiggle {
animation: wiggle 2s infinite;
}
.inner.right {
transform: translate(900%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>
Animated
</h2>
<div class="outer">
<div class="inner wiggle"></div>
</div>
<h2>
Not animated
</h2>
<div class="outer">
<div class="inner"></div>
</div>
<hr>
<button id="toggle">
move right
</button>
I hope this was helpful for you.
Its look it is the expected behavior. I think when you are adding the right class with left:90%, its not able to pick the starting value for the left css property. As an alternate you can create another keyframe for the .right class
$('#toggle').click(() => {
$('.inner').removeClass('wiggle').addClass('right');
});
#keyframes wiggle {
0% {
left: 10%;
}
50% {
left: 30%;
}
100% {
left: 10%;
}
}
#keyframes right {
100% {
left: 90%;
}
}
.outer {
height: 5em;
background-color: black;
position: relative;
}
.inner {
height: 100%;
width: 10%;
position: absolute;
left: 0;
background-color: green;
transition: left 2s;
}
.wiggle {
animation: wiggle 2s infinite;
}
.right {
animation: right 2s forwards;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>
Animated
</h2>
<div class="outer">
<div class="inner wiggle"></div>
</div>
<h2>
Not animated
</h2>
<div class="outer">
<div class="inner"></div>
</div>
<hr>
<button id="toggle">
move right
</button>
This workaround might help you, the idea is to use the css variables, the idea is to set the animation with an alternate function, so in this way we will only need From and To... on the other hand we need to set up and event for every iteration, in that way we know when an iteration ends,
So when we click the button you can change the variable value and remove the class...
$('#toggle').click(() => {
$('.inner').addClass('right');
});
let flag = false;
$(".inner").on("animationiteration webkitAnimationIteration oAnimationIteration MSAnimationIteration", function(){
flag && $(".inner").removeClass('wiggle');
if($(".inner").hasClass('right')) flag = true;
});
:root {
--from: 10%;
--to: 30%;
}
#keyframes wiggle {
from {
left: var(--from);
}
to {
left: var(--to);
}
}
.outer {
height: 5em;
background-color: black;
position: relative;
}
.inner {
height: 100%;
width: 10%;
position: absolute;
transform: translate(0%);
background-color: green;
transition: transform 2s ease-in-out;
left:90%;
}
.inner.wiggle {
left:10%;
animation: wiggle 1s infinite;
animation-direction: alternate;
}
.inner.right {
--to:90%;
animation-direction: normal;
animation-duration:2s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<h2>
Animated
</h2>
<div class="outer">
<div class="inner wiggle"></div>
</div>
<hr>
<button id="toggle">
move right
</button>
Consider that this has a bug when the animation is moving back to the start point...

Transform-origin issue

I'm trying to rotate object using: transform: rotate(90deg); and applying transform-origin: right bottom;. But there I get different transformation behavior depending on where I place transform-origin: either to parent element or to :hover state.
Why does those differences happens?
div {
width: 100px;
height: 100px;
transition: all 1s ease-in;
}
.main:hover {
transform: rotate(90deg);
transform-origin: right bottom;
}
.main2 {
transform-origin: right bottom;
}
.main2:hover {
transform: rotate(90deg);
}
/*********BG colors*******/
.main, .main2 {
background: green;
}
.wrapper {
background: red;
float: left;
margin-right: 20px;
}
<div class="wrapper">
<div class="main">Transform-origin on :hover state</div>
</div>
<div class="wrapper transform">
<div class="main2">Transform-origin on parent element</div>
</div>
With .main2, you're initializing transform-origin to right bottom, but with .main, you're setting the origin on :hover. Because of this, CSS is trying to tween between the default origin (the center of the element) and right bottom - creating this odd effect.

Resources