首页 > 解决方案 > 如何将一堆 svg 元素缩放到视口的 100%

问题描述


同时,我阅读了很多关于 viewbox、viewports 和 svg scaling 的网站。
我有一个 svg 框,大小可变,具体取决于浏览器窗口。
在其中,我显示了一些元素,如矩形、线条文本等。
这些元素,或者更好的是,使用鼠标滚轮可以“缩放”视图。

<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>

脚本:

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));
})

在这里,它在codepen上

现在,我想添加一个“fullZoom”功能。
为此,我认为我需要获取所有嵌套元素的外部尺寸。所以也许就像所有元素的边界框。在这里,你进入了游戏。与此同时,我完全糊涂了。

谢谢你,
卡斯滕

标签: javascriptsvg

解决方案


我不知道您是否意识到,但您的 SVG 也在改变大小(特别是滚动时的宽度)?(我在 中添加了蓝色边框,在 中添加了<div/>黑色边框<svg/>

负载:

在此处输入图像描述

滚动时:

在此处输入图像描述

这是因为您只固定文档加载的高度。如果你也 ifx 宽度,这个问题就会消失: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));
})

然后我们可以滚动而不“增加”我们的宽度。

双击时您想要的结果是这样的:

即尽可能靠近svg边缘的任何元素?

在此处输入图像描述

更新

如果您只想返回原始缩放,假设您在视口等中首先设置(硬编码)原始缩放等创建了 SVG?那么您可以简单地保存这个原始值并在用户双击时将其重置?

演示: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));
})

更新

我还尝试了另一个想法(以获得仍然在屏幕上显示所有元素的最大缩放:https ://codepen.io/Alexander9111/pen/NWqWEOw ,我相信我已经做到了。

我在屏幕上添加了一个红色圆圈,以表明我们将视口最大化到了可能的最大值。IE:

在此处输入图像描述

或者

在此处输入图像描述

无论我们在哪里移动元素,双击事件都会触发循环遍历元素并保存每个元素具有的边界框的最大底部和右侧点的函数,然后将缩放端口更改为该值。

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;
}

最重要的几行:

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);
}

遍历 svg 中的所有子元素并调用函数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;
}

将边界客户端矩形从页面坐标转换为 svg 坐标。

transform="translate(200, 250)"如果元素有一个变换(就像组元素经常做的那样——比如, 底部相同。将圆圈移动到该点以突出显示。

更新

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

您还可以添加左上角最小点(此处以绿色显示):

在此处输入图像描述

这来自以下函数,提出第二点 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;
}

推荐阅读