transitioning between images in vuejs - css

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?

Related

vue v-bind style - smooth changes, not instantly

I use "vuex-module-decorators" and dynamically determine the style in this getter:
<div class="viewport":style="viewportStyleVars">...</div>
get viewportStyleVars() {
const tx = -this.viewportRect.x;
const ty = -this.viewportRect.y;
return {
'--translate-x': `${tx}px`,
'--translate-y': `${ty}px`,
}
}
How to make change happen smoothly, not instantly?
I would be grateful for the hints, I am not familiar with the animation.
You just need to add transition in CSS to the div with class .viewport something like:
.viewport{
transition: all 1s linear;
}
The 1s in the transition, is the time, that the action will take, you can tweak this value to something smaller, like 0.1s, if that suits you better.

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;
}

Animating div content populated by AngularJS without jQuery

I have a div like this:
<div class="row">
<alert ng-repeat="alert in alerts" type="alert.type" close="closeAlert($index)">{{alert.msg}}</alert>
</div>
I create the alerts in angularjs (and I'm using bootstrap), and while this works great, the visual effect is kind of uncool. As alerts are added to the div, all page content is shoved ungracefully down to make room for the new alert.
I would like to animate the movement so that it is at least smooth. But I don't want to use jQuery. I've played with the CSS3 transitions, but can't seem to get them to work smoothly.
Can I do this where the trigger is a change in div height? How?
you can do it with directives in their link function. i think you wont mind about so small piece on jquery code
myModule.directive('animateRight', function () {
var linker = function (scope, element, attrs) {
var right = function() {
$(this).animate({
{"height": "800px"},
"fast");
})
}
element.on('click', right);
};
return {
restrict:'A',
link:linker
}
})
<div animate-right class="box"></div>
Ok, so I don't like to answer my own question, but this seems like the way to do it...
I used AngularJS 1.2, along with the new ngAnimate module. You need to add angular-animate.js, and reference the animate module, so at the end here's what my modules looked like:
var app = angular.module('tracker', ['$strap.directives', 'ui.bootstrap', 'ngRoute', 'ngAnimate']);
After that, its super simple, and very much CSS3 animations. My alert line ended up with a class repeat-item:
<alert class="repeat-item" type="alert.type" data-ng-repeat="alert in alerts" close="closeAlert($index)">{{alert.msg}}</alert>
And I added some CSS to target that class with the angularjs triggers:
.repeat-item.ng-enter,
.repeat-item.ng-leave {
-webkit-transition: 0.2s linear all;
-moz-transition: 0.2s linear all;
-o-transition: 0.2s linear all;
transition: 0.2s linear all;
}
.repeat-item.ng-enter,
.repeat-item.ng-leave.ng-leave-active {
opacity: 0;
}
.repeat-item.ng-leave,
.repeat-item.ng-enter.ng-enter-active {
opacity: 1;
}
And voila a nice fade in and out animation.
This page really explains very well how to do it. Cheers to Michael Benford for the great link.

Safari (mobile + desktop) grouping CSS Keyframe Animations

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/

Resources