2

I have been trying to make a responsive website which includes a canvas.

While drawing a circular progress bar, am invoking the arc() function, but its radius argument, looks like it needs to be in pixels, but I would need it as percents, vw, vh, em or any other unit css proposes.

Of course, I could render it in pixels and stretch it using CSS, but it will either be upscaled and pixelated, downscaled and be a waste of resources, or otherwise distorted.

How should I do it?

Magix
  • 4,989
  • 7
  • 26
  • 50

2 Answers2

1

After some searching, I stumbled upon this question which helped me solve the problem.

Here's how I did it :

var w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);

var vw = w/100;
var vh = h/100;

var pxradius = 50;
var vwradius = pxradius*vw;

unfortunately, I still have no solution for percents and ems

Magix
  • 4,989
  • 7
  • 26
  • 50
  • It seems to me, that your code exactly converts percentage value of `pxradius` into pixel value. So it is a solution for percents. Am I missing anything? –  Nov 15 '17 at 09:47
  • 1
    Well, not really, as percents are relative to the container in which the element is placed (afaik) and here it is only relative to the window height/width – Magix Nov 15 '17 at 10:56
1

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:

  1. Getting current PPI:
  2. Getting current font size:
  3. Percent to pixel conversion:
  4. Em to pixel conversion:
  5. Misc: