Transitions not working when setting .cssText on an element's style? - javascript

I have the following CSS snippet:
.Test-Content {
transition: height 2s;
}
and the following HTML code:
<div id="mydiv" class="Test-Content">foo</div>
Using pure JavaScript, I am trying to change the height of the div like that:
myId = document.getElementById("mydiv");
myId.style.cssText = "height: 0;";
/* ... Lots of code and situations where the browser re-renders the page ... */
myId.style.cssText = "height: 100px";
This indeed changes the div's height from whatever it was to 0 and then to 100px, but without any animation, i.e. immediately. I don't understand why this happens. After all, the div has .Test-Content in its class list, so any change of its height should trigger the transition.
Could anybody please explain why this is not the case?
When I change this to the following (very weird and worrying) code, it works as expected:
CSS:
.Test-Content-A {
transition: height 2s;
height: 0;
}
.Test-Content-B {
transition: height 2s;
height: 10px; /* or whatever number you prefer */
}
HTML:
<div id="mydiv" class="Test-Content-A Test-Content-B">foo</div>
JavaScript: (Same as above)
It seems that I can trigger a transition by setting an element's style.cssText directly only if this element also has two classes with different height properties in its class list.
I have that problem in Firefox and Chrome (didn't test others so far), both at current patch level at the time of writing this.

There are a couple things you can be running into as illustrated by the following snippet. The first time you click "Expand" or "Collapse", it won't animate, but then clicking "Expand" or "Collapse" after that will trigger an animation.
document.getElementById("expand").addEventListener('click', () => {
myId = document.getElementById("mydiv");
myId.style.cssText = "height: 100px";
});
document.getElementById("collapse").addEventListener('click', () => {
myId = document.getElementById("mydiv");
myId.style.cssText = "height: 0;";
});
document.getElementById("unset").addEventListener('click', () => {
myId = document.getElementById("mydiv");
myId.style.cssText = "";
});
document.getElementById("setandexpand").addEventListener('click', () => {
myId = document.getElementById("mydiv");
myId.style.cssText = "height: 0;";
setTimeout(() => { myId.style.cssText = "height: 100px"; });
});
.Test-Content {
transition: height 2s;
}
<div id="mydiv" class="Test-Content">foo</div>
<button id="expand">Expand</button>
<button id="collapse">Collapse</button>
<button id="unset">Unset</button>
<button id="setandexpand">Set and expand</button>
Issues you may be experiencing:
1. CSS can only transition from a value to a value.
Going from cssText = "" (the initial value) to cssText = "height: 100px" doesn't animate. You can reproduce this by clicking "Unset" then clicking "Expand" or "Collapse".
2. If CSS is set multiple times in a block of code, the browser will only process the last one.
The browser doesn't render changes immediately when they're set but instead switches between executing all executable JavaScript and updating the page based on what everything is set to. You can get around this by breaking it into two discrete steps. The best will probably be setting style="height: 0" in the HTML or adding a class with zero height.
Otherwise, you can do something like:
myId.style.cssText = "height: 0";
setTimeout(() => { myId.style.cssText = "height: 100px"; });
This code sets the height to zero, lets the browser update the style, then executes the new code setting the height to 100px, which the browser can animate. Of course, you only need to call cssText = "height: 0" because as soon as it's been rendered the browser will be able to animate.
You can see this in the snippet by clicking "Unset" followed by "Set and expand". The element will immediately decrease to zero and then expand to 100px. Clicking multiple times shouldn't appear to do anything because each time the browser will start animating down to zero then start animating back up to 100px within milliseconds.

One of the easiest ways to trigger CSS animations with JS is changing the class.
CSS:
.Test-Content {
height: 0;
transition: height 2s;
}
.Test-Content.taller {
height: 10px;
}
And then, with Javascript:
1) For adding the class (animating forwards)
document.getElementById("mydiv").classList.add('taller');
2) For removing the class (animating backwards)
document.getElementById("mydiv").classList.remove('taller');

This may not answer your question about why the transition isn't working with style.cssText. However, if you're wanting the div to change height right when the page loads, I recommend using an animation instead like the following:
.Test-Content {
transition: height 2s;
height: 100px;
animation: grow 2s;
}
#keyframes grow {
from {
height: 0
}
to {
height: 100px;
}
}
This eliminates the need to use JavaScript and better helps to keep JavaScript from doing what CSS can do.

Related

max-height for read more is not respecting 0px from css

I am trying to write a read more. It is working for the most part, except that the Divs that are supposed to be completely closed, are still showing the first line of text.
I made a codepen
https://codepen.io/justinblayney/pen/JjpZeJK
It is a bit complicated, in mobile view 5 divs need to be hidden behind a read more toggle, it desktop view they are always visible with a completely different layout which I use css grid to accomplish (which is why i don't wrap them into one div).
if you view my link above, make sure you are under 768px you will see at the bottom
Read More
Device compatibility
Country available
Year established
Licensed in
Support
Only "Read more" should be showing, the rest should open on the toggle, but instead the title(strong) is showing and only the content(span) is hidden. I want both and title and content hidden until it toggles.
They all have the code below, but it is not 0px
max-height: 0px;
overflow: hidden;
transition: max-height 0.2s ease-out;
The issue is that overflow: hidden isn't hiding your padding - you need to remove the padding from your divs when hidden, and add it back in when they are rendered
You can do this like so (ln 121 of your demo):
&__desktop, &__mobile, &__payout, &__currency, &__banking, &__platforms, &__countries, &__established, &__licensed, &__support, &__readmore {
//...
&.active {
padding: 0px 40px 20px 40px;
}
// ....
}
Aside: if you want to simplify your JS, here's a starting point:
const accordionEls = document.getElementsByClassName("accordion");
// add an event listener to each element, using a named function definition
Array.from(accordionEls).map(element => {
element.addEventListener("click", toggleVisibility)
});
function toggleVisibility(event) {
// get the element that the event was attached to out of the event object
const { currentTarget } = event;
// generate .review_page__ classnames
const classNames = [
"platforms",
"countries",
"licensed",
"support",
"established"
].map(name => `.review_page__${name}`);
// get the elements with those class names, filtering any null
// values out of the list
const toggleElements = classNames
.map(className => document.querySelector(className))
.filter(Boolean);
// set the .active class on the read more button
currentTarget.classList.toggle("active");
// loop over each element, switching its max height
toggleElements.map(el => {
const currentMaxHeight = el.style.maxHeight;
const maxHeight = currentMaxHeight ? null : `${el.scrollHeight}px`;
el.style.maxHeight = maxHeight;
});
}

Changing an HTML element's style in JavaScript with its CSS transition temporarily disabled isn't reliably functioning [duplicate]

This question already has answers here:
How can I force WebKit to redraw/repaint to propagate style changes?
(33 answers)
Closed 4 years ago.
Currently I am working on an animation for a website which involves two elements having their position changed over a period of time and usually reset to their initial position. Only one element will be visible at a time and everything ought to run as smoothly as possible.
Before you ask, a CSS-only solution is not possible as it is dynamically generated and must be synchronised. For the sake of this question, I will be using a very simplified version which simply consists of a box moving to the right. I shall be referring only to this latter example unless explicitly stated for the remainder of this question to keep things simple.
Anyway, the movement is handled by the CSS transition property being set so that the browser can do the heavy lifting for that. This transition must then be done away with in order to reset the element's position in an instant. The obvious way of doing so would be to do just that then reapply transition when it needs to get moving again, which is also right away. However, this isn't working. Not quite. I'll explain.
Take a look at the JavaScript at the end of this question or in the linked JSFiddle and you can see that is what I'm doing, but setTimeout is adding a delay of 25ms in between. The reason for this is (and it's probably best you try this yourself) if there is either no delay (which is what I want) or a very short delay, the element will either intermittently or continually stay in place, which isn't the desired effect. The higher the delay, the more likely it is to work, although in my actual animation this causes a minor jitter because the animation works in two parts and is not designed to have a delay.
This does seem like the sort of thing that could be a browser bug but I've tested this on Chrome, Firefox 52 and the current version of Firefox, all with similar results. I'm not sure where to go from here as I have been unable to find this issue reported anywhere or any solutions/workarounds. It would be much appreciated if someone could find a way to get this reliably working as intended. :)
Here is the JSFiddle page with an example of what I mean.
The markup and code is also pasted here:
var box = document.getElementById("box");
//Reduce this value or set it to 0 (I
//want rid of the timeout altogether)
//and it will only function correctly
//intermittently.
var delay = 25;
setInterval(function() {
box.style.transition = "none";
box.style.left = "1em";
setTimeout(function() {
box.style.transition = "1s linear";
box.style.left = "11em";
}, delay);
}, 1000);
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
Force the DOM to recalculate itself before setting a new transition after reset. This can be achieved for example by reading the offset of the box, something like this:
var box = document.getElementById("box");
setInterval(function(){
box.style.transition = "none";
box.style.left = "1em";
let x = box.offsetLeft; // Reading a positioning value forces DOM to recalculate all the positions after changes
box.style.transition = "1s linear";
box.style.left = "11em";
}, 1000);
body {
background-color: rgba(0,0,0,0);
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>
See also a working demo at jsFiddle.
Normally the DOM is not updated when you set its properties until the script will be finished. Then the DOM is recalculated and rendered. However, if you read a DOM property after changing it, it forces a recalculation immediately.
What happens without the timeout (and property reading) is, that the style.left value is first changed to 1em, and then immediately to 11em. Transition takes place after the script will be fihished, and sees the last set value (11em). But if you read a position value between the changes, transition has a fresh value to go with.
Instead of making the transition behave as an animation, use animation, it will do a much better job, most importantly performance-wise and one don't need a timer to watch it.
With the animation events one can synchronize the animation any way suited, including fire of a timer to restart or alter it.
Either with some parts being setup with CSS
var box = document.getElementById("box");
box.style.left = "11em"; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = "1em";
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
animation: move_me 1s linear 4;
}
#keyframes move_me {
0% { left: 1em; }
}
<div id="box"></div>
Or completely script based
var prop = 'left', value1 = '1em', value2 = '11em';
var s = document.createElement('style');
s.type = 'text/css';
s.innerHTML = '#keyframes move_me {0% { ' + prop + ':' + value1 +' }}';
document.getElementsByTagName('head')[0].appendChild(s);
var box = document.getElementById("box");
box.style.animation = 'move_me 1s linear 4';
box.style.left = value2; // start
box.addEventListener("animationend", animation_ended, false);
function animation_ended (e) {
if (e.type == 'animationend') {
this.style.left = value1;
}
}
#box {
width: 5em;
height: 5em;
background-color: cyan;
position: absolute;
top: 1em;
left: 1em;
}
<div id="box"></div>

How to get the "declared" CSS value, not the computed CSS value

How can we use JavaScript to get the value of the declared CSS value (like the value that shows up when inspecting an element using Chrome's inspector? Everything I try and search for on the web seems to only want to give the computed value.
For my specific use case I want to know what the width will be at the end of a CSS transition, which is set in the stylesheet, not inline. When I check for the width using JavaScript, I get the computed width, which at the time of retrieval in the script is at the beginning of the transition, so shows me 0 even though in 300ms it will be a declared width like 320px.
Take a look at transitionend event. Can it be used for your use case?
EDIT
Since this answer got upvoted i'm pasting some code below.
element.addEventListener("transitionend", function() {
// get CSS property here
}, false);
I think this is what you're after:
$('#widen').on('click', function(e){
$('.example').addClass('wider');
$('#prefetch').text('The div will be: ' + getWidth('wider'));
});
function getWidth(className){
for (var s = 0; s < document.styleSheets.length; s++){
var rules = document.styleSheets[s].rules || document.styleSheets[s].cssRules; console.log(rules);
for (var r = 0; r < rules.length; r++){
var rule = rules[r]; console.log(rule);
if (rule.selectorText == '.'+className){
console.log('match!');
return rule.style.width;
}
}
}
return undefined;
}
.example {
width: 100px;
background-color: #ccc;
border: 1px solid black;
}
.wider {
width: 320px;
-webkit-transition: width 5s;
-moz-transition: width 5s;
-o-transition: width 5s;
transition: width 5s;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="example">This is a simple container.</div>
<button id="widen" type="button">Widen</button>
<span id="prefetch"></span>
Keep in mind, I believe this still falls victim to cross-domain preventions (if the CSS is hosted on a CDN/other domain, you won't be able to retrieve the styleSheet, and, therefore, not be able to access then eventual width.
I selected the most helpful answer to my question, but it appears that the solution I was looking for does not exist. What I needed was to get what the width of an element would be in pixels BEFORE the transition actually was completed. This width is percent based and so the actual pixels width would vary based on a number of factors. What I ended up doing in reality was:
Making a jQuery clone of the item of which I needed the "end"
transition measurement.
Positioning the clone off screen
Adding inline styles to the clone, remove the CSS inherited transition properties so that it immediately gets the final/end width.
Using JS to save the ending width to a variable
Removing the clone with jQuery's .remove()
Doing this, I now know what the ending width in pixels would be at the moment the element begins to transition, rather than having to wait until the end of the transition to capture the ending width.
Here is a function that does what I described above.
var getTransitionEndWidth = function($el) {
$('body').append('<div id="CopyElWrap" style="position:absolute; top:-9999px; opacity:0;"></div>');
var copyEl = document.createElement('div');
var $copy = $(copyEl);
var copyElClasses = $el.attr('class');
$copy.attr('class', copyElClasses).css({
WebkitTransition: 'all 0 0',
MozTransition: 'all 0 0',
MsTransition: 'all 0 0',
OTransition: 'all 0 0',
transition: 'all 0 0'
}).appendTo($('#CopyElWrap'));
var postWidth = $copy.width();
$('#CopyElWrap').remove();
return postWidth;
};

Resetting state of transition after the transition played

I have a message which I display on the screen when a user clicks on some links. I use a transition on the opacity to get the message to fade away.
The problem is that when a user clicks on the next link which is supposed to display the message, the element has its opacity set to 0 (thus it's not visible).
The opacity transition is triggered by a JavaScript function.
My question: would it be possible to reset the opacity (back to 1) before the transition effect happens?
I only see a nasty way such as triggering a function from within the function that triggers the opacity transition, to reset the opacity back to 1. Something like:
setTimeout(function(){elem.style.opacity = 1;)}, 3000);
But this is not great because I'd like the opacity to be reset as soon as a user clicks another link for which this message is displayed.
ideas?
EDIT:
Fiddle
HTML:
<div id="pop_up" class="pop_up">negative</div>
<a class="something_else" href="#" onclick="show(event, this); return false;">toto</a>
<a class="something_else" href="#" onclick="show(event, this); return false;">titi</a>
CSS:
.pop_up
{
position: absolute:
top: -10px;
padding: 10px;
background-color: orange;
opacity: 1;
transition: opacity 1s linear;
}
JS:
function show(e, elem)
{
msg = document.getElementById("pop_up");
msg.style.top = elem.offsetTop;
msg.style.left = elem.offsetLeft;
msg.style.opacity = 0;
msg.innerHTML = "Hug Me!";
}
I think I know what you want. You need to reset the transition to none and back again each time, and also reset the opacity each time and hide/show. Using the following CSS:
.pop_up
{
position: absolute;
display: none;
padding: 10px;
background-color: orange;
}
and the following javascript:
function show(e, elem) {
var msg = document.getElementById("pop_up");
msg.style.transition = 'none';
msg.style.display = 'none';
msg.style.opacity = 100;
msg.innerHTML = "Hug Me!";
msg.style.display = 'block';
msg.style.top = '' + elem.offsetTop + 'px';
msg.style.left = '' + elem.offsetLeft + 'px';
msg.style.transition = 'opacity 2s linear';
msg.style.opacity = 0;
}
I think you get the effect you want. I have changed to a 2s transition to see ity better, that you can adjust. The point is to hide and reset the popup each time, then reset the transition and show so that the transition runs again.
I have made msg a local vairable (with the vardeclaration) but also agree with the comments that using global functions and inline event handlers like this is not ideal. Improvments to that depnd on if you want to use a js library and if so which or if you want to stick to pure js.
Working fiddle (only tested in Firefox).
This technique stopped working with me, until I nailed done what actually make it work. It's necessary to keep this line in the JS code:
msg.style.bottom = elem.offsetTop + 'px';
If you remove it, it seems like the CSS for the element is not re-evaluated, which means the transition is actually not reset from 'none' to 'opacity 1s ease' for instance, which actually triggers the transition. So, if you remove the lines that actually reset the position of the div, the css won't be re-evaluated. In my case, I ended up needing the element to have a fixed position. So I first do:
msg.style.bottom = elem.offsetTop + 'px';
Immediately followed by:
msg.style.bottom = 0;
The first call internally forces the transition to be reset from none to something else, and then of course I finally positioned the element where I want.
Note that the first line might as well be:
var tmp = elem.offsetTop;
What's important here, is to change the state of the element, so that its CSS is being internally re-evaluated by the browser engine.

CSS3 and Javascript: fade text out and then in using the same function

I'm trying to create a fading out and fading in effect using JavaScript and CSS3. The goal is to have a div shrink in width when clicked and have the text contained within it simultaneously fade out. Then when it is clicked again, the div expands back to its normal width, and the text fades back in.
Here is the HTML:
<div id="box1" onclick="slide1()">
<p class="fader">Lorem ipsum.</p>
</div>
Here is the CSS:
#box1 {
position:relative;
left:0%;
top:0%;
width: 70%;
height: 100%;
background-color: #666;
z-index:4;
}
Here is the javascript:
var box1
var fader
window.onload = function() {
box1 = document.getElementById('box1');
fader = document.getElementsByClassName('fader');
}
function slide1(){
if(box1.style.width=='10%'){
box1.style.width='70%';
fader[0].style.opacity='1';
fader[0].style.transition='opacity 0.25s ease-in';
}
else {
box1.style.width='10%';
fader[0].style.opacity='0';
fader[0].style.transition='opacity 0.75s ease-in';
}
}
It's working for the fade-out, but for the fade-in it is immediately transitioning from 0 opacity to 1 opacity... there's no fade-in. Any ideas?
I actually asked a very similar question with the same issue a while back: Opacity effect works from 1.0 to 0, but not 0 to 1.0. Check the out and see if it works for you.
Otherwise, try adding a class to the fader element instead of adding a style declaration. Then, in your actual CSS, write the code for the fader element transition.
I guess you use even firefox or opera? I think your code won't work on safari or chrome since transition needs webkit-prefix on those browsers. You can use following code to get transition support:
var transform = (function () {
var transforms = [
'OTransform',
'MozTransform',
'msTransform',
'WebkitTransform'
], transform = 'transform';
while (transform) {
if (document.body.style[transform] === undefined) {
transform = transforms.pop();
} else {
return transform;
}
}
return false;
}());
When im using CSS-transition, sometimes I change transition style and then let browser update changes before changing other styles. You can do this with timeout. On some browsers I have noticed that animation is not working unless doing that (some firefox browsers).
fader[0].style.transition='opacity 0.75s ease-in';
setTimeout(function () {
box1.style.width='10%';
fader[0].style.opacity='0';
}, 4);

Categories

Resources