浏览器缩放破坏了 svg 布局
Browser zoom breaks svg layout
(参考代码片段) 我试图从 foreignObject 内部的一个元素 #item-A > div
的底部绘制一条路径#item-A
到 foreignObject 内部的另一个元素 #item-B > div
的顶部 #item-B
。 div(s) 高度可变。
对于不同的浏览器缩放级别,路径 M ${item_A_div_Bottom - svgOffset.y}
值给出不同的结果。这破坏了布局。
我查阅了各种文档以尝试理解这种行为的原因:
- https://www.w3.org/TR/SVG/coords.html
- https://www.w3.org/TR/css-transforms-1
- https://www.w3.org/TR/SVG2/embedded.html
window.onload = function() {
var svgEl = document.querySelector('svg'),
itemA = document.querySelector('#item-A'),
itemB = document.querySelector('#item-B'),
path = document.createElementNS("http://www.w3.org/2000/svg", 'path');
const svgOffset = svgEl.getBoundingClientRect();
const {
a
} = svgEl.getScreenCTM();
const {
x: item_A_BBoxX,
} = itemA.getBBox();
const {
bottom: item_A_div_Bottom
} = document.querySelector(`#item-A > div`).getBoundingClientRect();
const {
y: item_B_BBoxY,
} = document.querySelector(`#item-B`).getBBox();
const path_d = `
M ${item_A_BBoxX},${(item_A_div_Bottom - svgOffset.y) * (1 / a)}
V ${item_B_BBoxY}
`;
path.setAttribute('d', path_d);
path.setAttribute('stroke', 'red');
path.setAttribute('stroke-width', '2px');
svgEl.appendChild(path);
}
#header {
width: 100%;
height: 30px;
background-color: purple;
}
#svg-wrapper {
display: flex;
justify-content: center;
height: 600px;
width: 100%;
background-color: grey;
}
svg {
width: 80%;
height: 100%;
}
foreignObject {
background-color: black;
}
<div id="header">Header</div>
<div id="svg-wrapper">
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<style>
div {
color: white;
font: 18px serif;
height: 40px;
overflow: auto;
background-color: black;
}
</style>
<foreignObject id="item-A" x="20" y="20" width="60" height="30">
<div xmlns="http://www.w3.org/1999/xhtml">
Item-A
</div>
</foreignObject>
<foreignObject id="item-B" x="20" y="120" width="60" height="30">
<div xmlns="http://www.w3.org/1999/xhtml">
Item-B
</div>
</foreignObject>
</svg>
</div>
如果放大或缩小然后重新运行代码段,路径位置和长度会发生变化。
如何防止布局因不同的缩放级别而中断?
element.getBoundingClientRect()
返回的属性值随浏览器缩放而变化。如 MDN web docs
中给出
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport. ....
The amount of scrolling that has been done of the viewport area (or any other scrollable element) is taken into account when computing the bounding rectangle. This means that the rectangle's boundary edges (top, right, bottom, left) change their values every time the scrolling position changes (because their values are relative to the viewport and not absolute).
不使用 element.getBoundingClientRect()
,而是使用 element.clientHeight
和 object.getBBox()
属性来计算所需的位置。
window.onload = function() {
var svgEl = document.querySelector('svg'),
itemA = document.querySelector('#item-A'),
itemB = document.querySelector('#item-B'),
path = document.createElementNS("http://www.w3.org/2000/svg", 'path');
const {
clientTop: item_A_ClientTop,
clientHeight: item_A_ClientHeight,
} = itemA;
const {
x: item_A_BBoxX,
y: item_A_BBoxY
} = itemA.getBBox();
const {
clientHeight: item_A_div_ClientHeight
} = document.querySelector(`#item-A > div`);
const {
y: item_B_BBoxY,
} = document.querySelector(`#item-B`).getBBox();
const path_d = `
M ${item_A_BBoxX},${item_A_BBoxY + item_A_div_ClientHeight}
V ${item_B_BBoxY}
`;
path.setAttribute('d', path_d);
path.setAttribute('stroke', 'red');
path.setAttribute('stroke-width', '2px');
svgEl.appendChild(path);
}
#header {
width: 100%;
height: 30px;
background-color: purple;
}
#svg-wrapper {
display: flex;
justify-content: center;
height: 600px;
width: 100%;
background-color: grey;
}
svg {
width: 80%;
height: 100%;
}
foreignObject {
background-color: black;
}
<div id="header">Header</div>
<div id="svg-wrapper">
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<style>
div {
color: white;
font: 18px serif;
height: 40px;
overflow: auto;
background-color: black;
}
</style>
<foreignObject id="item-A" x="20" y="20" width="60" height="30">
<div xmlns="http://www.w3.org/1999/xhtml">
Item-A
</div>
</foreignObject>
<foreignObject id="item-B" x="20" y="120" width="60" height="30">
<div xmlns="http://www.w3.org/1999/xhtml">
Item-B
</div>
</foreignObject>
</svg>
</div>
(参考代码片段) 我试图从 foreignObject 内部的一个元素 #item-A > div
的底部绘制一条路径#item-A
到 foreignObject 内部的另一个元素 #item-B > div
的顶部 #item-B
。 div(s) 高度可变。
对于不同的浏览器缩放级别,路径 M ${item_A_div_Bottom - svgOffset.y}
值给出不同的结果。这破坏了布局。
我查阅了各种文档以尝试理解这种行为的原因:
- https://www.w3.org/TR/SVG/coords.html
- https://www.w3.org/TR/css-transforms-1
- https://www.w3.org/TR/SVG2/embedded.html
window.onload = function() {
var svgEl = document.querySelector('svg'),
itemA = document.querySelector('#item-A'),
itemB = document.querySelector('#item-B'),
path = document.createElementNS("http://www.w3.org/2000/svg", 'path');
const svgOffset = svgEl.getBoundingClientRect();
const {
a
} = svgEl.getScreenCTM();
const {
x: item_A_BBoxX,
} = itemA.getBBox();
const {
bottom: item_A_div_Bottom
} = document.querySelector(`#item-A > div`).getBoundingClientRect();
const {
y: item_B_BBoxY,
} = document.querySelector(`#item-B`).getBBox();
const path_d = `
M ${item_A_BBoxX},${(item_A_div_Bottom - svgOffset.y) * (1 / a)}
V ${item_B_BBoxY}
`;
path.setAttribute('d', path_d);
path.setAttribute('stroke', 'red');
path.setAttribute('stroke-width', '2px');
svgEl.appendChild(path);
}
#header {
width: 100%;
height: 30px;
background-color: purple;
}
#svg-wrapper {
display: flex;
justify-content: center;
height: 600px;
width: 100%;
background-color: grey;
}
svg {
width: 80%;
height: 100%;
}
foreignObject {
background-color: black;
}
<div id="header">Header</div>
<div id="svg-wrapper">
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<style>
div {
color: white;
font: 18px serif;
height: 40px;
overflow: auto;
background-color: black;
}
</style>
<foreignObject id="item-A" x="20" y="20" width="60" height="30">
<div xmlns="http://www.w3.org/1999/xhtml">
Item-A
</div>
</foreignObject>
<foreignObject id="item-B" x="20" y="120" width="60" height="30">
<div xmlns="http://www.w3.org/1999/xhtml">
Item-B
</div>
</foreignObject>
</svg>
</div>
如果放大或缩小然后重新运行代码段,路径位置和长度会发生变化。
如何防止布局因不同的缩放级别而中断?
element.getBoundingClientRect()
返回的属性值随浏览器缩放而变化。如 MDN web docs
The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport. ....
The amount of scrolling that has been done of the viewport area (or any other scrollable element) is taken into account when computing the bounding rectangle. This means that the rectangle's boundary edges (top, right, bottom, left) change their values every time the scrolling position changes (because their values are relative to the viewport and not absolute).
不使用 element.getBoundingClientRect()
,而是使用 element.clientHeight
和 object.getBBox()
属性来计算所需的位置。
window.onload = function() {
var svgEl = document.querySelector('svg'),
itemA = document.querySelector('#item-A'),
itemB = document.querySelector('#item-B'),
path = document.createElementNS("http://www.w3.org/2000/svg", 'path');
const {
clientTop: item_A_ClientTop,
clientHeight: item_A_ClientHeight,
} = itemA;
const {
x: item_A_BBoxX,
y: item_A_BBoxY
} = itemA.getBBox();
const {
clientHeight: item_A_div_ClientHeight
} = document.querySelector(`#item-A > div`);
const {
y: item_B_BBoxY,
} = document.querySelector(`#item-B`).getBBox();
const path_d = `
M ${item_A_BBoxX},${item_A_BBoxY + item_A_div_ClientHeight}
V ${item_B_BBoxY}
`;
path.setAttribute('d', path_d);
path.setAttribute('stroke', 'red');
path.setAttribute('stroke-width', '2px');
svgEl.appendChild(path);
}
#header {
width: 100%;
height: 30px;
background-color: purple;
}
#svg-wrapper {
display: flex;
justify-content: center;
height: 600px;
width: 100%;
background-color: grey;
}
svg {
width: 80%;
height: 100%;
}
foreignObject {
background-color: black;
}
<div id="header">Header</div>
<div id="svg-wrapper">
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
<style>
div {
color: white;
font: 18px serif;
height: 40px;
overflow: auto;
background-color: black;
}
</style>
<foreignObject id="item-A" x="20" y="20" width="60" height="30">
<div xmlns="http://www.w3.org/1999/xhtml">
Item-A
</div>
</foreignObject>
<foreignObject id="item-B" x="20" y="120" width="60" height="30">
<div xmlns="http://www.w3.org/1999/xhtml">
Item-B
</div>
</foreignObject>
</svg>
</div>