CSS animation (position) not smooth - css

I made a subtle background movement, but the effect isn't that pleasing. While moving it kinda jerks, you can see the image move pixel by pixel.
How can I change this so it becomes a nice smooth animation.
The Code (https://jsfiddle.net/38tf0j21/):
body {
padding:50px;
margin 0;
height:100vh;
}
.landing_img_container {
position: absolute;
right: 0;
width: calc(100% - 100px);
height: 100%;
animation-name: start_animation;
animation-delay: 1s;
animation-direction: normal;
animation-duration: 5s;
animation-iteration-count: infinite;
animation-timing-function: ease;
}
.landing_img {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
background-color:red;
}
#keyframes start_animation {
0% {
left: 100px;
}
100% {
left: 50px;
}
}
<div class="landing_img_container">
<div class="landing_img"></div>
</div>

This should work like you expect it to: https://jsfiddle.net/38tf0j21/2/
This is using the transform property instead of left

For smooth CSS position animations you need use transform: translate3d property.
See this example

Related

Strange CSS3 animation issue in IE11 and Edge

I made a CSS3 animation, it works well in Firefox and Chrome, but it behaves differently in IE11 and Edge.
I couldn't fix the issue because it's hard to debug CSS3 Animation using IE Developer Tools. This issue also occurs on Edge (But i think my Edge version is outdated so please try to reproduce this issue only in IE11. The fix will probably work on both).
Here is how i want the animation to look (Works on Chrome/Firefox):
Here is how it animates differently on IE11:
Code:
HTML:
<div class="block"></div>
<span>click</span>
CSS:
span {
width: 50px;
height: 50px;
background: red;
font-size: 50px;
}
.block {
position: fixed;
height: 0%;
bottom: 0;
width: 100%;
top: auto;
display: block;
background-color: #0B0B0B;
z-index: 99999;
animation-fill-mode: both;
animation-duration: 2s;
animation-timing-function: ease-in-out;
}
.animate-up {
animation-name: overlayEffectUp;
}
#keyframes overlayEffectUp {
0% {
bottom: 0;
top: auto;
height: 0%;
}
35%,
65% {
height: 100%;
}
100% {
bottom: auto;
top: 0;
height: 0%;
}
}
JavaScript (With jQuery):
$('span').on('click', function() {
$('.block').addClass('animate-up')
})
Here is the Demo link: https://jsfiddle.net/zoq9h7xp/3/
Please, any help would be appreciated!
Edge seems to be buggy with position: fixed. Supposedly the switch between top: 0 and top: auto (and same story with the bottom property) causes this behaviour.
If you must maintain the fixed position, you can try to animate with the transform property. Change your rulesets as follow:
#keyframes overlayEffectUp {
0% {
transform: translateY(100%); // totally offscreen
}
35%,
65% {
transform: translateY(0%); // totally on screen from bottom
}
100% {
transform: translateY(-100%); // totally off screen again to top
}
}
.block {
position: fixed;
top:0;
bottom:0;
transform: translateY(100%);
width: 100%;
background-color: #0B0B0B;
z-index: 99999;
animation-fill-mode: both;
animation-duration: 2s;
animation-timing-function: ease-in-out;
}
Hope this helps.
Cheers, Jeroen

Opacity Animation for background-image

I'm currently attempting to get the background image of #InnerImage to fade out. Here is the code for #InnerImage:
<div id="InnerImage" style="background-image:url('imgurl.com'););background-repeat:no-repeat;background-position:50% 0%;">
Here's the code that I'm using:
#OuterImage #InnerImage {
-webkit-animation: 3s ease 0s normal forwards 1 fadein;
animation: 3s ease 0s normal forwards 1 fadein;
}
#keyframes fadein{
0% { opacity:0; }
66% { opacity:0; }
100% { opacity:1; }
}
#-webkit-keyframes fadein{
0% { opacity:0; }
66% { opacity:0; }
100% { opacity:1; }
}
I'm running into an issue where the code is making every other child(?) div within #InnerImage fade out as well, but I only want the background-image to fade.
I have two questions:
1) I did read that it was not possible for background-image opacity changes that the above code is performing. Is there a work around for this?
2) How do I go about making it so that after the image has been faded in, it fades back out in an infinite loop?
[EDIT]
#OuterImage #InnerImage{
-webkit-animation: 3s ease 0s normal forwards 1 fadein;
animation: 3s ease 0s normal forwards 1 fadein;
animation-iteration-count: infinite;
}
#keyframes fadein{
0% { opacity:0; }
66% { opacity:0; }
100% { opacity:1; }
}
#-webkit-keyframes fadein{
0% { opacity:0; }
66% { opacity:0; }
100% { opacity:1; }
}
#OuterImage #InnerImage::before {
background: url('imgurl.com') no-repeat center left;
content: "";
position: absolute;
/* the following makes the pseudo element stretch to all sides of host element */
top: 0;
right: 0;
bottom: 0;
left: 0;
transition: opacity 1s ease 2s;
z-index: 1;
}
#OuterImage #InnerImage {
position: relative;}
#OuterImage #InnerImage * {
position: relative;
z-index: 2;
}
#OuterImage #InnerImage
Answer to your first question:
Put the background-image on a pseudo element ::before instead:
#InnerImage::before {
background: url('imgurl.com') no-repeat center left;
content: "";
position: absolute;
/* the following makes the pseudo element stretch to all sides of host element */
top: 0; right: 0; bottom: 0; left: 0;
z-index: 1;
}
This requires to set position: relative; on #InnerImage:
#InnerImage {
position: relative;
}
and you need to make sure all other child elements are above the pseudo element using z-index (which only applies the way you need if you position those elements):
#InnerImage * {
position: relative;
z-index: 2;
}
Notice: #OuterImage #InnerImage can be safely shortened to #InnerImage since there may be only one element on a page with any given id value anyway. Also I'd advise not to use id selectors in CSS unless you know for sure why you are doing it.
Regarding your animation, it seems like you want it to start only after two seconds have gone by. This can be achieve using a transition like this:
transition: opacity 1s ease 2s;
where 1s is transition-duration and 2s is transition-delay.
https://developer.mozilla.org/en/docs/Web/CSS/transition
Example:
#InnerImage::before {
background: url(http://lorempixel.com/300/200) no-repeat center left;
content: "";
position: absolute;
/* the following makes the pseudo element stretch to all sides of host element */
top: 0;
right: 0;
bottom: 0;
left: 0;
transition: opacity 1s ease 2s;
z-index: 1;
}
#InnerImage {
position: relative;
width: 300px;
height: 200px;
}
#InnerImage * {
position: relative;
z-index: 2;
}
#InnerImage:hover::before {
opacity: 0.25;
}
<div id="InnerImage">
<h2>Hey!</h2>
<button>noop</button>
</div>
If you want a permanently on-going fadein-fadeout, you'll have to go with an animation instead of a transition.
#InnerImage::before {
background: url(http://lorempixel.com/300/200) no-repeat center left;
content: "";
position: absolute;
/* the following makes the pseudo element stretch to all sides of host element */
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 1;
animation: 3s ease 0s normal forwards 1 fadein;
animation-iteration-count: infinite;
}
#InnerImage {
position: relative;
width: 300px;
height: 200px;
}
#InnerImage * {
position: relative;
z-index: 2;
}
#keyframes fadein{
0% { opacity:0; }
50% { opacity: 1; }
100% { opacity:0; }
}
<div id="InnerImage">
<h2>Hey!</h2>
<button>noop</button>
</div>
To animate in an infinite loop you can use the animation-iteration-count property and set the value to infinite.
#OuterImage #InnerImage {
-webkit-animation: 3s ease 0s normal forwards 1 fadein;
animation: 3s ease 0s normal forwards 1 fadein;
animation-iteration-count: infinite;
}
Changing the opacity of an element will effect all child elements there is no way around that.
A work around you may consider is to create a element inside #InnerImage that solely handles the background. You set the background div to be position absolute, with a z-index of 0, then animate only this div. That way the other elements will not change in opacity as the animation changes.
#InnerImage {
height:200px;
position:relative;
}
.bg {
position: absolute;
height: 100%;
width: 100%;
background: red;
z-index: 0;
animation-name: fadein;
animation-duration: 6s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
}
.content {
position: relative;
}
#keyframes fadein{
0% { opacity:0; }
50% { opacity:1; }
100% { opacity:0; }
}
<div id="InnerImage">
<div class="bg"></div>
<div class="content">other content</div>
</div>
Note in the example the text content does not fade while the background does

Improve CSS3 background-position animation's performance

I'm trying to improve a CSS3 animation as it seems the current code is causing some excessive CPU load and the browser seems to be laggy.
What can I do? I've got all the vendor prefixes etc. I'm not sure I can improve the code or refactor it to use it as best code practices.
Fiddle Demo
.wrapper {
width: 960px;
height: 140px;
margin-top: 80px;
position: relative;
}
.content:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: "";
-webkit-transform: translateZ(0);
transform: translateZ(0);
-webkit-transform-origin: 50% 50% 0;
-ms-transform-origin: 50% 50% 0;
transform-origin: 50% 50% 0;
v -webkit-animation-name: sideupscroll;
animation-name: sideupscroll;
/*animation-duration*/
-webkit-animation-duration: 80s;
animation-duration: 80s;
/*animation-timing-function*/
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
/*animation-iteration-count*/
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
background: url("http://i.imgur.com/wNna7D3.png") repeat fixed 0 0 indigo;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
/* Safari and Chrome */
#-webkit-keyframes sideupscroll {
0% {
background-position: 0 0;
}
50% {
background-position: -50% -100%;
}
100% {
background-position: -100% -200%;
}
}
#keyframes sideupscroll {
0% {
background-position: 0 0;
}
50% {
background-position: -50% -100%;
}
100% {
background-position: -100% -200%;
}
}
<div class="wrapper">
<div class="content"></div>
</div>
Reason
Animating the background-position of an element is always going to be resource intensive and it has a high probability of causing laggy animations in almost all browsers. This is because, a change to the background-position results in a repaint + a composition in all browsers (+ it also results in re-layout in Webkit). Because of the need to perform so many costly operations, the result is always laggy.
Snippet with problem:
The below snippet is the same as your fiddle (without vendor prefixes). Run this snippet and inspect it using Chrome Dev tools after enabling "Show Paint Rects" option. You would see a red or green color box on top of the element (this is the paint rect) and the box will either keep blinking constantly or stay colored for the entire duration of the animation. It indicates that a repaint is happening often and thus it impacts performance.
In Firefox, the paint rects can be seen by enabling nglayout.debug.paint_flashing in about:config page (set it to true).
.wrapper {
width: 960px;
height: 140px;
margin-top: 80px;
position: relative;
}
.content:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 100%;
height: 100%;
content: "";
transform: translateZ(0);
transform-origin: 50% 50% 0;
animation-name: sideupscroll;
animation-duration: 80s;
animation-timing-function: linear;
animation-iteration-count: infinite;
background: url("http://i.imgur.com/wNna7D3.png") repeat fixed 0 0 indigo;
animation-fill-mode: both;
}
#keyframes sideupscroll {
0% {
background-position: 0 0;
}
50% {
background-position: -50% -100%;
}
100% {
background-position: -100% -200%;
}
}
<div class="wrapper">
<div class="content"></div>
</div>
Solution
It is always better to avoid animating the background-* properties (all of which are visual properties) and use properties like transform. Using transform produces better performance atleast in Blink (Chrome) and EdgeHTML as Blink only does a re-composition while EdgeHTML triggers a re-layout only for the first time (1st update within animation).
Snippet without problem: (or atleast much lesser impact on performance in Blink and EdgeHTML)
The below snippet uses transform property (translateX and translateY) to achieve what is very similar to your expected output (but not the same). If you inspect this snippet with dev tools, you'd see that the green box (paint rect) appears only once at the start of the animation. Post that, the browsers only perform compositing and hence the performance is much better.
.wrapper {
width: 960px;
height: 140px;
margin-top: 80px;
position: relative;
overflow: hidden;
}
.content:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 200%;
height: 400%;
content: "";
background: url("http://i.imgur.com/wNna7D3.png") 0 0 indigo;
background-repeat: repeat;
}
.content {
position: relative;
height: 100%;
width: 100%;
animation-name: sideupscroll;
animation-duration: 80s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-fill-mode: both;
}
#keyframes sideupscroll {
0% {
transform: translateX(0%) translateY(0%);
}
50% {
transform: translateX(-50%) translateY(-100%);
}
100% {
transform: translateX(-100%) translateY(-200%);
}
}
<div class="wrapper">
<div class="content"></div>
</div>
What about Gecko and Webkit?
Unfortunately there doesn't to be a solution for browsers using these rendering engines as at the time of writing. The only option seems to be to reduce the animation-duration. A reduction in animation's duration means that the no. of re-paint + re-layout + re-composition cycles required is lesser and thus the animation's performance is better.
The below snippet looks less laggy in Firefox as the duration is only 20s.
.wrapper {
width: 960px;
height: 140px;
margin-top: 80px;
position: relative;
overflow: hidden;
}
.content:before {
position: absolute;
z-index: 1;
top: 0;
left: 0;
width: 200%;
height: 400%;
content: "";
background: url("http://i.imgur.com/wNna7D3.png") 0 0 indigo;
background-repeat: repeat;
}
.content {
position: relative;
height: 100%;
width: 100%;
animation-name: sideupscroll;
animation-duration: 20s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-fill-mode: both;
}
#keyframes sideupscroll {
0% {
transform: translateX(0%) translateY(0%);
}
50% {
transform: translateX(-50%) translateY(-100%);
}
100% {
transform: translateX(-100%) translateY(-200%);
}
}
<div class="wrapper">
<div class="content"></div>
</div>
Useful Links:
CSS Triggers - Lists out which properties result in which operations being triggered.
HTML5 Rocks - Accelerated Rendering in Chrome - Explains how accelerated rendering works in Chrome (and how to enable "Show Paint Rects" option)
Note: As I had already stated above, the animation is not 100% the same as what you had in question but in my opinion this is about the closest you could get.

CSS Transition after animation ends

I have a css transition that moves an element on hover and an animation that rotates the element on hover too. There's a delay on the animation equal to the transition duration so that after it's transitioned to it's correct position, the animation starts. And it works nice, however, when we mouse off, the animation stops but it doesn't transition back down.
Is it possible to get it to transition back after we mouse off and the animation ends?
You can see an example here: http://codepen.io/jhealey5/pen/zvXBxM
Simplified code here:
div {
width: 200px;
height: 200px;
margin: 40px auto;
background-color: #b00;
position: relative;
&:hover {
span {
transform: translateY(-60px);
animation-name: rotate;
animation-duration: 1s;
animation-delay: .5s;
animation-iteration-count: infinite;
animation-direction: alternate;
}
}
}
span {
position: absolute;
width: 20px;
height: 20px;
background-color: #fff;
bottom: 10px;
left: 0;
right: 0;
margin: auto;
transition: .5s;
}
#keyframes rotate {
from {
transform: translateY(-60px) rotate(0);
}
to {
transform: translateY(-60px) rotate(-90deg);
}
}
I have forked your project and adapted it so it works. You can find it here.
What I have changed is the following:
I give the white square a start position of top: 150px and let it, on hover of div, get a top: 0. The span gets a transition: top .5s and with that it goes to top: 0; on hover and back to top: 150px; when the mouse leaves.
I have removed the translateY(-60px); from the animation, because that would move it even more up when the animation would start.
Here's your new CSS:
div {
width: 200px;
height: 200px;
margin: 40px auto;
background-color: #b00;
position: relative;
&:hover {
span {
top: 0px;
animation: rotate 1s infinite .5s alternate;
animation-direction: alternate;
}
}
}
span {
position: absolute;
width: 20px;
height: 20px;
background-color: #fff;
bottom: 10px;
left: 0;
right: 0;
top: 150px;
margin: auto;
transition: top .5s;
}
#keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(-90deg);
}
}
Edit: The problem is that an animation is time-based and not action-based, which means that as soon as you trigger an animation, a timer starts running and it will run through all the keyframes until the set time has passed. Hover-in and hover-out have no effect, except that the timer can be stopped prematurely, but the animation will not continue (or reversed, which you wanted) after that. transition is action-based, which means it gets triggered every time an action (for example :hover) is happening. On :hover, this means it takes .5s to go to top:0 and when the hover ends, it takes .5s to got to top:150px.
I hope the above addition makes sense :)
As you can see, I also cleaned up a bit in your animation-name: etc., since it can be combined into one line.
As Harry pointed out, the problem is that you are animating/transitioning the same property, in this case transform. It looks like the current versions of Chrome/FF will allow the animation to take control of the property, thereby breaking the transition. It seems like the only way to work around this is to transition/animation a different property. Since you need to continue rotating the element, you could translate/position the element by changing the bottom property instead. I know that doesn't produce the exact same results, but nonetheless, it does move the element (just not relative to the parent element).
Updated Example
div:hover span {
bottom: 80px;
}
As an alternative, you could also wrap the span element, and then translate that element instead.
In the example below, the .wrapper element is transitioned to translateY(-60px) on hover, and then the child span element is rotated and maintains the animation.
Example Here
div {
width: 200px;
height: 200px;
margin: 40px auto;
background-color: #b00;
position: relative;
}
div:hover .wrapper {
transform: translateY(-60px);
}
div:hover .wrapper span {
animation-name: rotate;
animation-duration: 1s;
animation-delay: .5s;
animation-iteration-count: infinite;
animation-direction: alternate;
}
.wrapper {
display: inline-block;
transition: .5s;
position: absolute;
bottom: 10px;
left: 0;
right: 0;
text-align: center;
}
.wrapper span {
display: inline-block;
width: 20px;
height: 20px;
background-color: #fff;
}
#keyframes rotate {
from {
transform: rotate(0);
}
to {
transform: rotate(-90deg);
}
}
<div>
<span class="wrapper">
<span></span>
</span>
</div>

Final position after CSS3 animation doesn't stick when using vh units and resizing browser window

I have an image that is absolutely positioned using vh units. I want to animate this positioning use CSS. When doing so, however, the relative nature of vh units seems to be lost. To illustrate, look at the following two examples. In both of them, drag the bottom of your browser up and down to change its height.
No animation
The positioning adjusts correctly in relation to the screen height.
http://codepen.io/maxedison/pen/jPOQPW
#mountain {
position: absolute;
left: 50%;
top: 55vh;
opacity: 1;
}
img {
width: 180vh;
margin-left: -50%;
}
<div id="screen1">
<div id="mountain">
<img src="http://upload.wikimedia.org/wikipedia/commons/2/20/Red_Slate_Mountain_1.jpg">
</div>
</div>
Animation
The positioning does NOT adjust at all. It's like the vh unites have turned into static px, maintaining the same distance from the top of the window regardless of screen height.
http://codepen.io/maxedison/pen/QbWJbj
#mountain {
position: absolute;
left: 50%;
top: 100vh;
opacity: 0;
-webkit-animation: lincoln_page_load 2s ease forwards;
animation: lincoln_page_load 2s ease forwards;
}
img {
width: 180vh;
margin-left: -50%;
}
#-webkit-keyframes lincoln_page_load {
to {
opacity: 1;
top: 55vh
}
}
#keyframes lincoln_page_load {
to {
opacity: 1;
top: 55vh
}
}
<div id="screen1">
<div id="mountain">
<img src="http://upload.wikimedia.org/wikipedia/commons/2/20/Red_Slate_Mountain_1.jpg">
</div>
</div>
Any ideas on how to correct this? I know I can resort to JavaScript to make this work :)
This is only a problem when the animation is paused with forwards and the animation is still active:
Place the top: 55vh in #mountain so that when the animation ends it has this value and remove the opacity: 0
Remove forwards so that the animation is completed
Add the opacity: 0 and top: 100vh to from in the keyframes so that these values are present when the page loads
This has the added benefit of showing the image if the browser does not support the animation property.
Codepen Example with SASS (Auto-prefixer is turned on)
Using a transform for animation
Here is another example using a transform — translate (info link) — which seems to provide a slightly smoother animation.
Working Example — vanilla CSS
#mountain {
position: absolute;
left: 50%;
top: 55vh;
-webkit-animation: lincoln_page_load 2s ease;
animation: lincoln_page_load 2s ease;
}
img {
width: 180vh;
margin-left: -50%;
}
#-webkit-keyframes lincoln_page_load {
from {
top: 100vh;
opacity: 0;
}
to {
opacity: 1;
top: 55vh
}
}
#keyframes lincoln_page_load {
from {
top: 100vh;
opacity: 0;
}
to {
opacity: 1;
top: 55vh
}
}
<div id="screen1">
<div id="mountain">
<img src="http://upload.wikimedia.org/wikipedia/commons/2/20/Red_Slate_Mountain_1.jpg">
</div>
</div>

Resources