i'm trying to get back a style property in all valid 'length' and 'percent' units, converted from the original value set for that property.
e.g., if i have a div with style.width set to 20%, i'd want back an object with that value in percent (of course, 20%), pixels (whatever the actual pixel width is), em, pt, ex, etc.
i realize that 'percentage' is not a 'length' value, and that not all properties that accept length values accept percentage, but want to include that as well.
of course, some values will be dependent on the element specifically, and possibly it's position in the DOM (e.g., getting the em value will require that element's parent computed font size as well).
i can assume that the style is set explicitly for the element - i'm aware of how to retrieve the current computed style of an element - i'm just hoping to not repeat work someone else has probably already done. i'm also aware of http://www.galasoft.ch/myjavascript/WebControls/css-length.html, but it relies on style.pixelWidth or node.clientWidth, and fails in Chrome (I'd assume it fails in Safari as well... and probably others).
i've already got color values worked out (rgb, rgba, hex, name) - this is of course a lot more straightforward. i'm working with values that are mathematically mutable, so really only need 'length' and 'percent' values (if called on a property set with a non-length, non-percent value - like 'font-size: larger' - the function could fail, or throw an error).
if written procedurally, something like this would be ideal:
function getUnits(target, prop){
var value = // get target's computed style property value
// figure out what unit is being used natively, and it's values - for this e.g., 100px
var units = {};
units.pixel = 100;
units.percent = 50; // e.g., if the prop was height and the parent was 200px tall
units.inch = 1.39; // presumably units.pixel / 72 would work, but i'm not positive
units.point = units.inch / 72;
units.pica = units.point * 12;
// etc...
return units;
}
I'm not asking for someone to write code for me, but my hope is that someone has already done this before and it's available in some open-source library, framework, blog post, tut, whatever. failing that, if someone has a clever idea how to streamline the process, that'd be great as well (the author of the link above created a temporary div and computed a single value to determine the ratios for other units - a handy idea but not one i'm entirely sold on, and definitely one that'd need supplemental logic to handle everything i'm hoping accept).
thanks in advance for any insight or suggestions.
CSS has several different units for expressing a length. Many CSS properties take "length" values, such as width , margin , padding , font-size , etc. Length is a number followed by a length unit, such as 10px , 2em , etc.
Summary: to convert units, construct a fraction that is equal to 1, multiply the original measurement by that fraction, and simplify.
ch. Character unit. The CSS ch unit is defined as the width of the character 0 (zero, or U+0030) of the font. While the ch unit works as an exact measurement for monospaced / fixed width fonts like Courier, it can be unpredictable with proportional fonts like Arial.
The full form of VW is viewport width. It works like the percentage unit. Specifying 10vw is equivalent to occupying 10% of entire visible screen width.
EDIT: updated to allow user to pick a single unit to be returned (e.g., exists as %, get back in px) - big improvement in performance for when that's enough - might end up changing it to just accept a single unit to convert, and get rid the loops. Thanks to eyelidlessness for his help. /EDIT
this is what i've come up with - after preliminary testing it appears to work. i borrowed the temporary div idea from the link mentioned in the original question, but that's about all that was taken from that other class.
if anyone has any input or improvements, i'd be happy to hear it.
(function(){
// pass to string.replace for camel to hyphen
var hyphenate = function(a, b, c){
return b + "-" + c.toLowerCase();
}
// get computed style property
var getStyle = function(target, prop){
if(prop in target.style){ // if it's explicitly assigned, just grab that
if(!!(target.style[prop]) || target.style[prop] === 0){
return target.style[prop];
}
}
if(window.getComputedStyle){ // gecko and webkit
prop = prop.replace(/([a-z])([A-Z])/, hyphenate); // requires hyphenated, not camel
return window.getComputedStyle(target, null).getPropertyValue(prop);
}
if(target.currentStyle){ // ie
return target.currentStyle[prop];
}
return null;
}
// get object with units
var getUnits = function(target, prop, returnUnit){
var baseline = 100; // any number serves
var item; // generic iterator
var map = { // list of all units and their identifying string
pixel : "px",
percent : "%",
inch : "in",
cm : "cm",
mm : "mm",
point : "pt",
pica : "pc",
em : "em",
ex : "ex"
};
var factors = {}; // holds ratios
var units = {}; // holds calculated values
var value = getStyle(target, prop); // get the computed style value
var numeric = value.match(/\d+/); // get the numeric component
if(numeric === null) { // if match returns null, throw error... use === so 0 values are accepted
throw "Invalid property value returned";
}
numeric = numeric[0]; // get the string
var unit = value.match(/\D+$/); // get the existing unit
unit = (unit == null) ? "px" : unit[0]; // if its not set, assume px - otherwise grab string
var activeMap; // a reference to the map key for the existing unit
for(item in map){
if(map[item] == unit){
activeMap = item;
break;
}
}
if(!activeMap) { // if existing unit isn't in the map, throw an error
throw "Unit not found in map";
}
var singleUnit = false; // return object (all units) or string (one unit)?
if(returnUnit && (typeof returnUnit == "string")) { // if user wants only one unit returned, delete other maps
for(item in map){
if(map[item] == returnUnit){
singleUnit = item;
continue;
}
delete map[item];
}
}
var temp = document.createElement("div"); // create temporary element
temp.style.overflow = "hidden"; // in case baseline is set too low
temp.style.visibility = "hidden"; // no need to show it
target.parentNode.appendChild(temp); // insert it into the parent for em and ex
for(item in map){ // set the style for each unit, then calculate it's relative value against the baseline
temp.style.width = baseline + map[item];
factors[item] = baseline / temp.offsetWidth;
}
for(item in map){ // use the ratios figured in the above loop to determine converted values
units[item] = (numeric * (factors[item] * factors[activeMap])) + map[item];
}
target.parentNode.removeChild(temp); // clean up
if(singleUnit !== false){ // if they just want one unit back
return units[singleUnit];
}
return units; // returns the object with converted unit values...
}
// expose
window.getUnits = this.getUnits = getUnits;
})();
tyia
Check out Units, a JavaScript library that does these conversions.
Here's a blog post by the author describing the code.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With