Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting inline css transition style with string alters the true value. Bizarre

I have a stylesheet which sets a css transition property like so (prefixed versions omitted for brevity):

transition: opacity 1s;

Then I have a number of elements on the page, and I wish to modify the transition-delay property of each element via JavaScript, to give a stagger effect. I am using jQuery like so:

$(element).css('transition-delay', delay + 's');

However, the above code does not add an inline transition-delay: Xs to the element. Instead, it results in:

<div style="transition: Xs;">

But that's fine, because it works as expected. Somehow, the browser knows that transition: Xs really means to just set the transition-delay to Xs and leave the rest intact.

However:

If I now get the inline style of that element via $(element).attr('style'), and then re-apply it to the element, $(element).attr('style', style), the HTML looks exactly the same, but now the transition has totally overwritten the other properties and essentially sets the element's transition value to all Xs ease 0s.

// HTML before - working
<div style="transition: Xs">

// then I do this
var style = $(el).attr('style');
$(el).attr('style', style);

// HTML after - broken!
<div style="transition: Xs">

Demo

A JSFiddle of exactly what I have described: http://jsfiddle.net/7vp8m/4/

What is going on?

like image 556
Michael Bromley Avatar asked Jul 24 '14 20:07

Michael Bromley


1 Answers

I think just writing out the question and coding that demo really helped me to find the answer:

The HTML style attribute is not the actual style. We need to use the CSSStyleDeclaration object

Although it seems that the inline style is as simple as whatever is contained in the style="..." HTML attribute (as I had assumed), it turns out that this is not the case. Behind the scenes, inline styles (and all other styles) are actually defined by an object called CSSStyleDeclaration. The string contained in the style attribute only represents this object, but does not contain all the information needed to define a style.

This is why setting `el.style = "width: 100px;" does not work. From the MDN article on HTMLElement.style:

Except in Opera, styles can not be set by assigning a string to the (read only) style property, as in elt.style = "color: blue;". This is because the style attribute returns a CSSStyleDeclaration object. Instead, you can set style properties like this:

elt.style.color = "blue";  // Directly

var st = elt.style;
st.color = "blue";  // Indirectly

So this shows us why doing $(el).attr('style', 'transition: Xs'); will not work as expected - and this is exactly what I was running into. Doing so will modify the underlying CSSStyleDeclaration object, but not always in the way we want it to (hence my original question).

The solution is therefore to use the API provided by CSSStyleDeclaration. The following SO question proved crucial to me understanding this issue: JavaScript & copy style

Copying a CSSStyleDeclaration:

var originalStyle = el.cloneNode().style;

Here we are using the cloneNode() method because otherwise (if we just get el.style) the CSSStyleDeclaration object is copied by reference, which is not what I want since I will be changing the element's inline style and then I want to restore the original style later. Cloning the element first allows us to get a "fresh" copy of the CSSStleDeclaration that will not change when we alter the inline styles of our element el.

Replacing current inline style with the saved CSSStyleDeclaration

// first we need to delete all the style rules
// currently defined on the element
for (var i = el.style.length; i > 0; i--) {
    var name = el.style[i];
    el.style.removeProperty(name);
}

// now we loop through the original CSSStyleDeclaration 
// object and set each property to its original value

for (var i = originalStyle.length; i > 0; i--) {
    var name = originalStyle[i];
    el.style.setProperty(name,
        originalStyle.getPropertyValue(name),
        priority = originalStyle.getPropertyPriority(name));
}

Demo

Here is an update of my original demo that implements the above methods: http://jsfiddle.net/7vp8m/11/

like image 106
Michael Bromley Avatar answered Oct 30 '22 11:10

Michael Bromley