Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid scaling of elements inside foreignObjects of svgs?

Tags:

html

css

svg

I want to use a svg as container for a div element which should contain several elements. At the moment it looks like this:

<body>
    <svg width="100%" height="100%" viewBox="0 0 45 90" version="1.1" xmlns="http://www.w3.org/2000/svg" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
       <path d="M45.02,17.449l0,-5.837l-0.324,0c0,-3.841 0,-6.21 0,-6.344c0,-0.786 0.105,-3.078 -2.657,-3.659c-5.996,-1.263 -19.539,-1.352 -19.539,-1.352c0,0 -13.543,0.089 -19.539,1.352c-2.762,0.58 -2.657,2.873 -2.657,3.659c0,0.192 0,4.987 0,12.133l-0.324,0l0,14.537l0.324,0c0,22.9 0,52.313 0,52.794c0,0.786 -0.105,3.079 2.656,3.66c5.997,1.262 19.54,1.351 19.54,1.351c0,0 13.542,-0.089 19.539,-1.351c2.762,-0.581 2.657,-2.874 2.657,-3.66c0,-0.594 0,-45.159 0,-67.283l0.324,0Zm-22.52,-13.778c0.535,0 0.969,0.434 0.969,0.969c0,0.536 -0.434,0.97 -0.969,0.97c-0.535,0 -0.969,-0.435 -0.969,-0.97c0,-0.536 0.434,-0.969 0.969,-0.969Zm20.262,75.595l-40.525,0l0,-71.234l40.524,0l0,71.234l0.001,0Z" style="fill-rule:nonzero;"></path>
       <foreignObject x="2.238" y="8.019" width="40" height="71">
          <div id="screen">
             I'm a very long text. Why am I so big?
          </div>
       </foreignObject>
    </svg>
</body>

CSS

html, body{
  width: 100%;
  height: 100%;
}

#screen{
  background: green;
  overflow: scroll;
  width: 100%;
  height: 100%;
  font-size: 10px;
}

JSFiddle

My problem is that all elements inside the screen-div are way larger than expected. e.g. see the scrollbar or the size of the text.

I assume the content of the foreignObject is scaled by the same factor as the svg. Is there a way to avoid this? Could I normalize the div inside the foreignObject to be not scaled or zoomed?


svg is "Smartphone" by Martin Jordan from the Noun Project

like image 594
Sven Avatar asked Jul 11 '17 20:07

Sven


1 Answers

The only solution I can think of is to use JavaScript to dynamically size and counter-scale the foreignObject based on the viewBox dimensions versus the offsetWidth and offsetHeight of the outer <svg>.

For example, in this demo I happen to have hard-coded the size of the SVG to be four times as large as the viewBox dimensions. To counteract this, I made the foreignObject four times as large, but then scaled it down to one-quarter the size:

<foreignObject width="164" height="288" transform="translate(2,8) scale(0.25,0.25)">

https://jsfiddle.net/7ttps7a7/3/

A good generic solution would be to put an extra attribute in a custom namespace on any foreignObject, and then load a JavaScript library that finds such elements and dynamically adjusts them (and keeps them adjusted as the size of the SVG changes).

Note that comparing offsetWidth (and height) vs viewBox width (and height) needs to consider the value of the preserveAspectRatio attribute on the SVG to be precise.

Edit: I've created a small library that does this

  • Library: http://phrogz.net/SVG/fixed-size-foreignObject.js
  • Demo: http://phrogz.net/SVG/fixed-size-foreignObject.html

To use it:

  1. Include the library in your HTML or SVG page.
    Please download it and host it on your own site; I am not a CDN.
  2. Be sure to use x and y attributes to place your <foreignObject>, and width and height values to size it.
  3. Use one of the following:

    fixedSizeForeignObject( someForeignObjectElement );
    fixedSizeForeignObjects( arrayOfForeignObjectElements );
    

How it works:

  1. When a foreignObject is added to the list of elements to keep resized, its original x, y, width, height values are recorded. The SVG element that owns the foreignObject is added to list of SVG elements to watch.
  2. When the window resizes, code is triggered that (a) calculates the scale of each SVG (actual pixels versus viewBox size) and then (b) for each foreignObject registered it adjusts the width/height to be correct, and then scales the element down to fit in the original location.

I'll copy/paste the library here in the (unlikely) case that my site is down:

(function(win){
  const svgs, els=[];

  win.fixedSizeForeignObjects = function fixedSizeForeignObjects(els) {
    els.forEach( fixedSizeForeignObject );
  }

  win.fixedSizeForeignObject = function fixedSizeForeignObject(el) {
    if (!svgs) { svgs = []; win.addEventListener('resize',resizeSVGs,false) }
    let svg=el.ownerSVGElement, found=false;
    for (let i=svgs.length;i--;) if (svgs[i]===svg) found=true;
    if (!found) svgs.push(svg);
    let info = {
      el:el, svg:svg,
      w:el.getAttribute('width')*1, h:el.getAttribute('height')*1,
      x:el.getAttribute('x')*1, y:el.getAttribute('y')*1
    };
    els.push(info);
    el.removeAttribute('x');
    el.removeAttribute('y');
    calculateSVGScale(svg);
    fixScale(info);
  }

  function resizeSVGs(evt) {
    svgs.forEach(calculateSVGScale);
    els.forEach(fixScale);
  }

  function calculateSVGScale(svg) {
    let w1=svg.viewBox.animVal.width, h1=svg.viewBox.animVal.height;
    if (!w1 && !h1) svg.scaleRatios = [1,1]; // No viewBox
    else {
      let info = win.getComputedStyle(svg);
      let w2=parseFloat(info.width), h2=parseFloat(info.height);
      let par=svg.preserveAspectRatio.animVal;
      if (par.align===SVGPreserveAspectRatio.SVG_PRESERVEASPECTRATIO_NONE) {
        svg.scaleRatios = [w2/w1, h2/h1];
      } else {
        let meet = par.meetOrSlice === SVGPreserveAspectRatio.SVG_MEETORSLICE_MEET;
        let ratio = (w1/h1 > w2/h2) != meet ? h2/h1 : w2/w1;
        svg.scaleRatios = [ratio, ratio];
      }
    }
  }

  function fixScale(info) {
    let s = info.svg.scaleRatios;
    info.el.setAttribute('width', info.w*s[0]);
    info.el.setAttribute('height',info.h*s[1]);
    info.el.setAttribute('transform','translate('+info.x+','+info.y+') scale('+1/s[0]+','+1/s[1]+')');
  }
})(window);
like image 66
Phrogz Avatar answered Nov 10 '22 22:11

Phrogz