简体   繁体   中英

How to scale a bunch of svg-elements to 100% of viewport


meanwhile I've read a lot of sites about viewbox, viewports and svg scaling.
I have an svg-box, with variable size, depending on the browser-window.
Inside this, I display some elements like rects, lines texts etc.
These elements, or better the view is "zoomable" by using the mousewheel.

<h1>Editor:</h1>
<div id="content">
<svg id="drawing"
     title="Layouteditor"
     viewBox="0 0 500 300"
     xmlns="http://www.w3.org/2000/svg"
     version="2.0"
     preserveAspectRatio="xMidYMid meet">
<g class="draggable preview" id="437" pointer-events="fill" transform="translate(150 50)">
  <rect fill="none" x="80" y="100" width="41" height="41"></rect>
  <line class="CDC300" fill="none" x1="140" y1="120" x2="121" y2="120" />
  <text class="CDC300" font-family="sans-serif" font-size="30px" fill="#000000" stroke-width="0.3" text-anchor="middle" x="100" y="131">T</text>
  <text class="CDC400" id="20010" text-anchor="end" x="75" y="125">=HTC1+HSP1</text>
  <circle class="insertpoint" cx="140" cy="120" r="3" />
  <circle class="pin" cx="140" cy="120" r="2" />
</g>
<g id="a123" style="touch-action: none; -webkit-tap-highlight-color: rgba(0, 0, 0, 0);">
  <line class="hotwater graphic" id="g123" x1="100" y1="120" x2="200" y2="120"></line>
</g> 
</svg>
</div>

the scripts:

var svgDrawing = document.getElementById('drawing');
var viewbox = svgDrawing.viewBox.baseVal;

function zoomInOut(evt) {
    if (evt.deltaY > 1) {
        viewbox.width += 10;
        viewbox.height += 10;
    } else {
        viewbox.width -= 10;
        viewbox.height -= 10;
    }
}

function zoomFull(evt){
    console.log("Zoom 100%");
  //here, the zoom should be set to 100% of the drawing
  //###################################################
  //###################################################
  //###################################################
}
$(document).ready(function () {
  console.log("document ready");
    svgDrawing.setAttribute("height", window.innerHeight - 200);
    svgDrawing.addEventListener('wheel', e => zoomInOut(e));
    svgDrawing.addEventListener('dblclick', e=>zoomFull(e));
})

Here, it is on codepen

now, I'd like to add a "fullZoom"-function.
For this, I need to get the outer dimensions of all nested elements, I think. So perhaps like a boundingBox over all elements. And here, you come into the game. I am totally confused meanwhile.

Thank you,
Carsten

I don't know if you realize but your SVG is also changing size (specifically its width as you scroll)? (I added a blue border to the <div/> and black border to the <svg/> )

On load:

在此处输入图片说明

On scroll-in:

在此处输入图片说明

This is because you only fix the height on document load. If you ifx the width too, this problem goes away: https://codepen.io/Alexander9111/pen/poJzWEN

$(document).ready(function () {
    console.log("document ready");
    svgDrawing.setAttribute("height", window.innerHeight - 200);
    svgDrawing.setAttribute("width", 1.5 * (window.innerHeight - 200));
    svgDrawing.addEventListener('wheel', e => zoomInOut(e));
    svgDrawing.addEventListener('dblclick', e=>zoomFull(e));
})

Then we can scroll without "growing" our width.

Is your desired outcome on double-click something like this:

ie any elements within as close to the edge of the svg as allows?

在此处输入图片说明

UPDATE

If you want to just return to original zoom, assuming you created the SVG with the original zoom first set up (hard-coded) etc. in the viewport etc.? Then you can simply save this original value and reset it when the user double clicks?

Demo: https://codepen.io/Alexander9111/pen/poJzWEN?editors=1010

JS:

const svgDrawing = document.getElementById('drawing');
// console.log(svgDrawing.currentTranslate);
let viewbox = svgDrawing.viewBox.baseVal;
const original_w = viewbox.width;
const original_h = viewbox.height;
const w_h_ratio = 2;
const g = document.getElementById('437'); 
console.log(g.transform.baseVal.consolidate().matrix);
var point = svgDrawing.createSVGPoint();

function zoomEvent(evt) {
  evt.preventDefault();
  if (evt.deltaY > 1) {
    zoomInOut(-1, 10);
  } else {
    zoomInOut(+1, 10);
  }
}

function zoomInOut(direction, stepSize) {
  if (direction > 0) {
    viewbox.width -= w_h_ratio * stepSize;
    viewbox.height -= stepSize;
  } else {
    viewbox.width += w_h_ratio * stepSize;
    viewbox.height += stepSize;
  }
}

function zoomFull(evt){
  evt.preventDefault();
  viewbox.width = original_w;
  viewbox.height = original_h;
  console.log("Zoom 100%");    
}

function elementIsInside(el, box){
  var result = false;
  el_rect = el.getBoundingClientRect();
  point.x = el_rect.x;
  point.y = el_rect.y;
  point.width = el_rect.width;
  point.height = el_rect.height; 
  var invertedSVGMatrix = el.getScreenCTM().inverse();
  var p = point.matrixTransform(invertedSVGMatrix);

  box_rect = box.getBoundingClientRect();
  if (el_rect.left >= box_rect.left &&
      el_rect.right <= box_rect.right &&
      el_rect.bottom <= box_rect.bottom &&
      el_rect.top >= box_rect.top){
    result = true;
  } else {
    result = false;
  }
  // console.log("result_" + el.tagName, result)
  return result;
}

$(document).ready(function () {
  console.log("document ready");
  svgDrawing.setAttribute("height", window.innerHeight - 200);
  svgDrawing.setAttribute("width", w_h_ratio * (window.innerHeight - 200));
  svgDrawing.addEventListener('wheel', e => zoomEvent(e));
  svgDrawing.addEventListener('dblclick', e=>zoomFull(e));
})

UPDATE

I also tried to do the other idea (to get the max zoom that still shows all elements on-screen: https://codepen.io/Alexander9111/pen/NWqWEOw and I believe I have done it.

I added a red circle on the screen to show that we maximized the viewport to the maximum possible. ie:

在此处输入图片说明

OR

在此处输入图片说明

Wherever we move the elements around, the double-click event will fire the function that loops through the elements and saves the largest bottom and right points of the boundary boxes each element has and then alters the zoom port to that.

JS:

const svgDrawing = document.getElementById('drawing');
// console.log(svgDrawing.currentTranslate);
let viewbox = svgDrawing.viewBox.baseVal;
let original_w = viewbox.width;
let original_h = viewbox.height;
let w_h_ratio = 2;
const g = document.getElementById('437'); 
console.log(g.transform.baseVal.consolidate().matrix);
// const extra = document.getElementById('example'); 
// console.log(extra.transform.baseVal.consolidate().matrix);
const b_box = document.getElementById('bounding_box');
const b_box_max = {right: 0, bottom: 0};
var point = svgDrawing.createSVGPoint();

function zoomEvent(evt) {
  evt.preventDefault();
  if (evt.deltaY > 1) {
    zoomInOut(-1, 10);
  } else {
    zoomInOut(+1, 10);
  }
}

function zoomInOut(direction, stepSize) {
  if (direction > 0) {
    viewbox.width -= w_h_ratio * stepSize;
    viewbox.height -= stepSize;
  } else {
    viewbox.width += w_h_ratio * stepSize;
    viewbox.height += stepSize;
  }
}

function zoomToSize(width, height) {
  viewbox.width = width;
  viewbox.height = height;
}

function zoomFull(evt){
  evt.preventDefault();
  viewbox.width = original_w;
  viewbox.height = original_h;
  console.log("Zoom 100%");
  const elements = [...svgDrawing.childNodes];
  console.log("elements", elements);
  var inside = true;
  for (let el of elements){            
    if (el.nodeName == "#text"){
      //this is not a real node
    } else if (elementIsInside(el, svgDrawing)){
      inside = true;
    } else {
      inside = false;
    }
  }
  // var dir = inside ? +1 : -1;
  // zoomInOut(dir, (100/(10**i)));
  zoomToSize(b_box_max.right,b_box_max.bottom);
}

function elementIsInside(el, box){
  var result = false;
  el_rect = el.getBoundingClientRect();
  point.x = el_rect.right;
  point.y = el_rect.bottom;
  var invertedSVGMatrix = el.getScreenCTM().inverse();
  var p = point.matrixTransform(invertedSVGMatrix);
  let trans = el.transform.baseVal;
  if (trans.length > 0){
    const matrix = trans.consolidate().matrix;
    p.x += matrix.e;
    p.y += matrix.f;
  }
  if (p.x > b_box_max.right){
    b_box.setAttribute('cx', p.x);
    b_box_max.right = p.x;
    b_box.setAttribute('cx', p.x);
  }
  if (p.y > b_box_max.bottom){
    b_box.setAttribute('cy', p.y);
    b_box_max.bottom = p.y;
    b_box.setAttribute('cy', p.y);
  }
  b_box.setAttribute('r', 5);  

  console.log(p.x, p.y);
  console.log(b_box_max.right,b_box_max.bottom);
  box_rect = box.getBoundingClientRect();
  if (el_rect.left >= box_rect.left &&
      el_rect.right <= box_rect.right &&
      el_rect.bottom <= box_rect.bottom &&
      el_rect.top >= box_rect.top){
    result = true;
  } else {
    result = false;
  }
  // console.log("result_" + el.tagName, result)
  return result;
}

$(document).ready(function () {
  console.log("document ready");
  resizeSVG();
  svgDrawing.addEventListener('wheel', e => zoomEvent(e));
  svgDrawing.addEventListener('dblclick', e=>zoomFull(e));
})

$(window).resize(function() {
  console.log($(window).width(), $(window).height());
  // console.log(window.innerWidth, window.innerHeight);
   resizeSVG();
});

function resizeSVG(){
  if ((window.innerWidth - 40) / w_h_ratio > (window.innerHeight - 120)){
    svgDrawing.setAttribute("height", window.innerHeight - 120);
    svgDrawing.setAttribute("width", w_h_ratio * (window.innerHeight - 120));
  } else{
    svgDrawing.setAttribute("height", (window.innerWidth - 40) / w_h_ratio);
    svgDrawing.setAttribute("width", (window.innerWidth - 40));
  }
  viewbox = svgDrawing.viewBox.baseVal;
  original_w = viewbox.width;
  original_h = viewbox.height;
  w_h_ratio = 2;
}

Most important lines:

function zoomFull(evt){
  evt.preventDefault();
  viewbox.width = original_w;
  viewbox.height = original_h;
  console.log("Zoom 100%");
  const elements = [...svgDrawing.childNodes];
  console.log("elements", elements);
  var inside = true;
  for (let el of elements){            
    if (el.nodeName == "#text"){
      //this is not a real node
    } else if (elementIsInside(el, svgDrawing)){
      inside = true;
    } else {
      inside = false;
    }
  }
  zoomToSize(b_box_max.right,b_box_max.bottom);
}

Loop through all children elements in the svg and call the function elementIsInside() :

function elementIsInside(el, box){
  var result = false;
  el_rect = el.getBoundingClientRect();
  point.x = el_rect.right;
  point.y = el_rect.bottom;
  var invertedSVGMatrix = el.getScreenCTM().inverse();
  var p = point.matrixTransform(invertedSVGMatrix);
  let trans = el.transform.baseVal;
  if (trans.length > 0){
    const matrix = trans.consolidate().matrix;
    p.x += matrix.e;
    p.y += matrix.f;
  }
  if (p.x > b_box_max.right){
    b_box.setAttribute('cx', p.x);
    b_box_max.right = p.x;
    b_box.setAttribute('cx', p.x);
  }
  if (p.y > b_box_max.bottom){
    b_box.setAttribute('cy', p.y);
    b_box_max.bottom = p.y;
    b_box.setAttribute('cy', p.y);
  }
  b_box.setAttribute('r', 5);  

  console.log(p.x, p.y);
  console.log(b_box_max.right,b_box_max.bottom);
  box_rect = box.getBoundingClientRect();
  if (el_rect.left >= box_rect.left &&
      el_rect.right <= box_rect.right &&
      el_rect.bottom <= box_rect.bottom &&
      el_rect.top >= box_rect.top){
    result = true;
  } else {
    result = false;
  }
  // console.log("result_" + el.tagName, result)
  return result;
}

Translate bounding client rect from page coordinates to svg coordinates.

If the element has a transformation (like the group elements often do - such as transform="translate(200, 250)" , then we also add these (matrix.e and matrix.f) onto our points. If the right is larger than the currently saved maximum right, then replace it, same for the bottom. Move the circle to this point to highlight.

UPDATE

https://codepen.io/Alexander9111/pen/NWqWEOw

You can also add a top left minimum point (shown here in green):

在此处输入图片说明

This comes from the following function, making a second point, p2:

function elementIsInside(el, box){
  var result = false;
  el_rect = el.getBoundingClientRect();
  point.x = el_rect.right;
  point.y = el_rect.bottom;
  var invertedSVGMatrix = el.getScreenCTM().inverse();
  var p = point.matrixTransform(invertedSVGMatrix);
  point.x = el_rect.left;
  point.y = el_rect.top;
  var p2 = point.matrixTransform(invertedSVGMatrix);
  let trans = el.transform.baseVal;
  if (trans.length > 0){
    const matrix = trans.consolidate().matrix;
    p.x += matrix.e;
    p.y += matrix.f;
    p2.x += matrix.e;
    p2.y += matrix.f;
  }
  if (p.x > b_box_max.right){
    b_box_br.setAttribute('cx', p.x);
    b_box_max.right = p.x;
  }
  if (p.y > b_box_max.bottom){
    b_box_br.setAttribute('cy', p.y);
    b_box_max.bottom = p.y;
  }
  b_box_br.setAttribute('r', 5); 

  if (p2.x < b_box_max.left){
    b_box_tl.setAttribute('cx', p2.x);
    b_box_max.left = p2.x;
  }
  if (p2.y < b_box_max.top){
    b_box_tl.setAttribute('cy', p2.y);
    b_box_max.top = p2.y;
  }
  b_box_tl.setAttribute('r', 5); 

  b_box_max.width = b_box_max.right - b_box_max.left;
  b_box_max.height = b_box_max.bottom - b_box_max.top;

  console.log(p2.x, p2.y, p.x, p.y);
  console.log(b_box_max.right,b_box_max.bottom);
  box_rect = box.getBoundingClientRect();
  if (el_rect.left >= box_rect.left &&
      el_rect.right <= box_rect.right &&
      el_rect.bottom <= box_rect.bottom &&
      el_rect.top >= box_rect.top){
    result = true;
  } else {
    result = false;
  }
  // console.log("result_" + el.tagName, result)
  return result;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM