My Intention:
Using the Vue.js (v2) attribute [v-cloak], I want to have the "app" hidden until ready. When [v-cloak] is removed, I want the "app" to fade in. Using CSS opacity and transitions to achieve the fade.
The Problem:
When [v-cloak] is removed from my "app" there is no transition as I would expect. It just goes from hidden to visible immediately. Seems to ignore the CSS.
Example:
I have made an exaggerated example with Vue.js and a JavaScript simulated version to show how they both behave.
https://codepen.io/freemagee/pen/wXqrRM
When viewing this example, you will see the "Plain old JavaScript" red box fade into view over 5 seconds. But the Vue controlled version just appears without a fade. They share the same CSS for the transition, so in theory should work the same way.
Anyone used [v-cloak] effectively to achieve smooth transitions?
Codepen Code
HTML
<div id="app" v-cloak>
<div class="red-box"></div>
<p>Vue.js {{ message }}</p>
</div>
<div id="app2" v-cloak>
<div class="red-box"></div>
<p>Plain old JavaScript</p>
</div>
CSS
html,
body {
margin: 0;
height: 100%;
min-height: 100%;
}
[v-cloak] .red-box {
opacity: 0;
visibility: hidden;
transition: visibility 0s 5s, opacity 5s linear;
}
#app,
#app2{
padding-top: 50px;
}
.red-box {
margin: 0 auto;
width: 100px;
height: 100px;
background: red;
opacity: 1;
visibility: visible;
transition: opacity 5s linear;
}
p {
text-align: center;
}
JS
new Vue({
el: "#app",
data: {
message: "Hello world"
}
});
window.setTimeout(function() {
document.getElementById("app2").removeAttribute("v-cloak");
}, 500);
This won't work because after the Vue app instance initializes, the #app div is actually removed, re-rendered and becomes a different div, even though it looks the same. This is probably due to Vue's virtual DOM mechanism.
The #app2 elements is still the same DOM after document.getElementById("app2").removeAttribute("v-cloak");: https://codepen.io/jacobgoh101/pen/PaKQwV
The #app element is a different DOM after new Vue(...): https://codepen.io/jacobgoh101/pen/ERvojx?editors=0010
For the Vue app, the element with v-cloak is removed, another element without v-cloak is added back. There is no element that transition from "with v-cloak" to "without v-cloak". That's why the CSS transition won't work. Hope that this is clear enough.
(If you don't already know this, )You can use Transition Component
As explained, there is no transition possible with [v-cloak], as the DOM element without the v-cloak is a new one.
I've figured a simple workaround, using the mounted methods and its hook vm.$nextTick(), in which case, the use of v-cloak isn't necessary anymore.
Mounted is called when the original app element has been replaced with the new one generated by Vue, but it doesn't necessarily mean that every child has been rendered yet. nextTick is called when every child element has been rendered inside the app view.
First, I have setup my HTML app element like this :
<div id="main-view" :class="{ready: isPageReady}">...</div>
In my vue app :
new Vue({
el: "#main-view",
data: {
isPageReady: false,
[...]
mounted: function() {
this.$nextTick(function () {
this.isPageReady = true;
});
}
});
Finally in the CSS, I tried a simple fadein using opacity:
#main-view {
opacity: 0;
}
#main-view.ready {
opacity: 1;
transition: opacity 300ms ease-in-out;
}
Beware: the transition doesn't always show if you have the browser's inspector/debugger open.
Related
chromium lighthouse tells me :
performance 89 (orange)
Cumulative Layout Shift 0.388 (red alert !)
Avoid large layout shifts
html :
<html>
...
<body class="loading">
...some static elements
</body>
...
</html>
css :
body.loading * { display : none; }
js when DOM is loaded :
document.addEventListener('DOMContentLoaded', (e) => {
//do many dynamic stuff with the DOM depending on user data (& register events listeners)
})
& finally when everything is in place in the dom, through window.onload (or if that event comes too soon, a callback once everything is in place) :
document.body.classList.remove('loading')
If I remove the loading class from the html lighthouse says great perf 100.
I thought before seeing this lighthouse analysis that my layout was shifting from a simple loading (css) animation to a completely ready page with no visible changes of the dom for the user during the load so i assume now that i am wrong, doing something wrong & missing something.
How should i do that instead for optimal loading while not risking having elements that are not ready ever being displayed ?
It turns out that now Lighthouse doesn't flag for CLS anymore although I didn't make any changes there (on the opposite I've added some code in HTML, CSS and JS that should have made the page slower).
So answer is (at least until being proven otherwise):
Hiding elements while the page is loading & JavaScript is not ready doesn't have negative impact on performance.
Here is minimal code to have a ultra-light spinner while the page is not ready yet:
const MyOnLoad = function() {
setTimeout(function(){ // only for the sake of displaying here the spinner 2 and half second obv
document.body.classList.remove('loading')
}
,2500)
}
window.onload = MyOnLoad
.loading {
margin: 34vmin auto;
border: 0;
border-radius: 50%;
border-top: 0.89vmin solid red;
width: 21vmin;
height: 21vmin;
animation: spinner 2s linear infinite;
}
#keyframes spinner {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading * {display: none;}
<html>
<body class="loading">
<main>show only when page is ready</main>
</body>
</html>
I have one div, with buttons that trigger JS functions to add/remove CSS classes to the div. I understand this is a common way of doing simple animations ("transitions"). Try pressing the first two buttons, one after another. The red div will teleport up and become semi transparent, then fade in and slide back down. It always works.
The third button simply executes the same code as the first two buttons, but from one function. I expected it to have the same visible effect: the div immediately would jump up from the translate and be transparent, then during the course of 1 second it would slide back to its normal spot and fade in to full opacity. But it does not - the button has no visible effect.
Why? How can I make this work?
Here is the JS, see the whole thing at the codepen link.
const div = document.getElementById('red-box')
function translateUp() {
div.classList.remove('no-translate');
div.classList.add('translate-up');
}
function noTranslate() {
div.classList.remove('translate-up');
div.classList.add('no-translate');
}
//why does this function not show any transition animation?
function both() {
translateUp();
noTranslate();
}
https://codepen.io/DMcCreepy/pen/BampPyB
No jQuery please :)
The reason is that the functions are executed immediately one after the another, with no time to see the effect. transition in CSS refers to when the class is applied (could be related to a CSS state like :hover), not to switching classes via JavaScript.
To add the 1 second delay in the JS, you can use window.setTimeout:
function both() {
translateUp();
window.setTimeout(noTranslate, 1000);
}
You are relying on the div getting rerendered and all its properties being recomputed before switching the class again with the second function. I am not sure entirely what order things are done in under the hood, but I am sure you cannot rely on constructions like this working in general. It is true that switching classes is a common way to do animations and maybe it is possible to work out a solution like that, but I have provided a workaround using a CSS animation. You can read more about animations here https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations
const div = document.getElementById('red-box')
div.addEventListener('animationend', reset);
function reset() {
div.classList.remove('dropin');
}
function both() {
div.classList.add('dropin');
}
button {
display: block;
}
.box {
height: 70px;
width: 80px;
font-size: 50px;
background-color: red;
animation-duration: 1s;
animation-fill-mode: forwards;
animation-name: none;
}
.box.dropin {
animation-name: dropin;
}
#keyframes dropin {
from {
transform: translate(0, -40px);
opacity: 0.1;
}
to {
transform: translate(0, 0);
opacity: 1;
}
}
<div id="red-box" class="box">div</div>
<button onclick='both()'>Do Both</button>
I am trying to create an accordion with React. I asked a question on this previously on StackOverflow in terms of, how do I assign classes to an accordion on click and open multiple at once. Somebody linked me the below code sandbox which will toggle a background colour on click.
https://codesandbox.io/s/nice-http-n0omm?file=/src/App.js
So, I have tried to put it into my code to open and close the accordion, and it will not transition no matter what I do. It is an instant change and I can't seem to figure out why. I have also tried making the transition from a bg colour of blue, to red, and same thing, it instantly changes with no transition.
Can somebody look at my code and help figure it out?
The only difference I have noticed in the difference between the code sandbox and my code, is that on the sandbox version, on the dom using inspect element, when the button is clicked, only the selected element flashes purple (chrome) to indicate something is changed. On my version, EVERY instance of the map flashes purple, to indicate that all the elements mapped out have been affected in some way.
const [accordionOpen, setAccordionOpen] = useState([]);
function openAccordion(num) {
setAccordionOpen((state) => {
return state.includes(num)
? state.filter((n) => n !== num)
: [...state, num];
});
}
return parsedData.map((data) => {
<>
<div
className="mortgage_moreinfo"
onClick={() => openAccordion(data.ProductCode)}
key={data.ProductCode}
>
<p>More Info</p>
</div>
</>
})
SCSS
.accordionClosed {
transition: all 1s ease;
max-height: 0;
overflow: hidden;
transition: max-height 0.2s ease-out;
}
.accordionOpen {
max-height: 101px !important;
}
Try something like this for your scss:
.accordion {
max-height: 0;
overflow: hidden;
transition: all 1s ease;
transition: max-height 0.2s ease-out;
}
.accordion.open {
max-height: 101px !important;
}
And for your React Component:
<div className=`accordion ${ConditionForOpeningTheAccordion ? 'open' : ''}`>
</div>
So you are basically having a .accordion class wich defines the basic style for your accordion, but with your height of 0px. And when your condition for opening the accordion is met you are adding the .open class to the accordion wich has a width of 101px. This should ensure, that your transition gets played every time the .open class gets removed or added to the accordion.
I hope my explanation helps you. If not I can provide a codesandbox or something like that.
I want to animate a translateX with transition on a click event by adding a class to the div in the js. The transform and transition properties are added in the css file.
var widget = document.getElementById('widget');
widget.style.display = 'block';
document.getElementById('widget2').clientWidth; //comment this line out and it wont work
widget.className = 'visible';
It only works if I query the width property of any element in the dom before adding the class.
here is a jsfiddle:
https://jsfiddle.net/5z9fLsr5/2/
Can anyone explain why this is not working?
That's because you begin your transition and modified the display property "at the same time". Altering display will ruin any transition (citation needed, admittedly), so it would be a good idea to isolate the display changing and actual transiting:
https://jsfiddle.net/5z9fLsr5/3/
document.getElementById('showWidget').addEventListener('click', function(e) {
e.preventDefault();
var widget = document.getElementById('widget');
widget.style.display = 'block';
//document.getElementById('widget2').clientWidth;
window.setTimeout(function(){
widget.className = 'visible';
},0);
});
#widget {
width: 200px;
height: 80px;
background: black;
position: absolute;
transition: transform 500ms;
transform: translateX(-200px);
display: none;
}
#widget.visible {
transform: translateX(200px);
}
#widget2 {
position: absolute;
right: 0
}
show
<div id="widget"></div>
<div id="widget2">xxx</div>
Querying clientWidth seems to "pause" the execution for some time, so it works too.
The issue here is the initial setting of display: none. To the browser's layout manager, this indicates that the layout should be done as if the element in question wasn't even in the DOM (it still is, mind you). This means that the CSS style transform: translateX(-200px); will not be applied.
Doing this:
widget.style.display = 'block';
widget.className = 'visible';
triggers both modifications essentially at the same time - the layout is only re-done after both statements have been executed. Inserting document.getElementById('widget2').clientWidth; (clientHeight works as well) triggers the layout manager to repaint, thus registering transform: translateX(-200px).
As others have mentioned before me, the solution is to either use opacity instead of display (this would be my choice), or to use setTimeout with a delay of 0 (see Why is setTimeout(fn, 0) sometimes useful?).
I have been searching on Google and Stackoverflow but haven't found what I am looking for.
Here's what I have. I promise I am making a sincere effort at figuring this out.
The problem is as follows: I have animations working with the list. When I add items to the list using the timeout, they correctly animate in. However, the "title" variable is a string. I want to apply an animation when this value changes. I am still clueless right now honestly on how to get that to work. I understand that I can add css classes for animations for ng-hide, but I still don't quite understand how to fit that here. Any help is appreciated in advance. Please enlighten me. You don't have to give me code. Even a high level description of how to accomplish this will suffice.
// app.js
(function() {
var app = angular.module("MyApp", ["ngAnimate"]);
// route configuration
}());
// homecontroller.js
(function() {
var app = angular.module("MyApp");
app.controller("HomeController", ["$scope","$timeout", homeController];
function homeController($scope, $timeout) {
$scope.items = ["Frodo", "Bilbo", "Merry", "Pippin", "Sam"];
$scope.title = "The Hobbits";
$timeout(function() {
$scope.title = "The Hobbits and the Wizard";
$scope.items.unshift("Aragorn","Faramir","Boromir");
}, 5000);
}
}());
Some HTML
<!-- view for HomeController -->
<h1>{{ title }}</h1>
<div ng-controller="HeaderWebpart.HeaderController">
<div class="testClass" style="display:block;" ng-repeat="item in items">{{ item }}</div>
</div>
And CSS
div.testClass.ng-enter {
-webkit-animation: enter 1000ms cubic-bezier(0.250, 0.100, 0.250, 1.000);
animation: enter 1000ms cubic-bezier(0.250, 0.100, 0.250, 1.000);
display: block;
position: relative;
}
#-webkit-keyframes enter {
from {
opacity: 0;
height: 0px;
left: -70px;
}
75% {
left: 15px;
}
to {
opacity: 1;
height: 20px;
left: 0px;
}
}
div.testClass.ng-enter-active {
opacity: 1;
}
You currently have nothing that applies any animation logic to the <h1> element, simply assigning a value to title within a controller is not enough.
If you have a look at the documentation for angular animations
https://docs.angularjs.org/api/ngAnimate - you'll see that only a specific set of directives have animation hooks. Each of these directives usually have a pairing of enter/leave or add/remove animations. This means that angular adds and removes specific CSS classes to these elements, which you can use to perform animations with, similar to what you have already done with the ng-repeat directive and testClass animations above:
.yourAnimationCSSClass.ng-enter { }
=> what your element should look like before the animation starts
what the change should be and the duration
.yourAnimationCSSClass.ng-enter.ng-enter-active { }
=> ending(stable) state for your animation, ie. what the
element should look like when you're done
... ng-leave and ng-leave-active work similarly.
So, to solve this for your <h1> element, one way to apply an animation is to optionally set a CSS class using ngClass. This ends up being fairly close to the Class and ngClass animation hooks example in the Angular developer guide for animations, so have a look at that example.