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: