When setting multiple transform properties on DOM element style, the browser is not showing all set properties (both -webkit-, ms, and regular) - for example :
element.style.tranform = 'rotate(90deg)';
element.style.webkitTranform = 'rotate(90deg)';
element.style.msTranform = 'rotate(90deg)';
will produce the following inline style attribute
<div style="transform:rotate(90deg)" ></div>;
This means that the browser is not settings ALL of the properties, instead it is taking only one of them.
This is a problem, because we have to send the HTML as is to the server side in order to produce PDF. so, instead we have found a workaround which works (kind of) - using element.setAttribute('style','transform...-webkit- etc...');
The above approach works, but we have to reconstruct the style attribute each time, and the transform must be applied only AFTER all of the other properties have been set, which is not so elegant.
Does anyone knows how to work around this?
Thanks in advance!
I think you've already found your workaround: Using setAttribute. It's not pretty, but with prefixed properties, you can't trust the HTML from browser X will work in browser Y, since there's no reason browser X would include invalid (from its perspective) properties when generating the text for the style attribute's value.
With your workaround, you'll need to be careful of at least two things:
That you test carefully in your target browsers. I think all browsers faithfully retain what you set with setAttribute (subject to #2 below), but...test. :-)
Be sure nothing in your code sets a style via the style object after you've done the setAttribute thing, since that will make the browser drop the (from its perspective) invalid style text. For instance, on Chrome, Firefox, Edge and Safari (at least), the following drops the invalid foo: bar style when I set fontWeight:
var element = document.getElementById("element");
element.setAttribute("style", "foo: bar");
console.log("before using style:", element.getAttribute("style"));
element.style.fontWeight = "bold";
console.log("after using style: ", element.getAttribute("style"));
<div id="element"></div>
Related
I have ran into extremely suprising behavior of some older jQuery that does not allow to modify svg element width or height attributes (not styles). It just does not modify them and always silently fails.
It works fine with attributes like mywidth, but not with width.
Note that is related to some very old jQuery version (v1.4.3), but still I am just curious about reasoning behind that.
The setup is simple:
<svg id='mysvg'></svg>
and JS code:
$('#mysvg').attr('width','100');
does nothing, where:
$('#mysvg').attr('mywidth','100');
works fine.
I was supposing some other scripts overrides the changes and tried to affect the attribute after a timeout or on click, but behavior is still the same.
Also I've tried to put breakpoint on attribute modification in Chrome, but it also does not work, it triggers for .attr('mywidht','100') but does not for .attr('width','100). It never generates a console error, always fails silently.
Is there any historical reason or unfamous bug that prevents modifying width and height attribute of svg HTML tag?
Note that with some custom tag like <mysvg> it works also fine and adds width attribute.
JSFiddle reproducing it: https://jsfiddle.net/d8703afx/
Looks like the attr('width') returns some "special" [object SVGAnimatedLength]
That $("#mysvg").attr('width') is returning an SVGAnimatedLength object is actually a good hint. The function does not return the attribute, as this DOM method would:
document.querySelector("#mysvg").getAttribute('width')
but the property
document.querySelector("#mysvg").width
which, for SVG elements that implement that property is an object of type SVGAnimatedLength with roughly this structure:
{
baseVal: {
unitType: number,
value: number,
valueInSpecifiedUnits: number,
valueAsString: number
},
animVal: { /* etc */ }
}
The difference to HTML width properties is that SVG provides for animated values and different unit identifiers.
So probably jQuery, when executing .attr('width', value) tries also not to set the attribute, but the width property to a number, which would be correct for HTML elements. But for SVG it must fail, as the object is readonly.
You can simply set the attribute with the DOM method
document.querySelector("#mysvg").setAttribute('width', 100);
The jquery version you are using (1.4.3) was released in 2010, when the up to date IE was still IE8, and we still had to support things like IE6, y'know the word "HTML5" was a bit like "flying car" at this time...
You should not use such an old version of this library nowadays.
Many bugs, and new features have been fixed/added in newer versions.
E.g a better support for SVG manipulation, which at this time was still an "upcoming" technology.
So by using an up-to-date jQuery version, (or a slightly newer one) you'll be able to do what you want:
$("#mysvg").attr('xxwidth', '333');
$("#info").append("The xxwidth is: " + $("#mysvg").attr('xxwidth') + '<br>');
$("#mysvg").attr('width', '333');
$("#info").append("The width is: " + $("#mysvg").attr('width'));
<!-- not even up to date... current is 3.3.1 -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg id="mysvg"></svg><br>
<span id="info"></span>
Now that support for custom css properties is becoming widespread, I had in mind to use them to simplify the creation of scroll-based animations. In Javascript, I'm using style.setProperty('--customProperty', value) to adjust the custom properties on specific elements as the user scrolls.
It works beautifully in Chrome, Firefox, and Safari 10.
But, in Safari 9.1 (which does support custom properties), I can only set the property once. After having been set, it will not update to a new value.
I've got it all in CodePen: https://codepen.io/kirkhadden/pen/JJbXmE/
// Have we scrolled since the last frame?
if (position != wrapper[0].scrollpos) {
// Keeps updating accurately every frame:
window.log.text(position);
// Only happens on the first frame:
wrapper[0].style.setProperty('--scrollpos', position+'px', '');
wrapper[0].scrollpos = position;
} else { // No Change
return false;
}
I can't find any information or even mention of this behavior. I've tested other, simpler uses of style.setProperty() in Safari, and I continue to find that once a property is set, Safari won't update the same property, even if I try to remove the property first.
Is this a bug in Safari 9.1? Is there a work-around? Is there another way to use javascript to set css variables?
Update
So, instead of style.setProperty, I could instead use jQuery's .attr() method to set the property. It's not ideal, since that will overwrite any other style properties, but it works for this.
The bigger problem is that this whole solution is based on the idea of setting ordinary css animations on all my animated elements, but setting the play-state to 'paused', and then using javascript to manipulate the animation-delay according to the scroll position. This allows me to take advantage of inheritance to animate lots of things with minimal DOM manipulation.
Once again, Safari 9.1 is the road block, since it appears that unlike Chrome or Firefox, if the play-state is 'paused', Safari does not start the animation at all, and ignores the animation-delay.
You can try to polyfill CSS variables via JavaScript
For example:
let variables =
{
color: "red",
border: "2px solid blue"
}
// Get STYLE tag
let style = document.getElementById('custom');
// Save its original text
style.dataset.source = style.innerHTML;
function updateStyle()
{
// Replace variables names with their values
style.innerHTML = style.dataset.source.replace(/#(\w+)/g, function(match, name)
{
return variables[name];
});
}
updateStyle();
Now you can use #variableName in your CSS and it'll be replaced with value of variables.variableName
Fiddle: https://jsfiddle.net/JacobDesight/v7mqps84/
#EDIT
You can even create a function for settings variables:
function setProperty(name, value)
{
variables[name] = value;
updateStyle();
}
Now you just simply do something like:
setProperty('color', 'green');
And it will automatically update styles.
Fiddle: https://jsfiddle.net/JacobDesight/v7mqps84/1/
I'm using the CSS content attribute to pass some values from my LESS stylesheet to JavaScript (to use some colors defined in LESS in Canvas elements).
To make my life easier I decided to place these values in a easy way to parse them in JavaScript.
LESS code:
div#colorChart-critical {
content:'#{critical-highest},#{critical-veryhigh},#{critical-high},#{critical-low},#{critical-medium},#{critical-verylow}';
}
which when compiled brings the following CSS:
div#colorChart-critical6 {
content: '#ff0000,#ff7200,#fffc00,#0000ff,#a200ff,#00ff00';
}
Then I try to read them using jQuery:
$("div#colorChart-critical").css("content").split(",");
The problem is that in IE9 calling $("div#colorChart-critical").css("content") is returning the string "normal" for some reason. Opera, Firefox, Safari and Chrome works fine.
Why does this happen in IE9?
Any work-around this issue on IE9? If not any other CSS atribute I can put random texts in?
I could use something like:
background: url(#ff0000,#ff7200,#fffc00,#0000ff,#a200ff,#00ff00);
But this would generate errors on the console.
It's because content as defined in CSS2.1 doesn't work on elements, only on the :before and :after pseudo-elements. IE9 is simply following the CSS2.1 spec here, which mandates that content on elements be computed to normal, always.
I don't know why other browsers would return the value you have defined, especially considering that .css() makes use of getComputedStyle() on those browsers. If they're implementing CSS2.1 content, then they're violating CSS2.1 by not computing the value to normal. If they're preparing for a late CSS3 implementation, whatever that may be, then it would make sense that they implement it on actual elements somehow... shame on them either way.
Which brings me to another point: if you're not actually trying to use CSS to modify the content of an element, don't use content, even if the fact that it's not defined for use with elements is the reason you're making use of this technique in the first place. You can try assigning those colors to certain classes, creating a hidden element and querying that element's color styles instead.
BoltClock answer shows the cause of my problems. I found a work-around by using the font-family instead of the content CSS property.
My LESS code:
div#colorChart-maincolors {
font-family: '#{colorChart1},#{colorChart2},#{colorChart3},#{colorChart4},#{colorChart5},#{colorChart6}';
}
Which compiled into CSS gives:
div#colorChart-maincolors {
font-family: '#c0392b,#2980b9,#2ecc71,#f1c40f,#ecf0f1,#34495e';
}
The string can be acquired using:
removeQuotes= function(string) {
return string.replace(/^['"]+|\s+|\\|(;\s?})+|['"]$/g, '');
};
removeQuotes($("#colorChart-maincolors").css("font-family")); //add a .split(',') to get the colors as an array
The function removeQuotes is necessary because each browser adds a different kind of quotes into the return of getComputedStyle (and by extension the jQuery .css() method). IE9 adds a double quote, Webkit adds a single quote.
See this post on CSS tricks: http://css-tricks.com/making-sass-talk-to-javascript-with-json/ for more information.
you can use replace(/["']/g, "") to remove extra quotation from string
""string"" will be change to "string"
I have an IMG element c. The following works to change its cursor (in Chrome):
c.style.cursor='-webkit-zoom-out';
But if I try to add compatibility for other browsers, as in the following, it breaks:
c.style.cursor='-webkit-zoom-out, -moz-zoom-out';
(And I haven't even gotten to trying to link a .cur for IE-compatibility....) What's the correct syntax to add multiple values to a style element?
You have to add them separately:
c.style.cursor = '-webkit-zoom-out';
c.style.cursor = '-moz-zoom-out';
Unfortunately zoom-in and zoom-out are not supported by Internet Explorer yet. See the MDN Cursor: Browser compatibility.
Demo
Try before buy
Alternatively you can create a CSS class that sets the correct styles and add or remove this class using JavaScript if you need to toggle it.
When we run following script under Firefox ...
var d = $("<div class='test'></div>");
d.hide();
$("body").prepend(d);
d.show();
... and look to HTML, this inserted element will have style attribute:
style="display: block;"
Under Webkit, element will have:
style="display: none;"
This scenario in used in a JavaScript component, i develop. This component has a collection of HTML elements in it's internal variables, and inserts they to specified destination container.
Because inserted element having display-property initialized in style attribute, it overrides CSS. This breaks the layout of a page.
As a quick solution, i can store "style" attribute, before element is inserted to the DOM, and, after inserting, write stored version over created one.
Is there any better solution ?
Why this ever happens and how can i check, whether element is not yet inserted to the DOM ?
When I do that with either Chrome or Safari (both WebKit-based browsers), if I inspect the element with the built-in tools, it has no style.display property at all, and so the default div style of display: block is used. (Here's a version with some text in the div, so it's easier to see and to find with the DOM inspector.)
So I suspect the problem lies elsewhere. For instance, is there intervening code that may be failing on WebKit, so that d.show(); never ends up getting called? That would certainly explain it. Easy enough with the built-in tools in Chrome or Safari to set a breakpoint on the code creating the div and walk through it.
Re your question:
...how can i check, whether element is not yet inserted to the DOM ?
That question was asked here on StackOverflow just recently, and one of the answers specific to jQuery was rather elegant:
if (d.closest("body").length == 0) {
// It's not in the DOM yet
}
Update: Re your comment below
Look at this test page with Firefox. The div has "style=display: block;" explicitly defined. Under Webkit, it has empty style attr. I'm using built-in inspector in both Firefox and Safari.
Ah, okay, so the problem isn't a display: none in WebKit browsers (your statement about that in the question led me astray), but rather that Firefox (and possibly other Gecko browsers) end up having display: block on the element.
I'd probably approach this like this:
var d = $("<div class='test'></div>");
d.addClass("hidden");
$("body").prepend(d);
d.removeClass("hidden");
...with this CSS:
.hidden {
display: none;
}
Live copy
That way, you know you won't end up with a style.display property set at all.
Update 2: The other thing you can do is remove the style.display property directly:
var d = $("<div class='test'>Hi there</div>");
d.hide();
$("body").prepend(d);
d.show();
d[0].style.display = "";
Live example
You alluded to effects, so if you're doing fadeIn or something like that, use the callback:
var d = $("<div class='test'>Hi there</div>");
d.hide();
$("body").prepend(d);
d.fadeIn(function() {
this.style.display = "";
});
Live example
How about if you do
var d = $("<div class='test'></div>");
d.hide();
$("body").prepend(d);
d.removeAttr('style'); // this will revert to the original styling