
使用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人参与
发表评论: