即使在3个像素的距离处检测单击SVG线

发布于 2025-01-18 13:52:42 字数 1039 浏览 4 评论 0原文

以下是我检测 SVG 行点击的方法:

window.onmousedown = (e) => {
    if (e.target.tagName == 'line') {
        alert();  // do something with e.target
    }
}
svg line:hover { cursor: pointer; }
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
<line x1="320" y1="160" x2="140" y2="00" stroke="black" stroke-width="2"></line>
<line x1="140" y1="00" x2="180" y2="360" stroke="black" stroke-width="2"></line>
<line x1="180" y1="360" x2="400" y2="260" stroke="black" stroke-width="2"></line>
<line x1="00" y1="140" x2="280" y2="60" stroke="black" stroke-width="2"></line>
</svg>

只有当鼠标光标精确地在线上时它才起作用,这并不容易,因此这是一个糟糕的用户体验。

如何从 Javascript 检测 SVG 线上的点击,即使不是完全在线上,但距离 <= 3 像素?

Here is how I detect clicks on SVG lines:

window.onmousedown = (e) => {
    if (e.target.tagName == 'line') {
        alert();  // do something with e.target
    }
}
svg line:hover { cursor: pointer; }
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
<line x1="320" y1="160" x2="140" y2="00" stroke="black" stroke-width="2"></line>
<line x1="140" y1="00" x2="180" y2="360" stroke="black" stroke-width="2"></line>
<line x1="180" y1="360" x2="400" y2="260" stroke="black" stroke-width="2"></line>
<line x1="00" y1="140" x2="280" y2="60" stroke="black" stroke-width="2"></line>
</svg>

It only works if the mouse cursor is precisely on the line, which is not easy, so it's a bad UX.

How to detect a click on a SVG line from Javascript, even if not perfectly on the line, but at a distance of <= 3 pixels?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

叹倦 2025-01-25 13:52:42

有点棘手的解决方案,但要完成工作:

window.onmousedown = (e) => {
    if (e.target.classList.contains('line')) {
        console.log(e.target.href);
    }
}
svg .line:hover {
  cursor: pointer;
}
.line {
  stroke: black;
  stroke-width: 2px;
}
.line.stroke {
  stroke: transparent;
  stroke-width: 6px;
}
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
    <defs>
      <line id="line1" x1="320" y1="160" x2="140" y2="00"></line>
      <line id="line2" x1="140" y1="00" x2="180" y2="360"></line>
      <line id="line3" x1="180" y1="360" x2="400" y2="260"></line>
      <line id="line4" x1="00" y1="140" x2="280" y2="60"></line>
    </defs>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line1" class="line stroke"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line1" class="line"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line2" class="line stroke"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line2" class="line"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line3" class="line stroke"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line3" class="line"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line4" class="line stroke"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line4" class="line"></use>
</svg>

A bit tricky solution, but does the job:

window.onmousedown = (e) => {
    if (e.target.classList.contains('line')) {
        console.log(e.target.href);
    }
}
svg .line:hover {
  cursor: pointer;
}
.line {
  stroke: black;
  stroke-width: 2px;
}
.line.stroke {
  stroke: transparent;
  stroke-width: 6px;
}
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
    <defs>
      <line id="line1" x1="320" y1="160" x2="140" y2="00"></line>
      <line id="line2" x1="140" y1="00" x2="180" y2="360"></line>
      <line id="line3" x1="180" y1="360" x2="400" y2="260"></line>
      <line id="line4" x1="00" y1="140" x2="280" y2="60"></line>
    </defs>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line1" class="line stroke"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line1" class="line"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line2" class="line stroke"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line2" class="line"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line3" class="line stroke"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line3" class="line"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line4" class="line stroke"></use>
    <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#line4" class="line"></use>
</svg>

怼怹恏 2025-01-25 13:52:42

只有一个&lt; line&gt;和一些JavaScript的解决方案很有趣。

我们可以使用现有的Web API document.ElementFrompoint(x,y)。它在给定点返回最高元素。
单击用户单击点,我们可以沿每个轴行驶,并使用该方法找到第一个&lt; line&gt;元素。当我们获得线路或达到最大搜索距离时,我们会停止搜索。

在以下演示中,没有创建额外的元素。变量接近控制着从行的最大距离以考虑选择。
奖励功能:突出显示到鼠标指针的最接近线。因此,用户可以轻松单击所需的线,而无需任何麻烦。

const proximity = 8;

const directions = [
  [0, 0],
  [0, 1], [0, -1],
  [1, 1], [-1, -1],
  [1, 0], [-1, 0],
  [-1, 1], [1, -1]
];

// tracks nearest line
let currentLine = null;

// highlight nearest line to mouse pointer
container.onmousemove = (e) => {
  let line = getNearestLine(e.clientX, e.clientY);
  if (line) {
    if (currentLine !== line)
      currentLine?.classList.remove('highlight');

    currentLine = line;
    currentLine.classList.add('highlight');
    container.classList.add('pointer');
  } else {
    currentLine?.classList.remove('highlight');
    currentLine = null;
    container.classList.remove('pointer')
  }
}

container.onclick = (e) => {
  // we already know in 'onmousemove' which line is the nearest
  // so no need to figure it out again.
  log.textContent = currentLine ? currentLine.textContent : '';
}

// find a nearest line within 'proximity'
function getNearestLine(x, y) {
  // move along each axis and see if we land on a line
  for (let i = 1; i <= proximity; i++) {
    for (let j = 0; j < directions.length; j++) {
      const xx = x + directions[j][0] * i;
      const yy = y + directions[j][1] * i;
      const element = document.elementFromPoint(xx, yy);
      if (element?.tagName == 'line')
        return element;
    };
  }
  return null;
}
svg {
  background-color: wheat;
}

.pointer {
  cursor: pointer;
}

.highlight {
  filter: drop-shadow(0 0 4px black);
}

#log {
  user-select: none;
}
<p>Clicked on: <span id="log"></span></p>
<svg id='container' width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
    <line x1="320" y1="160" x2="140" y2="00" stroke="red" stroke-width="2">1</line>
    <line x1="140" y1="00" x2="180" y2="360" stroke="green" stroke-width="2">2</line>
    <line x1="18" y1="60" x2="400" y2="60" stroke="orange" stroke-width="2">3</line>
    <line x1="00" y1="140" x2="280" y2="60" stroke="blue" stroke-width="2">4</line>
  </svg>

这只是一个演示代码,您可以摆脱不需要的东西。如果您不想在接近中显示何时显示onmousemove并将逻辑移至onclick method。
Only 过滤器:Drop-shadow(...)可以突出显示非平方形形状。否则,您可以更改线宽度或颜色等。

A solution with just one <line> and some JavaScript would be interesting.

We can use existing Web API document.elementFromPoint(x, y). It returns topmost element at given point.
Form user click point we can travel along each axis and find first <line> element using the method. We stop the search when we get a line or we reach maximum search distance.

In following demo no extra elements have been created. The variable proximity controls the max distance from a line to consider it for selection.
Bonus feature: the nearest line to the mouse pointer is highlighted. So user can easily click on desired line without any hassle.

const proximity = 8;

const directions = [
  [0, 0],
  [0, 1], [0, -1],
  [1, 1], [-1, -1],
  [1, 0], [-1, 0],
  [-1, 1], [1, -1]
];

// tracks nearest line
let currentLine = null;

// highlight nearest line to mouse pointer
container.onmousemove = (e) => {
  let line = getNearestLine(e.clientX, e.clientY);
  if (line) {
    if (currentLine !== line)
      currentLine?.classList.remove('highlight');

    currentLine = line;
    currentLine.classList.add('highlight');
    container.classList.add('pointer');
  } else {
    currentLine?.classList.remove('highlight');
    currentLine = null;
    container.classList.remove('pointer')
  }
}

container.onclick = (e) => {
  // we already know in 'onmousemove' which line is the nearest
  // so no need to figure it out again.
  log.textContent = currentLine ? currentLine.textContent : '';
}

// find a nearest line within 'proximity'
function getNearestLine(x, y) {
  // move along each axis and see if we land on a line
  for (let i = 1; i <= proximity; i++) {
    for (let j = 0; j < directions.length; j++) {
      const xx = x + directions[j][0] * i;
      const yy = y + directions[j][1] * i;
      const element = document.elementFromPoint(xx, yy);
      if (element?.tagName == 'line')
        return element;
    };
  }
  return null;
}
svg {
  background-color: wheat;
}

.pointer {
  cursor: pointer;
}

.highlight {
  filter: drop-shadow(0 0 4px black);
}

#log {
  user-select: none;
}
<p>Clicked on: <span id="log"></span></p>
<svg id='container' width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
    <line x1="320" y1="160" x2="140" y2="00" stroke="red" stroke-width="2">1</line>
    <line x1="140" y1="00" x2="180" y2="360" stroke="green" stroke-width="2">2</line>
    <line x1="18" y1="60" x2="400" y2="60" stroke="orange" stroke-width="2">3</line>
    <line x1="00" y1="140" x2="280" y2="60" stroke="blue" stroke-width="2">4</line>
  </svg>

This is just a demo code you can get rid of unwanted stuff. If you don't want hand to show when in proximity then delete onmousemove and move the logic to onclick method.
Only filter: drop-shadow(...) can highlight non-square shapes. Otherwise, you can change line width or color etc.

你げ笑在眉眼 2025-01-25 13:52:42

只是做数学...

这可能是过分的,但是这三个像素的确切性使我感到困扰,所以这是“关于数学的全部”解决方案。

getLineNrange(Point,Mindist,SVG)将返回Mindist的所有行。当前,它正在使用mousemove将类应用于所有线路。单击显示所有线路的数组范围内的数组,该数组首先具有最接近的线。

一个警告,这将在SVG的“执行任何内部缩放或偏移定位”的地方无法使用。

更新:现在不在乎任何SVG突变,例如缩放和偏移。

更新2 已经提出了速度问题,因此我决定证明其实际计算的速度。计算机擅长的一件事是处理数字。唯一真正的放缓是,当它将掉落阴影应用于150多行时,这是渲染而不是数学的限制,而不是微小的修改,您只能将效果应用于最接近的行。现在,您可以添加多达1000行进行测试。

//Distance Calculations
const disToLine = (p, a, b) => {
    let sqr = (n) => n * n,
        disSqr = (a, b) => sqr(a.x - b.x) + sqr(a.y - b.y),
        lSqr = disSqr(a, b);
  if (!lSqr) return disSqr(p, a);
  let t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / lSqr;
  t = Math.max(0, Math.min(1, t));
  return Math.sqrt(
    disSqr(p, { x: a.x + t * (b.x - a.x), y: a.y + t * (b.y - a.y) })
  );
};

//Calculates the absolute coordinates of a line
const calculateAbsoluteCords = (line) => {
    let getSlope = ([p1, p2]) => (p1.y - p2.y) / (p1.x - p2.x),
        rec = line.getBoundingClientRect(),
      coords = [
          { x: +line.getAttribute("x1"), y: +line.getAttribute("y1") },
          { x: +line.getAttribute("x2"), y: +line.getAttribute("y2") }];
  if (getSlope(coords) <= 0)
    coords = [ 
      { x: rec.x, y: rec.y + rec.height },
      { x: rec.x + rec.width, y: rec.y }
    ];
  else
    coords = [
      { x: rec.x, y: rec.y },
      { x: rec.x + rec.width, y: rec.y + rec.height }
    ];
  return coords;
};

//gets all lines in range of a given point
const getLinesInRange = (point, minimumDistance, svg) => {
  let linesInRange = [],
    lines = svg.querySelectorAll("line");
  lines.forEach(line => {
    let [p1, p2] = calculateAbsoluteCords(line),
      dis = disToLine(point, p1, p2);
    if (dis <= minimumDistance) {
      line.classList.add("closeTo");
      linesInRange.push({ dis: dis, line: line });
    } else line.classList.remove("closeTo");
  });
  return linesInRange.sort((a,b) => a.dis > b.dis ? 1 : -1).map(l => l.line);
};

let minDist = 3, el = {};
['mouseRange', 'rangeDisplay', 'mouseRangeDisplay', 'numberOfLines', 'numberInRange', 'numberOfLinesDisplay', 'clicked', 'svgContainer']
    .forEach(l => {el[l] = document.getElementById(l); })

el.svgContainer.addEventListener("mousemove", (e) => {
  el.numberInRange.textContent = getLinesInRange({ x: e.clientX, y: e.clientY }, minDist, el.svgContainer).length;
});

el.svgContainer.addEventListener("click", (e) => {
  let lines = getLinesInRange({ x: e.clientX, y: e.clientY }, minDist, el.svgContainer);
  el.clicked.textContent = lines.map((l) => l.getAttribute("stroke")).join(', ');
});

el.mouseRange.addEventListener("input", () => {
  minDist = parseInt(el.mouseRange.value);
  el.mouseRangeDisplay.textContent = minDist;
});

el.numberOfLines.addEventListener("input", () => {
  let numOfLines = parseInt(el.numberOfLines.value);
  el.numberOfLinesDisplay.textContent = numOfLines;
  generateLines(numOfLines);
});

let generateLines = (total) => {
    let lineCount = el.svgContainer.querySelectorAll('line').length;
  if(lineCount > total) {
    let lines = el.svgContainer.querySelectorAll(`line:nth-last-child(-n+${lineCount-total})`);
    lines.forEach(l => l.remove());
  }
  for(let i=lineCount; i<total; i++) {
    var newLine = document.createElementNS('http://www.w3.org/2000/svg','line')
    newLine.setAttribute('id','line2');
    ['x1','y1','x2','y2'].map(attr => newLine.setAttribute(attr,Math.floor(Math.random()*500)));
    newLine.setAttribute("stroke", '#' + Math.floor(Math.random()*16777215).toString(16));
    el.svgContainer.appendChild(newLine);
  }
}
generateLines(10);
.closeTo {
  filter: drop-shadow(0 0 3px rgba(0,0,0,1));
}
Range: <input type="range" min="1" max="50" id="mouseRange" value="3" /><span id="mouseRangeDisplay">3</span>
#Lines: <input type="range" min="0" max="1000" id="numberOfLines" value="10" step="10" /><span id="numberOfLinesDisplay">10</span>
In Range: <span id="numberInRange">3</span>
<br/>
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svgContainer" style="width:500px;height:500px;background:#F1F1F1;">
</svg><br/>
Clicked: <span id="clicked"></span>

Just do the maths...

This is probably overkill, but the exactness of those 3 pixels bothered me so here's an "all about the math's" solution.

getLinesInRange(point, minDist,svg) will return ALL lines in range of the minDist. It is currently applying a class to all lines in range with mousemove. Click shows an array of all lines in range sorted by distance having the closest line first.

One caveat, this will not work in svg's where any internal scaling or offset positioning is being performed.

UPDATE: Now doesn't care about any SVG mutations like scaling and offset.

UPDATE 2 The question of speed has been brought up so I've decided to demonstrate how quickly it actual does calculations. One thing computers are good at is crunching numbers. The only real slowdown is when it's applying a drop-shadow to 150+ lines, however, this is a limitation of the render and not the maths, with a small modification you can just apply the effect to the closest line only. Now you can add up to 1000 lines to test.

//Distance Calculations
const disToLine = (p, a, b) => {
    let sqr = (n) => n * n,
        disSqr = (a, b) => sqr(a.x - b.x) + sqr(a.y - b.y),
        lSqr = disSqr(a, b);
  if (!lSqr) return disSqr(p, a);
  let t = ((p.x - a.x) * (b.x - a.x) + (p.y - a.y) * (b.y - a.y)) / lSqr;
  t = Math.max(0, Math.min(1, t));
  return Math.sqrt(
    disSqr(p, { x: a.x + t * (b.x - a.x), y: a.y + t * (b.y - a.y) })
  );
};

//Calculates the absolute coordinates of a line
const calculateAbsoluteCords = (line) => {
    let getSlope = ([p1, p2]) => (p1.y - p2.y) / (p1.x - p2.x),
        rec = line.getBoundingClientRect(),
      coords = [
          { x: +line.getAttribute("x1"), y: +line.getAttribute("y1") },
          { x: +line.getAttribute("x2"), y: +line.getAttribute("y2") }];
  if (getSlope(coords) <= 0)
    coords = [ 
      { x: rec.x, y: rec.y + rec.height },
      { x: rec.x + rec.width, y: rec.y }
    ];
  else
    coords = [
      { x: rec.x, y: rec.y },
      { x: rec.x + rec.width, y: rec.y + rec.height }
    ];
  return coords;
};

//gets all lines in range of a given point
const getLinesInRange = (point, minimumDistance, svg) => {
  let linesInRange = [],
    lines = svg.querySelectorAll("line");
  lines.forEach(line => {
    let [p1, p2] = calculateAbsoluteCords(line),
      dis = disToLine(point, p1, p2);
    if (dis <= minimumDistance) {
      line.classList.add("closeTo");
      linesInRange.push({ dis: dis, line: line });
    } else line.classList.remove("closeTo");
  });
  return linesInRange.sort((a,b) => a.dis > b.dis ? 1 : -1).map(l => l.line);
};

let minDist = 3, el = {};
['mouseRange', 'rangeDisplay', 'mouseRangeDisplay', 'numberOfLines', 'numberInRange', 'numberOfLinesDisplay', 'clicked', 'svgContainer']
    .forEach(l => {el[l] = document.getElementById(l); })

el.svgContainer.addEventListener("mousemove", (e) => {
  el.numberInRange.textContent = getLinesInRange({ x: e.clientX, y: e.clientY }, minDist, el.svgContainer).length;
});

el.svgContainer.addEventListener("click", (e) => {
  let lines = getLinesInRange({ x: e.clientX, y: e.clientY }, minDist, el.svgContainer);
  el.clicked.textContent = lines.map((l) => l.getAttribute("stroke")).join(', ');
});

el.mouseRange.addEventListener("input", () => {
  minDist = parseInt(el.mouseRange.value);
  el.mouseRangeDisplay.textContent = minDist;
});

el.numberOfLines.addEventListener("input", () => {
  let numOfLines = parseInt(el.numberOfLines.value);
  el.numberOfLinesDisplay.textContent = numOfLines;
  generateLines(numOfLines);
});

let generateLines = (total) => {
    let lineCount = el.svgContainer.querySelectorAll('line').length;
  if(lineCount > total) {
    let lines = el.svgContainer.querySelectorAll(`line:nth-last-child(-n+${lineCount-total})`);
    lines.forEach(l => l.remove());
  }
  for(let i=lineCount; i<total; i++) {
    var newLine = document.createElementNS('http://www.w3.org/2000/svg','line')
    newLine.setAttribute('id','line2');
    ['x1','y1','x2','y2'].map(attr => newLine.setAttribute(attr,Math.floor(Math.random()*500)));
    newLine.setAttribute("stroke", '#' + Math.floor(Math.random()*16777215).toString(16));
    el.svgContainer.appendChild(newLine);
  }
}
generateLines(10);
.closeTo {
  filter: drop-shadow(0 0 3px rgba(0,0,0,1));
}
Range: <input type="range" min="1" max="50" id="mouseRange" value="3" /><span id="mouseRangeDisplay">3</span>
#Lines: <input type="range" min="0" max="1000" id="numberOfLines" value="10" step="10" /><span id="numberOfLinesDisplay">10</span>
In Range: <span id="numberInRange">3</span>
<br/>
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svgContainer" style="width:500px;height:500px;background:#F1F1F1;">
</svg><br/>
Clicked: <span id="clicked"></span>

乖乖兔^ω^ 2025-01-25 13:52:42

使用多个元素

通常 ,您可以使用SVG组('g'元素),并包括两个元素,其中一个较大,一个不透明度为0或pranse>透明的填充< /代码>。

document.querySelectorAll('g.clickable').forEach(node => node.addEventListener('click', function() {
  alert();
}))
svg .clickable:hover { cursor: pointer; }
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
<g class="clickable">
<line x1="320" y1="160" x2="140" y2="0" stroke="black" stroke-width="2"></line>
<line x1="320" y1="160" x2="140" y2="0" stroke="transparent" stroke-width="16" opacity="0"></line>
</g>
</svg>

使用具有相同坐标的两个元素自动执行此操作

有点冗余。实际上,您可能想从动态数据(尤其是在执行数据驱动图形的情况下)构造元素,或者可以通过编程方式迭代所有现有行,然后用组元素替换它们。

我将展示第二个,因为这似乎是问题:

var svgNS = 'http://www.w3.org/2000/svg';
document.querySelectorAll('svg line').forEach(function (node) {
  if (svg.parentNode.classList.contains('clickable-line')) {
    return;
  }
  var g = document.createElementNS(svgNS, 'g');
  g.classList.add('clickable-line');
  var displayLine = node.cloneNode();
  var transparentLine = node.cloneNode();
  g.appendChild(displayLine);
  g.appendChild(transparentLine);
  transparentLine.setAttributeNS(null, 'stroke-width', '20');
  transparentLine.setAttributeNS(null, 'opacity', '0');
  
  g.addEventListener('click', function () {
    // do something with `node` or `g`
    alert();
  });
  node.parentNode.replaceChild(g, node);
});
svg .clickable-line:hover {
  cursor: pointer
}
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
<line x1="320" y1="160" x2="140" y2="0" stroke="black" stroke-width="2"></line>
<line x1="140" y1="0" x2="180" y2="360" stroke="black" stroke-width="2"></line>
</svg>

Using multiple elements

In general, you can use an svg group ('g' element), and include two elements, with one bigger and an opacity of 0 or a stroke/fill of transparent.

document.querySelectorAll('g.clickable').forEach(node => node.addEventListener('click', function() {
  alert();
}))
svg .clickable:hover { cursor: pointer; }
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
<g class="clickable">
<line x1="320" y1="160" x2="140" y2="0" stroke="black" stroke-width="2"></line>
<line x1="320" y1="160" x2="140" y2="0" stroke="transparent" stroke-width="16" opacity="0"></line>
</g>
</svg>

Automatically doing this

Using two elements with the same coordinates is a bit redundant. In practice, probably you'd want to construct elements based from dynamic data (particularly if you're doing data-driven graphics), or you can programmatically iterate through all of the existing lines and then replace them with group elements.

I'll show the second, since that's what the question seems to be asking:

var svgNS = 'http://www.w3.org/2000/svg';
document.querySelectorAll('svg line').forEach(function (node) {
  if (svg.parentNode.classList.contains('clickable-line')) {
    return;
  }
  var g = document.createElementNS(svgNS, 'g');
  g.classList.add('clickable-line');
  var displayLine = node.cloneNode();
  var transparentLine = node.cloneNode();
  g.appendChild(displayLine);
  g.appendChild(transparentLine);
  transparentLine.setAttributeNS(null, 'stroke-width', '20');
  transparentLine.setAttributeNS(null, 'opacity', '0');
  
  g.addEventListener('click', function () {
    // do something with `node` or `g`
    alert();
  });
  node.parentNode.replaceChild(g, node);
});
svg .clickable-line:hover {
  cursor: pointer
}
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
<line x1="320" y1="160" x2="140" y2="0" stroke="black" stroke-width="2"></line>
<line x1="140" y1="0" x2="180" y2="360" stroke="black" stroke-width="2"></line>
</svg>

农村范ル 2025-01-25 13:52:42

将其包装在本机 Web 组件 (JSWC) 中

所有浏览器都支持。因此,您可以在任何您想要的地方重复使用它,

  • 从其他答案中获取最好的部分
<svg-lines margin="30">
  <svg>
    <style> line { stroke-width:2 }  </style>
    <line x1="320" y1="160" x2="140" y2="00" stroke="red"  >1</line>
    <line x1="140" y1="0"  x2="180" y2="360" stroke="green" >2</line>
    <line x1="18"  y1="60"  x2="400" y2="60" stroke="orange">3</line>
    <line x1="00"  y1="140" x2="280" y2="60" stroke="blue"  >4</line>
  </svg>
</svg-lines>

<script>
  customElements.define("svg-lines", class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => { // wait till lightDOM is parsed
        this.querySelector("svg")
          .append(Object.assign(
              document.createElement("style"), {
                innerHTML: `.hover { filter:drop-shadow(0 0 4px black) }
                       .hoverline {stroke-width:${this.getAttribute("margin")||20}; 
                                   opacity:0; cursor:pointer }`
              }),
            ...[...this.querySelector("svg")
                       .querySelectorAll("[stroke]")
               ].map((el) => {
                  let hover = el.cloneNode();
                  hover.classList.add("hoverline");
                  hover.onmouseenter = () => el.classList.add("hover");
                  hover.onmouseout = () => el.classList.remove("hover");
                  hover.onclick = () => alert("clicked line#" + el.innerHTML);
                  return hover;
            }));
      })
    }
  })
</script>

wrapping it in a Native Web Component (JSWC) <svg-lines>

Supported in all Browsers. So you can reuse it anywhere you want

  • taking the best parts from other answers

<svg-lines margin="30">
  <svg>
    <style> line { stroke-width:2 }  </style>
    <line x1="320" y1="160" x2="140" y2="00" stroke="red"  >1</line>
    <line x1="140" y1="0"  x2="180" y2="360" stroke="green" >2</line>
    <line x1="18"  y1="60"  x2="400" y2="60" stroke="orange">3</line>
    <line x1="00"  y1="140" x2="280" y2="60" stroke="blue"  >4</line>
  </svg>
</svg-lines>

<script>
  customElements.define("svg-lines", class extends HTMLElement {
    connectedCallback() {
      setTimeout(() => { // wait till lightDOM is parsed
        this.querySelector("svg")
          .append(Object.assign(
              document.createElement("style"), {
                innerHTML: `.hover { filter:drop-shadow(0 0 4px black) }
                       .hoverline {stroke-width:${this.getAttribute("margin")||20}; 
                                   opacity:0; cursor:pointer }`
              }),
            ...[...this.querySelector("svg")
                       .querySelectorAll("[stroke]")
               ].map((el) => {
                  let hover = el.cloneNode();
                  hover.classList.add("hoverline");
                  hover.onmouseenter = () => el.classList.add("hover");
                  hover.onmouseout = () => el.classList.remove("hover");
                  hover.onclick = () => alert("clicked line#" + el.innerHTML);
                  return hover;
            }));
      })
    }
  })
</script>

原谅我要高飞 2025-01-25 13:52:42

制作该行的两个副本,将它们分组在一起,并在CSS中增加第二行的描边宽度,还设置描边:透明以隐藏第二行,现在您将获得更宽的可点击区域。我希望您发现这是最好的方法。

document.querySelectorAll('#svg g').forEach((item) => {
    item.addEventListener('click', (e) => {
        const index = Array.from(item.parentNode.children).indexOf((item))
        console.log(index+1);
    })
})
g{
  cursor: pointer;
}
line{
  stroke: black;
  stroke-width: 2px;
}
line:nth-child(2) {
  stroke-width: 1em;
  stroke: transparent;
}
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
      <g>
        <line x1="320" y1="160" x2="140" y2="00"></line>
        <line x1="320" y1="160" x2="140" y2="00"></line>
      </g>
      <g>
        <line x1="140" y1="00" x2="180" y2="360"></line>
        <line x1="140" y1="00" x2="180" y2="360"></line>
      </g>
      <g>
        <line x1="00" y1="140" x2="280" y2="60"></line>
        <line x1="00" y1="140" x2="280" y2="60"></line>
      </g>
</svg>

Make two copies of the line, group them together, and increase the stroke width of the second line in CSS also set stroke: transparent to hide second line, now you will get clickable area wider. I hope you find this is the best method.

document.querySelectorAll('#svg g').forEach((item) => {
    item.addEventListener('click', (e) => {
        const index = Array.from(item.parentNode.children).indexOf((item))
        console.log(index+1);
    })
})
g{
  cursor: pointer;
}
line{
  stroke: black;
  stroke-width: 2px;
}
line:nth-child(2) {
  stroke-width: 1em;
  stroke: transparent;
}
<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg" id="svg">
      <g>
        <line x1="320" y1="160" x2="140" y2="00"></line>
        <line x1="320" y1="160" x2="140" y2="00"></line>
      </g>
      <g>
        <line x1="140" y1="00" x2="180" y2="360"></line>
        <line x1="140" y1="00" x2="180" y2="360"></line>
      </g>
      <g>
        <line x1="00" y1="140" x2="280" y2="60"></line>
        <line x1="00" y1="140" x2="280" y2="60"></line>
      </g>
</svg>

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文