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;
};
Related
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.
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>
I have an Angular app with a button that has a label of "+"
On mouse-over I call element.append(' Add a New Number'); This adds that text new to the + in the label.
Use clicks the button, new number is added, label of button is returned to "+"
I would like to animate the button size change and/or the txt label change. So far, just adding a css transition to width does nothing.
Thoughts?
UPDATE:
To help clarify, this is a bootstrap input group button. I don't want to set widths or css transforms, to avoid breaking the group either here or at other screen sizes.
here are the 2 states:
I was simply letting the existing button stretch due to the injection of more words.
I am probably guessing you don't have a predefined width. anyways you could use transform-origin and scale to achieve such an effect
FIDDLE HERE
HTML:
<button id="btn">Click</button>
CSS:
#btn {
outline: none;
border:none;
background: orange;
padding: 1em 1.5em;
-webkit-transition: .3s;
-o-transition: .3s;
transition: .3s;
}
#btn:hover {
-webkit-transform: scaleX(1.2);
-ms-transform: scaleX(1.2);
-o-transform: scaleX(1.2);
transform: scaleX(1.2);
-webkit-transform-origin:0 0;
-moz-transform-origin:0 0;
-ms-transform-origin:0 0;
-o-transform-origin:0 0;
transform-origin:0 0;
}
you should use CSS transforms for animations rather than a property like width. The animation is slightly jerky , so you might want to work on it a bit more.
You had jQuery tagged, so this is how I would do it.
All the transitions. fade + animate
function changeButtonText(button, text){
// jQuery it
$button = $(button);
// get orinal css'es
oooon = $button.css('text-align');
doooo = $button.css('overflow');
treee = $button.css('white-space');
$button.css('text-align', 'left').css('overflow', 'hidden').css('white-space', 'nowrap');;
// get new width first
$tmpBtn = $button.clone().append(text).css('opacity', '0.0').appendTo('body');
newWidth = $tmpBtn.outerWidth();
$tmpBtn.remove();
// now stretch the button out
$button.animate({width: newWidth+"px"});
// fade texts into the butt
$button.append('<span style="display:none">'+text+'</span>');
$btnText = $button.find('span').fadeIn('slow');
return {
'text-align':oooon,
'overflow':doooo,
'white-space':treee
};
}
Fiddle
I think that with bootstrap CSS and Angular - it will be more complex, but this is how I would go about it programatically. You'll have to deal with the model and the data differently - and you should probably build a directive to repeat the action and integrate with Angular smoothly:
HTML
<div class="thing">+ <span id="message">
<span id='target'></span>
</span></div>
JavaScript
$('.thing').hover( function() {
var originalWidth = $(this).outerWidth();
$messageHolder = $(this).find('#message');
$target = $(this).find('#target');
$target.text('Some helpful text');
var targetWidth = $target.outerWidth();
$messageHolder.animate({
width: targetWidth
}, {
duration: 200,
complete: function() {
$messageHolder.animate({
opacity: 1
}, 500);
}
});
});
$('.thing').on('click', function() {
$target = $(this).find('#target');
$target.empty();
$messageHolder = $(this).find('#message');
$messageHolder.animate({
opacity: 0
}, {
duration: 200,
complete: function() {
$messageHolder.animate({
width: 0
}, 200);
}
});
});
I'm sure that Angular's ng-animate library watches the dom and also has an excellent way of animating things as they change in the model/controller or whatever they are calling it. This is probably something what it looks like behind the scenes.
Good luck!
jsFiddle
I have a string like
content = "content1<br/>content2<br/>"
and I add content3 into string the result gonna be like
content1
content2
content3
I want the container of content animate height while showing new content.
I have tried
document.getElementById("detail").innerHTML = content;
$("detail").animate({
height: 'auto'
}, 1000);
it's just appear without animate
It's a tricky to animate to unknown height. I would try to make use of the combination of CSS transition and javascript helper function to calculate new width.
Of course you can still go with just animate but you will still need to calculate new height and animate to new height.
var content = "content1<br/>content2<br/>";
var height = getHeight(content);
$("#detail").css('max-height', height).html(content);
setTimeout(function() {
content += 'content3<br/>';
var height = getHeight(content);
$("#detail").css('max-height', height).html(content);
}, 1000);
function getHeight(str) {
var $tmp = $("<div>").html(content).css({position: 'absolute', top: -1000}).appendTo('body'),
height = $tmp.height() + 10 + 'px';
$tmp.remove();
return height;
}
#detail {
background: #EEE;
overflow: hidden;
max-height: 0;
transition: max-height 1s ease;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="detail"></div>
That's because jQuery .Animate() only works with numbers.
for example your current height is: 20px and you make it 30px in animate function.
jQuery will rise 20 to 30 in the declared time and set it to the element continuously until it gets to 30.
on the other hand CSS acts more quickly than JS and makes the element auto-heighted automatically.
So you have to use numbers to animate the new height (and the previous height has to be a number too) OR you can use CSS3 transitions.
FIDDLE here
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.