I'm working on something right now, where I had to solve the exact same problem (i.e. setting radius values for CanvasRenderingContext2D.arc()
in percents or ems).
Here is JavaScript code of my solution:
function percentToPixelParentBased(percent, element, width_based=true) {
return percentToPixelElementRelative(percent, element.parentElement, width_based);
}
function emToPixelParentBased(em_value, element) {
return emToPixelElementRelative(em_value, element.parentElement);
}
function percentToPixelElementRelative(percent, element=false, width_based=true) {
if (element) {
if (width_based) {
var dimension = element.style.width || element.offsetWidth;
} else {
var dimension = element.style.height || element.offsetHeight;
}
} else {
if (width_based) {
var dimension = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
} else {
var dimension = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
}
}
var pixels_in_percent = dimension / 100;
return pixels_in_percent * percent;
}
function emToPixelElementRelative(em_value, element=false) {
var font_size = getFontSize(element);
var ppi = getPPI();
var px_in_pt = 72 / ppi; /* 1pt is 1/72 of an inch */
var value_in_pt = em_value * font_size;
return value_in_pt * px_in_pt;
}
function getFontSize(element=false) {
if (element) {
return parseFloat(getComputedStyle(element).fontSize);
} else {
/* getting font size from body */
return parseFloat(getComputedStyle(document.body).fontSize);
}
}
function getPPI() {
var inch_div_html = "<div id='inch-div' style='height: 1in; left: -100%; position: absolute; top: -100%; width: 1in;'></div>";
document.body.insertAdjacentHTML( 'afterbegin', inch_div_html);
var device_pixel_ratio = window.devicePixelRatio || 1;
var ppi_test_div = document.getElementById('inch-div');
var ppi = ppi_test_div.offsetWidth * devicePixelRatio;
document.body.removeChild(ppi_test_div);
return ppi;
}
To poke it a bit, there is demo on JSFiddle.
Now, a bit of explanation/remarks: emToPixelElementRelative
and percentToPixelElementRelative
each converts value passed to it as a first argument em=>px
and %=>px
respectively. Second argument to both functions (element
) is an element, based on which dimensions, values in px
will be calculated (NB: keep in mind that you pass radius, not diameter to the arc()
, which for percents means that if you'll pass a 50(%) and will base calculations on canvas
element — diameter of the circle will be 100% and will fill the whole canvas
(in case of square canvas)), if the argument is not passed — values are calculated, based on a viewport's dimensions. Third argument (width_based
) reflects if values calculated should be relative to width (if set to true
, which is default) or height (if set to false
).
percentToPixelParentBased
and emToPixelParentBased
are just wrappers for percentToPixelElementRelative
and emToPixelElementRelative
, which base conversion on the parent of the passed element
.
getFontSize
returns font size for the element
passed, or for <body>
element, if called without arguments.
getPPI
is getting current PPI via, sort of, hack mentioned in this answer, it accepts a single argument (width_based
) indicating whether to base it's calculations on width (if true
- default) or height (if false
).
Also note, that I have based em=>px
calculations on the following assumptions:
Questions (and answers to them, of course)/resources, which helped me to put this solution together:
- Getting current PPI:
- Getting current font size:
- Percent to pixel conversion:
- Em to pixel conversion:
- Misc: