Safari (mobile + desktop) grouping CSS Keyframe Animations - css

I'm having trouble with Safari creating an effect that mimics bubbles floating into the air, except with feathers. I've omitted some code to get to the gist of things. The url to the work-in-progress is here.
Here are the webkit styles for my animated objects.
#-webkit-keyframes f1 {
100% {
-webkit-transform: translateX(-25px) translateY(-350px);
}
}
.feather {
/* other styling omitted */
-webkit-animation-duration: 7s;
-webkit-animation-timing-function: linear;
}
And the javascript to create a bunch of objects.
animateFeathers = function() {
var $feather = $('<img>'),
$feather.addClass('feather animated');
$feather.attr('src','img/feather.png');
$feather.css('-webkit-animation-name','f1');
$featherContainer.append($feather);
setTimeout(function() {
$feather.remove();
}, 9000);
// random time to create next feather
var rTimeout = Math.random() * maxTime + minTime;
setTimeout(animateFeathers, rTimeout);
}
If you visit the link in Chrome or Firefox you'll see the intended effect. However in Safari (again, mobile or desktop) the feathers stack and only animate in a group every 7 seconds. I'd like for them to begin their animation as soon as they are inserted into the DOM. Any ideas on this?

Had to resort to using a canvas as I really couldn't get performance working on Safari. Took quite a few hours, but its working:
http://poetreatapp.com/

Related

Is it possible to turn off all CSS animations in Chrome?

I have a weird issue where if I play an animation on one of my 3 monitors, YouTube videos on any other monitor crashes. I did fix this by disabling hardware acceleration in chrome://flags, but a new update in Chrome recently made the issue come back, and I haven't found a way to fix it yet. Animations occur on places like Facebook ("Someone is typing a comment...") or basically any website with a animation-duration CSS property on something (spinners are probably the most used form of animations I guess).
I can verify this simply by placing this CSS on any page:
* {
animation-duration: 0s !important;
}
Boom instantly all my videos play perfectly. No issues what so ever. I could add this to an userscript (or make a tiny extension), and I don't think it would mess up too much, but I'm more interested in knowing if there's a Chrome flag that can disable animations completely? I don't know if animation-duration works for any animation.
From what I know Chrome has no such option.
But, I was able to make something similar using the Tampermonkey extension.
Simply add the following script to the extension:
// ==UserScript==
// #name Animation Stopper
// #description Stop all CSS animations on every website.
// #author Ba2siK - https://stackoverflow.com/users/7845797
// #match *://*/*
// #grant GM_addStyle
// #run-at document-end
// ==/UserScript==
GM_addStyle(`
*, *:before, *:after {
transition-property: none !important;
transform: none !important;
animation: none !important;
}`
);
console.log("Animation Stopper ran successfully");
Make sure it's enabled at the extensions bar
Note: it won't work on iframe elements.
Btw, You can disable the window animation in chrome by adding the --wm-window-animations-disabled command-line flag.
* {
animation: none !important;
}
/* turn off animation on all elements*/
Allow me to answer my own question. Setting animation-duration to 0s !important seems to be working. However, I added animation-play-state: paused for good measure as well.
I made an userscript, and found that it doesn't target the Shadow DOM, so I have to traverse through every element, check if it's a shadow root, and then add the CSS. Since elements can be added to a page dynamically, I decided to do this every second. So far I cannot see a performance difference, even on pages with a lot of elements.
Install TamperMonkey (Chrome) or GreaseMonkey (Firefox) to use this:
// ==UserScript==
// #name Disable all animations
// #version 1.0
// #author mortenmoulder
// #include *
// #grant GM_addStyle
// #grant GM_addElement
// ==/UserScript==
//remove animations globally
GM_addStyle("* { animation-duration: 0s !important; animation-play-state: paused; }");
var ignoreElements = [];
//remove animations inside shadow DOM elements
function findShadowRoots(elements) {
for (var i = 0; i < elements.length; i++) {
if(elements[i].shadowRoot) {
if(ignoreElements.includes(elements[i].shadowRoot) == false) {
GM_addElement(elements[i].shadowRoot, 'style', { textContent: '* { animation-duration: 0s !important; animation-play-state: paused;' });
ignoreElements.push(elements[i].shadowRoot);
}
findShadowRoots(elements[i].shadowRoot.querySelectorAll("*"));
}
}
}
//remove animations every 1 second
setInterval(() => {
var allNodes = document.querySelectorAll('*');
findShadowRoots(allNodes);
}, 1000);

transitioning between images in vuejs

I want to create a smooth transition between 2 images with a legend.
The images come from an object-array of images.
Because works only on single tags and components, I've created a component to define the image+legend.
<transition>
<home-image :slide="slide" :key="slide"></home-image>
</transition>
The classes I define are like this
.v-enter-active,
.v-leave-active {
transition: opacity 2s ease-in-out;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
.v-enter,
.v-leave-to {
opacity: 0;
}
The new image is returned by a method
updateSlide() {
this.slide = this.entries[ Math.floor( Math.random() * this.entries.length ) ];
}
where entries is my array defined in data
this.slide is updated in regular intervals, every 10seconds like this, which is defined in the created() section
this.updateSlide();
this.uSlide = setInterval( this.updateSlide, 10000);
The code works, in the sense that a new image is loaded in this.slide every 10 seconds.
However, the transitions work only "half-way".
There is no transition fading out: the "old image" disappears and makes way for the new image fading in.
However, what I'd like is a smooth transition from one to the other.
I've tried more than a couple of ideas including using mode="out-in" and "in-out" but nothing works as I want.
What am I overlooking?

CSS Animation Not Working As Expected With Image element In Safari

Below is a minimum mockup of a bug that I'm experiencing in Safari.
Expected behaviour: The code is supposed to display a placeholder while another image loads and then fade-in that image while removing the placeholder.
placeholder -> fading in loaded image
Actual behaviour:
Works as expected in Chrome and Mozilla but fails in Safari, resulting in the following effect:
placeholder -> white screen -> loaded image
Can somebody help me figure out why this is happening in Safari please? (try running below example in chrome or mozilla vs safari to see for your self.)
const image = new Image();
image.onload = () => {
document.getElementById('placeholder').remove();
const el = document.createElement('img');
el.setAttribute('src', 'http://deelay.me/1000/https://cdn.pixabay.com/photo/2014/06/03/19/38/board-361516_1280.jpg');
el.setAttribute('width', '150');
el.setAttribute('height', '150');
el.style = `
animation-name: testAnimation;
animation-duration: 0.3s;
animation-iteration-count: 1;
animation-timing-function: ease-in;
background: red;
`;
document.body.appendChild(el);
}
image.src = 'http://deelay.me/1000/https://cdn.pixabay.com/photo/2014/06/03/19/38/board-361516_1280.jpg';
#placeholder {
background: #eee;
}
#-webkit-keyframes testAnimation {
0% {
opacity: 0.25;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
<img id="placeholder" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'200' height%3D'300' viewBox%3D'0 0 200 300'%2F%3E" width="150" height="150">
(
Another weird behaviour is that if animation-duration gets increased to around 3-4 seconds then the image does fade-in but the white screen is still there, i.e.
placeholder -> white screen -> fading in loaded image
)
update:
After playing around with img’s background color it seems like in Safari the animation begins taking effect around 1s before the image even begins getting rendered to the screen.
hence the white flick if the real image doesn't have a background color like the placeholder image does and hence why after adding a background color to the real image that background color becomes visible around 1 sec earlier before the image even starts displaying
and hence the fade effect starting to take place around 1 sec earlier before the image even starts displaying
Whereas in Chrome and Mozilla the animation appears to be perfectly in sync with the image.
Run updated code to see for yourself
(it also only seems to happen with <img>, I've tested it with some other elements (still inside of image.onload) and they work as expected.)
update 2: See the fix in the answer below together with an explanation
This was fixed by adding another onload event this time on the image element rather than on the explicit Image object and putting the image.onload listener inside of it.
Which seems to suggest that in Safari, animation and styles are applied as soon as the <img> is added to the DOM - before it is fully loaded (its onload event called.)
This can be tested by giving the <img> a background color and seeing it show up together with the animation as soon as the element is added to the DOM and seeing that it takes an extra second until the image itself becomes visible
You can also see the same effect happen without using any animation at all just by giving the <img> a background color
(a bug in Safari? My Safari version: Version 11.0.2 (13604.4.7.1.3))
Edit: Actually the problem seems to be caused by caching, Safari doesn't seem to cache the pre-loaded proxied image while other browsers do cache it. Safari seems to fire another request for that image when it gets rendered to the screen - hence the styles being visible prior to seeing the actual image since the image is still being downloaded.
Edit 2: Upon further investigation it seems like Safari is actually the only one out of those 3 browsers that is behaving as expected - this is because the image's HTTP response contains a Cache-Control: no-cache, must-revalidate header explicitly instructing the browser to not cache the image.
const image = new Image();
const el = document.createElement('img');
el.setAttribute('src', 'http://deelay.me/500/https://cdn.pixabay.com/photo/2014/06/03/19/38/board-361516_1280.jpg');
el.setAttribute('width', '150');
el.setAttribute('height', '150');
el.style = `
animation-name: testAnimation;
animation-duration: 1s;
animation-iteration-count: 1;
animation-timing-function: ease-in;
background-color: red;
`;
el.onload = () => {
image.onload = () => {
document.getElementById('placeholder').remove();
document.body.appendChild(el);
}
}
image.src = 'http://deelay.me/500/https://cdn.pixabay.com/photo/2014/06/03/19/38/board-361516_1280.jpg';
#placeholder {
background: #eee;
}
#-webkit-keyframes testAnimation {
0% {
opacity: 0.25;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
<img id="placeholder" src="data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'200' height%3D'300' viewBox%3D'0 0 200 300'%2F%3E" width="150" height="150">
This appears to be related to the specific server/image you are using. I tried changing just the image URL (to something on wikimedia.org, just for testing purposes), and the animation began working as expected in Safari 11.0.2 (13604.4.7.1.6).
Pre-loading the image may help... or even better (all of this assuming you have proper usage rights, of course), you can try copying the image to your web server so it can be loaded locally. Essentially, you're just remedying the fact that Safari has finished running the animation by the time it can load the image from the remote server. You could also try using window.onload instead of image.onload, to force all external resources to be loaded before it runs.

Pause/play ALL the CSS animations of every child element

I'm creating a dashboard page which is full of CSS animations. From Bootstrap stuff (animated progress bars) to custom animations.
When you click some of the elements, a near full-screen modal is triggered, which overlaps all the animations, so I want to temporarily pause them all (because of possible performance issues) by adding/removing a class to one of the top elements, and using CSS to pause all animations when that class is set.
This solution would use only a single line of js, just to toggle the class on opening the modal.
My template looks somewhat like this:
<body>
<div class="modal">
<!-- Modal code -->
</div>
<div class="app">
<!-- Template -->
</div>
</div>
Is it possible to add a class to .app which pauses every CSS animation in every child element?
Note 1:
I know you can use the exact opposite of what I request: namely, have a default .animation-play class to one of the top elements, and prefix every child element with an animation with this class, and then remove this class to pause every animation. Just like:
app.animation-play .somediv .somediv .element {
// animation code
}
app.animation-play .somediv .element {
// animation code
}
app.animation-play .somediv .somediv .somediv .somediv .element {
// animation code
}
But then I have to edit a lot of CSS code, and it doesn't look very nice either.
Note 2:
I'm also open for a JS solution, but I would heavily prefer a pure CSS way of achieving this.
You can use a universal selector to target everything when a class of 'paused' is added to your app wrapper, however many CSS linters still warn against using these due to performance impacts.
To be honest the impact is probably minimal these days and many CSS resets for example use them.
You could use something like:
.app.paused * {
animation: none;
}
EDIT:
Looking through the comments above it seems as though the above selector doesn't have enough precedence to overwrite the animations so '!important' has been added.
.app.paused * {
animation: none !important;
transition: none !important;
}
However this is generally not a great idea, I always try to avoid using '!important' at all costs due to the difficulty in maintaining the stylesheet with these selectors in place. If you can overwrite the animations with a greater precedence then it would be better to do so rather than using '!important'.
EDIT 2:
As you mentioned you were open to JS solutions, here is some JS that should clear all the animations within a given selector. I'm not sure what the performance impact of doing it this way is but I added it here just in case someone else prefers to do it only using JS:
let stopAnimationsWrap = document.querySelector('.app');
let stoppedAnims = [];
// Stop animations
document.querySelector('.stop').addEventListener('click', () => {
let appAllEls = stopAnimationsWrap.querySelectorAll('*');
let allElsAr = Array.prototype.slice.call(appAllEls);
allElsAr.forEach((thisEl) => {
let elClass = thisEl.classList[0];
let cs = getComputedStyle(thisEl, null);
let thisAnimation = cs.getPropertyValue('animation-name');
if (thisAnimation !== 'none') {
stoppedAnims.push([elClass, {
'animationName': thisAnimation
}]);
thisEl.style.animationName = 'none';
}
});
});
// Start animations
document.querySelector('.start').addEventListener('click', () => {
stoppedAnims.forEach((thisEl) => {
let domEl = '.' + thisEl[0];
stopAnimationsWrap.querySelector(domEl).style.animationName = thisEl[1].animationName;
});
});
Fiddle:
https://jsfiddle.net/vu6javb2/14/
.app {
-webkit-animation-play-state: paused; /* Safari 4.0 - 8.0 */
animation-play-state: paused;
}
on hover:
.app:hover {
-webkit-animation-play-state: paused; /* Safari 4.0 - 8.0 */
animation-play-state: paused;
}

jQuery Mobile: How do you simulate the iOS slide-out navigation pattern on jQuery Mobile?

EDIT 2/13: The Panels widget in jQuery Mobile 1.3 now exists! Please use this instead.
I'm trying to write a custom CSS-based transition in jQuery Mobile to simulate the slide-out navigation design pattern.
What I'm trying to accomplish is to have the navigation slide into view and take up 75% of the viewport space. The remaining 25% is filled with the remainder of the previous page.
Here's my CSS:
.slidenav.in { /*New page coming in*/
-webkit-transform: translateX(-75%);
-webkit-animation-name: slidenav-in;
}
#-webkit-keyframes slidenav-in {
from { -webkit-transform: translateX(-75%); }
to { -webkit-transform: translateX(0); }
}
.slidenav.out { /*Old page going out*/
-webkit-transform: translateX(0);
-webkit-animation-name: slidenav-content-out;
}
#-webkit-keyframes slidenav-content-out {
from { -webkit-transform: translateX(0); }
to { -webkit-transform: translateX(75%); }
}
.slidenav.in.reverse { /*Old page coming in*/
-webkit-transform: translateX(75%);
-webkit-animation-name: slidenav-content-in;
}
#-webkit-keyframes slidenav-content-in {
from { -webkit-transform: translateX(75%); }
to { -webkit-transform: translateX(0); }
}
.slidenav.out.reverse { /*New page going out*/
-webkit-transform: translateX(0);
-webkit-animation-name: slidenav-out;
}
#-webkit-keyframes slidenav-out {
from { -webkit-transform: translateX(0); }
to { -webkit-transform: translateX(-75%); }
}
I can't seem to get it to do what I want, though. It removes the old page entirely instead of leaving the remaining 25% of the page in view.
You can see what's happening here (Webkit browsers): http://jsbin.com/ukajeb/7
What am I doing wrong?
EDIT 2/13: The Panels widget in jQuery Mobile 1.3 now exists! Please use this instead.
So I worked some more on this and, with the help of Firebug, discovered that after the keyframe animation was occurring, the original page was being reset to its original position and display set to none.
Knowing this, I started going through the jQuery Mobile docs a bit more thoroughly and found that there are Page Transition Events that you can bind to. More specifically, the pageshow and pagehide events.
Then I set the CSS of the original page manually:
$('[data-role=page]').live('pagehide',function(event, ui){
$(this).css({
"display": "block",
"-webkit-transform": "translateX(75%)"
});
});
$('[data-role=page]').live('pageshow',function(event, ui){
$(this).css({
"display": "",
"-webkit-transform": ""
});
});
Check it out here (Webkit browsers): http://jsbin.com/ukajeb/3
Hope someone else finds this useful as well!
Note: This demo uses jQuery Mobile 1.0.1, which only supports jQuery 1.6.4. This is why .live() was used rather than .on(). However, the upcoming jQuery Mobile 1.1.0 will support jQuery 1.7.1, so moving forward .on() should be used in place of the deprecated .live().
I don't know if this will fully help, but I took a look at this inside of firebug a bit. What I think is happening is when you complete the transition (even the partial one) the active page becomes the menu you are transitioning to. This means the last page is being hidden as it is no longer active. And in fact you can see when the transition completes the other page with "content" disappears.
You might try this on your link that invokes the menu - try using a dialog. You would still need to apply your custom transition there. I think what you are trying to accomplish you may also look into a plugin called "sub page" it may get you to the look you are trying to achieve. Also there is splitview (http://asyraf9.github.com/jquery-mobile/) Alternately you could try messing around with the 1.2 branch of JQM and see if POPUP men may get you there as well. Essentially, it is a div that can sit on top of the existing active page.
Open dialog
I originally was using a dialog with a slide up / down effect - but it became too troublesome in Android so we removed it.
I don't think JQM was really designed by default to keep two pages active and visible at once.

Resources