I was wondering if it's possible and feasible to convert absolute CSS values to relative ones while maintaining proportions using Javascript. The solution must work with HTML as well as SVG markup.
Say you got this:
<div style="width:100%;height:800px">
<svg width="1000" height="500">
<rect width="30" height="50" x="34" y="24"/>
</svg>
</div>
How would you achieve this?
<div style="width:100%;height:100%">
<svg width="83%" height="62.5%">
<rect width="3%" height="10%" x="3.4% y="4.8%"/>
</svg>
</div>
I'd
div
).width()
and .height()
of this
and the parent elementthis_height/parent_height*100
)What are common pitfalls that may likely happen? There are probably tags that don't have a width or height set, like svg:g
. Then there are non-standard size definitions like in svg:circle
or attributes you don't think about at first, like svg:text
s dy
. Is there a way to take an educated guess about the performance hit due to massive reflows? What else?
Thank you very much!
Ok. So here is what I understand that you want to do in my own words.
Loop through all DOM elements converting absolute dimensions (i.e. height/width in terms of pixels) into relative dimensions (i.e. height/width in terms of percentages). This should be done with sensitivity as to whether the dimension is set using CSS or HTML attributes so as to be usable for SVG elements.
That being said, there is one major constraint: If you want to convert absolute dimensions to relative dimensions using <x>
as the root element, then <x>
must have absolute dimensions. Otherwise, the relative dimensions inside the container will change size based upon their contents instead of the overall size of the parent <x>
.
With that stipulation, what you are asking is fairly easy. This is the algorithm that needs to be used.
<body>
<body>
fixed dimensions<body>
doing the following for each element
To test my code I used the following HTML. It has both the code you include and an instance where a child element is larger than it's parent. This is one case I identified as a boundary condition to test.
<div style="width:100%;height:800px">
<svg width="150" height="200" style="background: red;">
<rect width="30" height="50" x="34" y="24"/>
</svg>
<div style="background: green; width: 50px; height: 50px;">
<p style="background: teal; width: 100px; height: 20px;">Content</p>
</div>
</div>
This is the JavaScript. It is fairly well commented and provides decent console output. For production code I would recommend recommend removing the console logging.
Please keep in mind that the code does not execute immediately, but on a 2 second delay. This way you can see the way the DOM looks before it gets modified.
For those who do not know the following code will return a
if it is present and not false and b
otherwise.
foo = a || b;
We could rewrite like one of the two below, but this is more succinct.
foo = a ? a : b;
if(a)
foo = a
else
foo = b
Here is a jsFiddle.
And the code!
/** convertToRelative
* Convert absolute width/height to relative. Should work for
* Both HTML and SVG objects. Tries to use CSS attributes, falls
* back on HTML attributes.
*/
function convertToRelative(element) {
// Get Element and Parent
element = $(element);
parent = element.parent();
// If there is no valid with, we need to use attributes
var useAttr = !element.width();
// Get dimensions from css or attributes
var width = element.width() || element.attr('width');
var height = element.height() || element.attr('height');
var pWidth = parent.width() || parent.attr('width');
var pHeight = parent.height() || parent.attr('height');
// Find relative dimensions
var relWidth = (width / pWidth) * 100.0;
var relHeight = (height / pHeight) * 100.0;
// Log info
console.log(element);
console.log("1: "+ parent.height() +", 2: "+ parent.attr('height'));
console.log("Width: "+ width +", Height: "+ height);
console.log("PWidth: "+ pWidth +", pHeight: "+ pHeight);
console.log("relWidth: "+ relWidth +", relHeight: "+ relHeight);
// Clear current stylings
element.removeAttr('width');
element.removeAttr('height');
// Apply relative dimensions
if (useAttr) {
element.attr('width', relWidth +'%');
element.attr('height', relHeight +'%');
} else {
element.width(relWidth +"%");
element.height(relHeight +"%");
}
}
/** walk
* Walk through a DOM element and all its sub elements running
* the convertToRelative function to convert absolute positions
* to relative positions.
* DOES NOT CONVERT THE FIRST ELEMENT, ONLY CHILDREN.
*/
function walk(element) {
$(element).children().each(function() {
convertToRelative(this);
walk(this);
});
}
/** fixBody
* In order to play with relative dimensions for children of the
* body, we need to fix the dimensions of the body. Otherwise it
* will resize as we begin to use relative dimensions.
*/
function fixBody() {
$('body').css('width', $('body').width() +"px");
$('body').css('height', $('body').height() +"px");
}
// Walk the body and all children on a 2 second delay.
setTimeout(function() {
console.log('Running Conversion');
fixBody()
walk('body');
console.log('Conversion Finished');
}, 2000);
Let me know if this is not what you wanted, or you run into a problem!
For your questions
What are common pitfalls that may likely happen?
The most common pitfall is going to be an incorrect conversion. Something will go wrong in the transformation and the resulting DOM will look nothing like the input.
There are probably tags that don't have a width or height set, like svg:g. Then there are non-standard size definitions like in svg:circle or attributes you don't think about at first, like svg:texts dy.
There are tags that do not have a width
/height
element. However, using jQuery should give us a bit more robustness in this department. I have not worked with jQuery and SVG much, but there is a plugin for support available if we run into any problems with the more obscure elements.
Is there a way to take an educated guess about the performance hit due to massive reflows?
I believe that this code will run in O(m * n)
Where m = delay for a single reflow
and n = number of nodes
. The Reflows should have a fairly consistent change because we are converting the largest elements before the smallest. BUT I may be proven wrong on that last statement.
What else?
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