1

I am writing an application in plain JavaScript and want to define a draw() method that is called whenever an SVG is resized. Unfortunately, simply assigning svg.onresize = draw does not work, nor does the equivalent svg.AddEventListener('resize', draw).

<html><body style="margin:0">

<svg id="svg" width="100%" height="100%"></svg>

<script>

let svg = document.getElementById("svg");

function line(x1, y1, x2, y2)
{
    let e = document.createElementNS(svg.namespaceURI, 'line');
    e.setAttribute('x1', x1);
    e.setAttribute('y1', y1);
    e.setAttribute('x2', x2);
    e.setAttribute('y2', y2);
    e.setAttribute('style', 'stroke:#000');
    svg.appendChild(e);
}

function frame_rect(r)
{
    let e = document.createElementNS(svg.namespaceURI, 'rect');
    e.setAttribute('x', r.x);
    e.setAttribute('y', r.y);
    e.setAttribute('width', r.width);
    e.setAttribute('height', r.height);
    e.setAttribute('style', 'stroke:#000;fill:none');
    svg.appendChild(e);
}

function draw()
{
    svg.innerHTML = ''; // remove all elements from the SVG
    let r = svg.getBoundingClientRect();
    line(r.x,r.y,r.x+r.width,r.y+r.height);
    line(r.x,r.y+r.height,r.x+r.width,r.y);
    frame_rect(r);
}
draw();


// onresize = draw;
// Works if entire page is resized, or developer tools opened.
// But misses other resize causes like splitter dragged, tab expanded, etc.

// svg.onresize = draw;
svg.addEventListener('resize', draw);
// Either of the above *should* work, per:
//
//   https://developer.mozilla.org/en-US/docs/Web/API/SVGElement
//
// But neither does, even if the entire window is resized.

svg.onclick = draw;
// Just to show that draw() can be called in response to an event.

</script></body></html>

As explained in the comment, I can use window.onresize, but this is a hack that will only catch SVG resizes that are caused by the entire window being resized. OK, and it also happens to work if the SVG is resized due to the developer tools being opened, but that is presumably because this also resizes the entire window.

Also, svg.onclick = draw causes the SVG to be redrawn in response to a mouse click, so it seems like I'm attaching the draw() handler to the correct object. Why would onclick be any different from onresize in terms of ability to catch events?

What is the proper way to catch SVG resizes that have been propagated from any source, and not just window resizes?

personal_cloud
  • 3,943
  • 3
  • 28
  • 38

2 Answers2

1

The browsers don't seem to support the SVGResize event. But it is simple enough to check if the SVG size has changed in a window resize event handler.

let svg = document.getElementById("svg");
let lastSize = null;

function line(x1, y1, x2, y2)
{
    let e = document.createElementNS(svg.namespaceURI, 'line');
    e.setAttribute('x1', x1);
    e.setAttribute('y1', y1);
    e.setAttribute('x2', x2);
    e.setAttribute('y2', y2);
    e.setAttribute('style', 'stroke:#000');
    svg.appendChild(e);
}

function frame_rect(r)
{
    let e = document.createElementNS(svg.namespaceURI, 'rect');
    e.setAttribute('x', r.x);
    e.setAttribute('y', r.y);
    e.setAttribute('width', r.width);
    e.setAttribute('height', r.height);
    e.setAttribute('style', 'stroke:#000;fill:none');
    svg.appendChild(e);
}

function resize(evt)
{
    let r = svg.getBoundingClientRect();
    if (lastSize && (lastSize.width !== r.width || lastSize.height !== r.height))     {
      draw();
    }
}

function draw()
{
    svg.innerHTML = ''; // remove all elements from the SVG
    let r = svg.getBoundingClientRect();
    line(r.x,r.y,r.x+r.width,r.y+r.height);
    line(r.x,r.y+r.height,r.x+r.width,r.y);
    frame_rect(r);
    lastSize = r;
}
draw();

window.addEventListener('resize', resize);
body {
  margin: 0;
}
<svg id="svg" width="100%" height="100%"></svg>
Paul LeBeau
  • 97,474
  • 9
  • 154
  • 181
  • OK, so anything (splitter, multitab pane, etc) that changes the SVG size should dispatch a full window resize. It is efficient because each individual SVG can then check whether its size has changed, and does nothing if the size has not changed. There may be a few hundred SVGs but the check should be fast. OK I can go with that. I'm surprised the browser doesn't just propagate the resize, like it does with mouse clicks (but top down instead of bottom up). – personal_cloud Jun 16 '19 at 20:15
  • 1
    The resize event is a `Window` event only. Normal HTML elements don't have that event. Technically `` elements do have a similar event: `SVGResize`. But browsers have chosen not to implement that event. Perhaps some do, but I only tested Chrome and Firefox. – Paul LeBeau Jun 16 '19 at 22:07
  • That is interesting that browsers don't currently implement the resize event on SVG elements. According to [this table](https://developer.mozilla.org/en-US/docs/Web/API/SVGElement#Browser_compatibility) Chrome and Firefox supported the SVG `resize` event at one time. Evidently they took it out. (Google loves to remove useful features, e.g. [basic SVG path manipulation functions](https://stackoverflow.com/questions/34352624/), just to show how much they care about people's time) – personal_cloud Jun 17 '19 at 17:01
0

You can use the ResizeObserver object. In this example, I've also included code to show the delta change in width & height when the resize occurs.

let svgWidth, svgHeight, resizeInit = false;
let svg = document.getElementById("svg");

function line(x1, y1, x2, y2) {
    let e = document.createElementNS(svg.namespaceURI, 'line');
    e.setAttribute('x1', x1);
    e.setAttribute('y1', y1);
    e.setAttribute('x2', x2);
    e.setAttribute('y2', y2);
    e.setAttribute('style', 'stroke:#000');
    svg.appendChild(e);
}

function frame_rect(r) {
    let e = document.createElementNS(svg.namespaceURI, 'rect');
    e.setAttribute('x', r.x);
    e.setAttribute('y', r.y);
    e.setAttribute('width', r.width);
    e.setAttribute('height', r.height);
    e.setAttribute('style', 'stroke:#000;fill:none');
    svg.appendChild(e);
}

function draw() {
    svg.innerHTML = ''; // remove all elements from the SVG
    let r = svg.getBoundingClientRect();
    line(r.x, r.y, r.x + r.width, r.y + r.height);
    line(r.x, r.y + r.height, r.x + r.width, r.y);
    frame_rect(r);
}

function resize(observer) {
    if (resizeInit) console.log(`Change in width: ${observer[0].contentRect.width - svgWidth}, height: ${observer[0].contentRect.height - svgHeight}`);
    else resizeInit = true;
    svgWidth = observer[0].contentRect.width;
    svgHeight = observer[0].contentRect.height;
    draw();
}

new ResizeObserver(resize).observe(svg);
body {
    margin: 0;
}
<svg xmlns="http://www.w3.org/2000/svg" id="svg" width="100%" height="100%"></svg>
Adam Sassano
  • 234
  • 1
  • 9