CSS transitions on Safari are choppy vs. other browsers - javascript

I'm really struggling trying to create smooth CSS transitions in Safari. I'm trying to hide/show divs by changing their height with a JavaScript onclick event. The resulting transitions are nice and smooth with Chrome, Firefox, and Edge. However with Safari it just looks bad and must be around 15 fps when rendering.
A basic JSFiddle is here: https://jsfiddle.net/q5a9b62s/6/
The website I'm working on is here: http://www.allinimages.ch/
Thanks.

You could try using JavaScript to add a className to you div like this:
function grow() {
var element = document.getElementById("boxid");
if (!element.className) {
element.className = 'tall';
} else {
element.className = '';
}
};
I've added the nullification of the className to enable toggling of the animation.
Then, let CSS processing do all of the transforming for you:
#boxid {
background-color: red;
width: 50px;
height: 50px;
position: relative;
-webkit-transition: height 0.3s ease;
transition: height 0.3s ease;
}
#boxid.tall {
height: 500px;
-webkit-transition: height 0.3s ease;
transition: height 0.3s ease;
transform: translate3d(100) /* this property ensures GPU processing cross-browser */
}
Codepen example is here.
Great article on smooth CSS transitions is here.
Some issues for cross-browser use of translate3d are documented here.

Related

Smooth transitions in Edge?

I have a sidebar transition that works fine in Firefox, but the first time it is used, the animation is "jerky" in Edge. It lags and then comes out really fast in that browser. After the first time of use per-page-load, it behaves smoothly like in Firefox though. I know Edge has issues with translate all but even specifying the transition type (translatex) in the CSS code did nothing for me.
var sidebar = document.getElementById('sidebar');
var burger = document.getElementById('BurgerID');
burger.addEventListener('click', function() {
if (burger.classList.contains('open')) {
burger.classList.remove('open');
sidebar.style.transform = 'translateX(400%)';
} else {
burger.classList.add('open');
sidebar.style.transform = 'translateX(300%)';
sidebar.style.zIndex = 998;
}
});
sidebar {
background: rgba(255,255,255,0.90);
position: fixed;
transform: translateX(400%);
transition: all .5s ease;
-webkit-transition: all .5s ease;
-ms-transition: translatex .5s ease;
top: 0px;
bottom: 0px;
width: 25%;
height: 100%;
}
<div id="BurgerID" class="">
<mark class="mark-1"></mark>
<mark class="mark-2"></mark>
<mark class="mark-3"></mark>
</div>
<div id="sidebar" class="sidebar">
It's hard to tell because your code sample doesn't run, but you could try adding will-change: transform; to your sidebar element.
The will-change CSS property hints to browsers how an element is expected to change. Browsers may set up optimizations before an element is actually changed. These kinds of optimizations can increase the responsiveness of a page by doing potentially expensive work before they are actually required.
Important: will-change is intended to be used as a last resort, in order to try to deal with existing performance problems. It should not be used to anticipate performance problems.
https://developer.mozilla.org/en-US/docs/Web/CSS/will-change

How to improve this animation?

I have three divs on the same line. You can check the example here: http://yoyo.ro/abw just scroll to the bottom of the page to the three boxes: Made to Measure, Instagram and Video Tracking.
When I click the left one, I want the other two to slide to the right and some text to appear. I tried to do it, but it seems that I complicated it so much and it isn't even smooth.
function hideTest(){
$(".instagram").addClass("slideout");
$(".videotracking").addClass("slideout");
$(".instagram").animate({left:"150%"},500);
$(".videotracking").animate({left:"150%"},500);
}
function showTest(){
$(".instagram").animate({left:"33.3%"},500);
$(".videotracking").animate({left:"66.6%"},500);
$(".instagram").removeClass("slideout");
$(".videotracking").removeClass("slideout");
}
$(".madetomeasure").on('click',function(){
var testwidth = $(this).find(".vc_btn3-container").width();
$(this).find(".vc_btn3-container").css("width", testwidth);
if(!$(this).hasClass("openslide")){
hideTest();
$(".madetomeasure").addClass("openslide");
$(this).find(".txtbox").animate({left:0},500);}
else {
$(this).find(".txtbox").animate({left:"-100%"},500);
$(".madetomeasure").removeClass("openslide");
showTest();
}
});
here is the css relevant to the JS
.txtbox{
-webkit-transition: all 0.3s ease-out;
-moz-transition: all 0.3s ease-out;
-o-transition: all 0.3s ease-out;
transition: all 0.3s ease-out;
width: 66.5%;
display:none;
left:-100%;
padding:0px 15px;
float:left;
position:relative;}
.instagram, .videotracking{position:static;}
.instagram {left:33.3%;}
.videotracking{left:66.5%;}
.instagram.slideout{position:absolute;}
.videotracking.slideout{position:absolute;}
.madetomeasure .button{
z-index:1;
height:300px;
background: url(http://yoyo.ro/abw/wp-content/uploads/2016/02/instagram.jpg) 100% 30% !important;
border: none !important;}
.madetomeasure.openslide {width:100%;}
.madetomeasure.openslide .wpb_wrapper {display:flex;}
.madetomeasure.openslide .txtbox {display:block;}
Thank you so much for the patience... :) I really appreciate it
As far as I know, your problem of smoothness is because:
jQuery change the inline styling of the animated element per frame. That is a lot of work and you can actually see the action if you inspect your element when it's animating.
CSS does poorly on animating left and right. There are many articles about this but here's one if you don't want to search: https://css-tricks.com/tale-of-animation-performance/
The Solution
Fiddle: https://jsfiddle.net/kv5twc64/1/
The solution is very common, and is used by many CSS libraries, a trick using .active, CSS animation and some JS.
Here I used the transition property for .card:
.card {
display:inline-block;
float:left;
max-width:33.333%;
position:relative;
cursor: pointer;
transition: 0.5s all ease-out;
}
If you don't know, transition will create a tweening effect when the elements' property has changed.
And here is the trick: By using ~ selecting the siblings in CSS and the transform property:
.card.active .desc {
transform: translateX(0);
}
.card.active ~.card {
transform: translateX(66.666vw);
}
There are several upsides on using CSS in this case:
You can simplify your JS. The JS became:
$(function(){
$(".card").eq(0).click(function(){
$(this).toggleClass("active");
})
})
You can improve webpage performance
You can have more choices on (simple) easing functions in CSS (jQuery only offers "swing" by default). Check this out: http://easings.net You can do something like this:
transition: all 600ms cubic-bezier(0.77, 0, 0.175, 1);
Hope this can help. But the lesson here is: Use CSS rather than JS when you can!
P.S. 66.666vw means 2/3 the width of the viewport width.

how to animate "simple jquery filtering" by using css only

I'm trying to animate the elements when they are filtered by javascript but the codes below don't work. Any suggestion?
This is what I have so far.
http://jsfiddle.net/ejkim2000/J7TF4/
$("#ourHolder").css("animation","scaleUp 0.3s linear 0.4s forwards");
$("#ourHolder").css({"animation" : "scaleDown 0.3s linear 0.4s forwards"});
On second thought, is there any other way that I can animate the elements by using css only?
I suggest you do it using CSS transitions. I believe this is what you're trying to do, but I can't find the referenced 'scaleDown' animation anywhere. My solution animates both width and height of the .item elements when toggling an additional '.hidden' class on them:
#ourHolder div.item.hidden {
transition: all 0.3s linear
}
#ourHolder div.item.hidden {
width: 0;
height: 0;
overflow: hidden;
}
Full solution on http://jsfiddle.net/5pKwy/
EDIT: Fiddle using min-height and min-width: http://jsfiddle.net/5pKwy/1/

Bypassing transition and changing a property instantly

I want to bypass CSS transition and change a property instantly.
I tried to set transition-duration to 0s before the change and then set transition-duration back to its original value:
$('div').css('width', '200px').delay(1000).queue(function() {
$(this).css({
transitionDuration: '0s',
msTransitionDuration: '0s',
mozTransitionDuration: '0s',
webkitTransitionDuration: '0s',
oTransitionDuration:'0s'
}).css('width', '10px').css({
transitionDuration: '2s',
msTransitionDuration: '2s',
mozTransitionDuration: '2s',
webkitTransitionDuration: '2s',
oTransitionDuration:'2s'
})
})​
Fiddle
This obviously doesn't work.
I understand that the spec does not define that behavior for this:
Since this specification does not define when computed values change,
and thus what changes to computed values are considered simultaneous,
authors should be aware that changing any of the transition properties
a small amount of time after making a change that might transition can
result in behavior that varies between implementations, since the
changes might be considered simultaneous in some implementations but
not others.
Is there an easy way to do this?
Note: The property I am changing is transform so .animate() would not be an option.
Since nobody else is posting a valid answer, here goes:
$('div').css('width', '200px').delay(1000).queue(function() {
$(this).css({transition: '0s', width: '10px'}).delay(1).queue(function() {
$(this).css({transition:'2s'});
});
},1000)​;
FIDDLE
Or if it's the other way:
$('div').css({
transition: '0s'
}).css('width', '200px').delay(1000).queue(function() {
$(this).css({width: '10px', transition: '2s'});
});
FIDDLE
jQuery should normalize vendor prefixes these days, so you don't have to type them all yourself.
The issue here is that jQuery attaches all the styles at once, only keeping the last styles, overwriting the previous styles of the same CSS property without ever doing a repaint of the DOM, and testing with native javascript seems to be doing the same thing, so it's probably the browser trying to avoid uneccessary reflows by adding a style just to have it changed in the next line of code, so doing:
$('div').css({
transition: '0s',
width: 200
}).css({
transition: '3s',
width: 10
});
won't work as only the last style is added.
This is where delay() comes into play, the OP's question was already using delay() so there was no reason not to use it, but removing delay() will of course cause the above issue, where the browser doesn't paint the first style, but only the last etc.
As delay() is really just a fancy timeout, it effectively defers the execution of the second setting of the styles, causing two browser repaints.
As this is most likely a browser issue, and not something we can change, deferring the setting of the second style is the only way to make this work, and using a delay will still work even if it's set to just 1 milliseconds, or one could defer the execution with a regular timeout, which is the usual way to defer execution of a script:
$('div').css({
transition: '0s',
width: 200
});
setTimeout(function() {
$('div').css({
transition: '3s',
width: 10
});
});
FIDDLE
The above will work just fine, as the timeout causes the first setting of the style to be painted by the browser, and defers the setting of the style inside the timeout to a later time, but as no time is set, it's executed as soon as the browser can (but still deferred until after the current script has completed), which for the human eye would seem like immediately, and that solves the issue.
Set up an override class that would disable css transitions on an element applied to, !important is perfect for this:
.notransition {
-webkit-transition: none !important;
-moz-transition: none !important;
-o-transition: none !important;
-ms-transition: none !important;
transition: none !important;
}
You can now toggleClass to switch the desired behaviour (smooth transition vs instant change):
$('div').
toggleClass('notransition', true). //or false!
css('width', '200px');
Fiddled. IMO one of the advantages of this approach is that you have clear separation between default element styling and the disable all smooth animations flag. This is also a very "wrappable" reusable approach, i.e. you can easily add an optional boolean property to your existing methods that would indicate whether or not it should be executed with transitions.
NB: sometimes you may want to disable transitions on the page altogether for whatever performance/UX reasons. In that case, you can change the selector to .notransition * and disable transition on all descendant elements.
If you have control of the CSS
The easiest thing to do is tie the animation to some class, and then at what point you want the animation to no longer be bypassed, you add the class, otherwise no animation is ever set. If the reverse, you generally want the animation, but occasionally want to bypass it, then add the class by default and remove it at time of bypassing.
Example CSS
div{
height: 100px;
width: 200px;
background: red;
}
div.doTransition {
width: 10px;
transition: width 2s linear;
-ms-transition: width 2s linear;
-moz-transition: width 2s linear;
-webkit-transition: width 2s linear;
-o-transition: width 2s linear;
}
See fiddle which creates a click event to start animation when it is desired, but this could be some other programmatic trigger to add the class at the time that one no longer wants to bypass it. This fiddle does the opposite, it assumes the animation is present, but on page load immediately bypasses it by removing the class.
The issue is that, since there is no reason for the browser to slow down and execute each operation seperately, it combines them and does both at the same time. Querying offsetHeight is one way to force it to do each operation seperately, as it has to recalculate the height. http://jsfiddle.net/markasoftware/6cTeY/15/ works perfectly
This is the only way I could make it work. jQuery seems to be a bit stubborn.
http://fiddle.jshell.net/8qTpe/1/
P.S. There are some errors in your approach:
You are re-sizing to 200px before the delay, thus using the default CSS settings.
You are re-sizing to 10px before the change of the transition back to 2s.
Now jQuery seems to apply all CSS settings in a row so that's why the whole thing does not seem to work.
I'd go for a rather clean CSS solution
HTML
<div id="foo"></div>
<button class="out">out</button>
<button class="in">in</button>
JS
$('button.out').click(function(){console.log($('#foo').addClass);$('#foo').addClass('out')})
$('button.in').click(function(){$('#foo').removeClass('out')})
CSS
div{
height: 100px;
width: 10px;
background: red;
transition: width 0s linear;
-ms-transition: width 0s linear;
-moz-transition: width 0s linear;
-webkit-transition: width 0s linear;
-o-transition: width 0s linear;
}
div.out {
width: 200px;
transition: width 2s linear;
-ms-transition: width 2s linear;
-moz-transition: width 2s linear;
-webkit-transition: width 2s linear;
-o-transition: width 2s linear;
}
http://jsfiddle.net/6cTeY/19/
I usually do it in this vanilla JS fashion.
FIDDLE
HTML
Suppose you have an element
<div id="element"></div>
CSS
Suppose your element has CSS Transitions already active and background: green
#element {
background: green;
width: 200px;
height: 200px;
-webkit-transition: 'all 0.5s ease-out';
-moz-transition: 'all 0.5s ease-out';
-ms-transition: 'all 0.5s ease-out';
-o-transition: 'all 0.5s ease-out';
}
JS
The element has CSS transitions but we want to change the element's background to BLUE, instantly.
Right after that, we want the element's normal behaviour to return so we can animate it's background to RED.
We need to shut off transitions for a moment and restore them right after.
// grab the element
var element = document.getElementById('element');
// removeTransitions
element.style.webkitTransition = 'none';
element.style.mozTransition = 'none';
element.style.msTransition = 'none';
element.style.oTransition = 'none';
// apply desired 'instant' property
element.style.background = 'blue'; // is applied instantly
// this 10ms timeout is necessary for the transitions to be active again
setTimeout(function() {
element.style.webkitTransition = 'all 5s ease-out';
element.style.mozTransition = 'all 5s ease-out';
element.style.msTransition = 'all 5s ease-out';
element.style.oTransition = 'all 5s ease-out';
// apply desired 'animated' property
element.style.background = 'red'; // is applied smoothly
}, 10);
var ball = document.querySelector('.ball'),
ballSpeed = 2,
button = document.querySelector('button');
// Chane to random speed "instantly" (on button "click")
button.addEventListener('click', ()=>{
ballSpeed = Math.random()*8 + 1;
ball.style.transitionDuration = ballSpeed + 's';
ball.classList.remove('move');
ball.clientHeight; // <--- triggers repaint
ball.classList.add('move');
// set button text
button.textContent = ballSpeed.toFixed(2) + 's';
})
function animate( speed ){
ball.style.transitionDuration = '0s';
ball.classList.remove('move');
ball.clientHeight; // <--- triggers repaint. has to be after "move" class was removed
ball.style.transitionDuration = ballSpeed + 's';
ball.classList.add('move');
ball.removeEventListener('transitionend', animate)
ball.addEventListener('transitionend', animate); // keep rollin..
}
animate();
html, body{ height:100%; overflow:hidden; }
.ball{
position: absolute;
width: 3em;
height: 3em;
left:0; right:0; top:0; bottom:0;
margin: auto;
transition-duration: 2s; /* <-- start speed */
transition-timing-function: linear;
}
.ball::after{
content: '';
display: block;
width: 100%;
height: 100%;
transform: translateX(100%);
border-radius:50%;
background: gold;
}
.ball.move{
transform: rotate(360deg);
}
<button>2s</button>
<div class="ball"></div>
I don't know how this works in JQuery, but you could do this in CSS (at least at the time of writing):
div {
animation: trans 2s;
}
#keyframes trans {
0% {
width: 200px;
}
99.9% {
width: 200px;
}
100% {
width: 10px;
}
}
pure JS solution that should work with JQuery (have not tested).
The problem with the accepted answer is placing a value for the delay, but a better solution is to have a delay of 0. We are gonna use a little trick with the event loop to achieve this:
const button = document.querySelector('button');
function reposition() {
button.style.transition = 'none';
button.style.transform = 'translate(-50%)';
setTimeout(() => {
button.style.transition = '1s';
button.style.transform = 'translate(0)';
}, 0);
}
button.addEventListener('click', reposition);
<button>
Click Me
</button>
This is called a zero delay. It still uses a delay, but you don't have to feel icky about it because this will instantly run when the stack is clear.
If you want to understand why this is, I recommend watching this video
But here's a (messy) short explanation:
Basically what's happening is that setTimeout() will hold the value until a certain amount of time, our time here is 0, but it won't execute yet until the stack is clear, (why? this is, watch the video) because the browser still has to repaint the changes, the re-render will still be in the stack and as it finishes, the function passed to setTimeout() will be executed causing another re-render.
Do I know if this works 100% of the time? In theory, it should, but I'm no expert.

DIV equivalent HTML5

I am trying to redo the a tutorial I had posted links to in previous question to have as much HTML5 and CSS3 code. I read in some online article's that in HTML5 they are trying to get rid of the idea of Div tags, rather they are pushing for something known as section. Is that a correct observation. For example I have this section of code from the above tutorial.
https://skitch.com/android86/r67ey/dreamweaver
and what I am interested to know if I should be using div tags in my HTML5 code as well or is there a better way to do it rather than using Div's?
What I have in my HTML5 code at present is the following.
https://skitch.com/android86/r67ej/dreamweaver
Thanks for the group's valuable input.
a proof of concept for a sliding link over content (with display: block) on hover, using CSS3 transitions only.
NOTICE: this is a webkit (safari & chrome) only syntax, for the syntax for the rest of the browsers go here: http://css3.bradshawenterprises.com/transitions/
a simple element, with the following style:
a {
z-index: 100;
position: fixed;
-webkit-transition: all 1s ease-in-out;
display: block;
background-color: black;
width: 100%;
height: 500px; }
and the hover state style:
a:hover { height: 700px; }
The code submitted looks good, however I would include the extra lines to cover Firefox and Opera.
-moz-transition: all 1s ease-in-out;
-o-transition: all 1s ease-in-out;
transition: all 1s ease-in-out;
You can then use Modernizr (http://www.modernizr.com/) to cover all browsers with backup Javascript.
I would use something like this:
a.slideDown {
z-index: 100;
position: fixed;
display: block;
background-color: black;
width: 100%;
height: 500px;
-webkit-transition: top .2s ease;
-moz-transition: top .2s ease;
-o-transition: top .2s ease;
transition: top .2s ease;
top:5px;
}
a:hover.slideDown {
top:495px;
}
If you are including CSS3 as part of HTML5 (which you have to to allow animation!), have a look at: http://css3.bradshawenterprises.com/sliding/ . The jQuery isn't necessary, you could use the :target pseudo element instead.
For your case, just set a transition on the element, then change the height, or top value using the target selector.
It's probably around 4 lines of code if you only want it to work in newer browsers.
I'd agree with the comments about using CSS3 for transitions. I used this on a portfolio site and it looks fairly good. It's just a couple lines of code and if the browser doesn't support the transitions it will still display the content on hover, just without the animation.

Categories

Resources