
使用SVG相对简单-在您想要混合DOM和矢量交互之前。
SVG在viewBox属性中定义了自己的坐标系。例如:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 600">
从处开始设置宽度为800单位,高度为600单位0,0。这些单位是用于绘图目的的任意度量,并且可以使用单位的分数。如果将此SVG放置在800按600像素区域中,则每个SVG单元应直接映射到屏幕像素。
但是,矢量图像可以缩放到任意大小,尤其是在自适应设计中。你可能SVG缩小到400通过300,甚至延伸面目全非在10通过1000空间。如果要根据光标位置放置其他元素,则向该SVG添加更多元素将变得更加困难。
注意:有关SVG坐标的更多信息,请参见Sara Soueidan的视口,viewBox和preserveAspectRatio文章。
避免协调翻译
您也许可以避免完全在坐标系之间转换。
嵌入HTML页面(而不是图像或CSS背景)中的SVG成为DOM的一部分,并且可以通过与其他元素类似的方式进行操作。例如,以一个圆形的基本SVG为例:
<svg id="mysvg" xmlns="https://www.w3.org/2000/svg" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid meet"> <circle id="mycircle" cx="400" cy="300" r="50" /> <svg>
您可以对此应用CSS效果:
circle {
stroke-width: 5;
stroke: #f00;
fill: #ff0;}
circle:hover {
stroke: #090;
fill: #fff;}您还可以附加事件处理程序以修改属性:
const mycircle = document.getElementById('mycircle');
mycircle.addEventListener('click', (e) => {
console.log('circle clicked - enlarging');
mycircle.setAttribute('r', 60);
});下面的示例将30个随机圆添加到SVG图像,在CSS中应用悬停效果,并在单击圆时使用JavaScript将半径增加10个单位。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>将30个随机圆添加到SVG图像(Web前端之家https://jiangweishan.com/)</title>
<style>
*, *:before, *:after {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
height: 100%;
}
body {
min-height: 100%;
font-family: Lato, sans-serif;
font-size: 100%;
padding: 0;
margin: 10px;
color: #444;
background-color: #fff;
overflow: hidden;
}
svg {
background: radial-gradient(ellipse at center, #fefefe 0%, #cbeeff 100%);
}
circle {
stroke-width: 5;
stroke: #f00;
fill: #ff0;
}
circle:hover {
stroke: #090;
fill: #fff;
}
</style>
</head>
<body>
<svg id="mysvg" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1600 800" preserveAspectRatio="xMidYMid meet"></svg>
<script>
var
svg = document.getElementById('mysvg'),
NS = svg.getAttribute('xmlns');
// add random circles
var c, i;
for (i = 0; i < 30; i++) {
c = document.createElementNS(NS, 'circle');
c.setAttributeNS(null, 'cx', Math.round(Math.random() * 1600));
c.setAttributeNS(null, 'cy', Math.round(Math.random() * 800));
c.setAttributeNS(null, 'r', 20 + Math.round(Math.random() * 30));
svg.appendChild(c);
}
// click a circle
svg.addEventListener('click', function(e) {
var t = e.target;
if (t.nodeName != 'circle') return;
t.setAttributeNS(null, 'r',
parseFloat(t.getAttributeNS(null, 'r')) + 10
);
console.log(t.getBoundingClientRect());
}, false);
</script>
</body>
</html>SVG到DOM坐标转换
可能有必要在SVG元素的顶部覆盖DOM元素-例如,在世界地图上显示的活动国家/地区上的菜单或信息框。假设SVG嵌入到HTML中,则元素成为DOM的一部分,因此getBoundingClientRect()方法可用于提取位置和尺寸。(在上面的示例中打开控制台,以显示半径增加后单击的圆的新属性。)
Element.getBoundingClientRect()在所有浏览器中均受支持,并返回一个DOMrect具有以下像素尺寸属性的对象:
.x和.left:元素左侧相对于视口原点的x坐标.right:相对于视口原点的元素右侧的x坐标.y和.top:元素顶侧相对于视口原点的y坐标.bottom:元素相对于视口原点的y坐标.width:元素的宽度(在IE8及以下版本中不支持,但与.rightminus相同.left).height:元素的高度(在IE8及以下版本中不支持,但与.bottom负号相同.top)
所有坐标都相对于浏览器视口,因此将在滚动页面时发生变化。可以通过添加window.scrollX到.left和window.scrollY来计算页面上的绝对位置.top。
DOM到SVG坐标转换
这更具挑战性。假设您要viewBox在单击事件发生的位置上放置一个新的SVG元素。事件处理程序对象提供DOM.clientX和.clientY像素坐标,但是必须将其转换为SVG单位。
很容易想到您可以通过应用乘法因子来计算SVG点的坐标。例如,如果将1000个单位宽度的SVG放置在500px宽度的容器中,则可以将任何DOMx坐标乘以2以获得SVG位置。它行不通!…
不能保证SVG完全适合您的容器。
如果元素尺寸发生变化(可能是响应用户调整浏览器大小的结果),则必须重新计算宽度和高度因子。
SVG或一个或多个元素可以在2D或3D空间中转换。
即使您克服了这些障碍,它也永远不会像您预期的那样有效,并且经常会有误差。
幸运的是,SVG提供了自己的矩阵分解机制来转换坐标。第一步是使用createSVGPoint()方法在SVG上创建一个点,并传入屏幕/事件x和y坐标:
const
svg = document.getElementById('mysvg'),
pt = svg.createSVGPoint();
pt.x = 100;
pt.y = 200;然后,您可以应用从SVG.getScreenCTM()方法的逆函数创建的矩阵变换,该变换将SVG单位映射到屏幕坐标:
const svgP = pt.matrixTransform( svg.getScreenCTM().inverse() );
svgP现在具有.x和.y属性,可提供SVG上的坐标viewBox。
以下代码在SVG画布上单击的点处放置了一个圆圈:
// get SVG element and namespace
const
svg = document.getElementById('mysvg'),
NS = svg.getAttribute('xmlns');
// click event
svg.addEventListener('click', (e) => {
const pt = svg.createSVGPoint();
// pass event coordinates
pt.x = e.clientX;
pt.y = e.clientY;
// transform to SVG coordinates
const svgP = pt.matrixTransform( svg.getScreenCTM().inverse() );
// add new SVG element
const circle = document.createElementNS(NS, 'circle');
circle.setAttribute('cx', svgP.x);
circle.setAttribute('cy', svgP.y);
circle.setAttribute('r', 10);
svg.appendChild(circle);
});注意:这些createElementNS()方法与标准DOMcreateElement()方法相同,除了它指定XML名称空间URI之外。换句话说,它作用于SVG文档而不是HTML。
转换为已转换的SVG坐标
还有进一步的复杂化。可以通过平移,缩放,旋转和/或倾斜来变换SVG或单个元素,这会影响所得的SVG坐标。例如,下<g>一层比标准SVG单位大4倍,因此坐标将是包含SVG的坐标的四分之一:
<g id="transformed" transform="scale(4)"> <rect x="50" y="50" width="100" height="100" /> </g>
将得到的矩形显示为具有400在位置单元的宽度和高度200, 200。
幸运的是,该.getScreenCTM()方法可以应用于任何元素以及SVG本身。结果矩阵考虑了所有转换,因此您可以创建一个简单的svgPoint()转换函数:
const
svg = document.getElementById('mysvg'),
transformed = svg.getElementById('transformed');
console.log( svgPoint(svg, 10, 10) ); // returns x, y
console.log( svgPoint(transformed, 10, 10) ); // = x/4, y/4
// translate page to SVG coordinate
function svgPoint(element, x, y) {
const pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
return pt.matrixTransform( element.getScreenCTM().inverse() );
}以下演示适用于所有现代浏览器(如果将JavaScript转换为ES5,则将在IE11中使用!)。轻击/单击SVG时,将在光标处添加一个圆圈。
<g>单击转换的区域时也会发生这种情况,但是将该元素而不是SVG本身传递给svgPoint()函数以确保计算出正确的坐标:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>轻击/单击SVG时,将在光标处添加一个圆圈(Web前端之家https://jiangweishan.com/)</title>
<style>
*, *:before, *:after {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
height: 100%;
}
body {
height: 100%;
font-family: Lato, helvetica, sans-serif;
font-size: 100%;
padding: 0;
margin: 0;
color: #333;
background-color: #fff;
overflow: hidden;
}
#mysvg {
display: block;
width: auto;
height: 100%;
margin: 0;
background: radial-gradient(ellipse at center, #fefefe 0%, #cbeeff 100%);
border: 5px solid #333;
touch-action: none;
}
@media (max-aspect-ratio: 1/1) {
#mysvg {
width: 100%;
height: auto;
}
}
#mysvg rect {
fill: #069;
}
#mysvg circle {
stroke-width: 3;
stroke: #f00;
fill: #ff0;
}
#mysvg text {
fill: #fff;
}
#output {
position: fixed;
display: grid;
grid-template-columns: auto 3em 3em;
gap: 0.2em;
bottom: 10px;
right: 10px;
padding: 0.2em;
background-color: rgba(255,255,255,0.9);
border-radius: 5px;
pointer-events: none;
}
#output strong, #output span {
text-align: right;
font-variant-numeric: tabular-nums;
}
h1 {
font-size: 1.1em;
grid-column-start: span 3;
}
#targetID {
grid-column-start: span 2;
}
</style>
</head>
<body>
<svg xmlns="http://www.w3.org/2000/svg" id="mysvg" viewBox="0 0 1000 1000" preserveAspectRatio="xMidYMid meet">
<g id="transform1" transform="scale(4)">
<rect x="50" y="50" width="100" height="100" rx="10" />
<text x="100" y="100" text-anchor="middle" dominant-baseline="middle">scale(4)</text>
</g>
<g id="transform2" transform="scale(2) rotate(10 350 350) skewY(5)">
<rect x="300" y="300" width="100" height="100" rx="10" />
<text x="350" y="350" text-anchor="middle" dominant-baseline="middle">scale(2)</text>
</g>
</svg>
<div id="output">
<h1>DOM to SVG Co-ordinates</h1>
<strong>DOM client X,Y:</strong> <span id="clientX"></span> <span id="clientY"></span>
<strong>SVG X,Y:</strong> <span id="svgX"></span> <span id="svgY"></span>
<strong>target X,Y:</strong> <span id="targetX"></span> <span id="targetY"></span>
<strong>add target:</strong> <span id="targetID"></span>
<strong>add X,Y:</strong> <span id="addX"></span> <span id="addY"></span>
</div>
<script>
// initialize
const
svg = document.getElementById('mysvg'),
NS = svg.getAttribute('xmlns'),
out = {};
'clientX,clientY,svgX,svgY,targetX,targetY,targetID,addX,addY'.split(',').map(s => {
out[s] = { node: document.getElementById(s), value: '-' }
});
// events
svg.addEventListener('pointermove', getCoordinates);
svg.addEventListener('pointerdown', getCoordinates);
svg.addEventListener('pointerdown', createCircle);
// update co-ordinates
function getCoordinates(event) {
// DOM co-ordinate
out.clientX.value = event.clientX;
out.clientY.value = event.clientY;
// SVG co-ordinate
const svgP = svgPoint(svg, out.clientX.value, out.clientY.value);
out.svgX.value = svgP.x;
out.svgY.value = svgP.y;
// target co-ordinate
const svgT = svgPoint(event.target, out.clientX.value, out.clientY.value);
out.targetX.value = svgT.x;
out.targetY.value = svgT.y;
updateInfo();
};
// add a circle to the target
function createCircle(event) {
// circle clicked?
if (event.target.nodeName === 'circle') return;
// add circle to containing element
const
target = event.target.closest('g') || event.target.ownerSVGElement || event.target,
svgP = svgPoint(target, event.clientX, event.clientY),
cX = Math.round(svgP.x),
cY = Math.round(svgP.y),
circle = document.createElementNS(NS, 'circle');
circle.setAttribute('cx', cX);
circle.setAttribute('cy', cY);
circle.setAttribute('r', 30);
target.appendChild(circle);
// output information
out.targetID.value = target.id || target.nodeName;
out.addX.value = cX;
out.addY.value = cY;
updateInfo();
}
// translate page to SVG co-ordinate
function svgPoint(element, x, y) {
var pt = svg.createSVGPoint();
pt.x = x;
pt.y = y;
return pt.matrixTransform(element.getScreenCTM().inverse());
}
// output values
function updateInfo() {
for (p in out) {
out[p].node.textContent = isNaN(out[p].value) ? out[p].value : Math.round(out[p].value);
}
}
</script>
</body>
</html>理想情况下,最好避免DOM与SVG坐标之间的平移,但是,在这种情况下,请使用上述方法来确保该过程在所有页面尺寸上均可靠。








网友评论文明上网理性发言 已有0人参与
发表评论: