Bas*_*asj 9 javascript mouse svg hover
以下是我检测 SVG 行点击的方法:
window.onmousedown = (e) => {
if (e.target.tagName == 'line') {
alert(); // do something with e.target
}
}Run Code Online (Sandbox Code Playgroud)
svg line:hover { cursor: pointer; }Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
只有当鼠标光标精确地在线上时它才起作用,这并不容易,因此这是一个糟糕的用户体验。
如何从 Javascript 检测 SVG 线上的点击,即使不是完全在线上,但距离 <= 3 像素?
syd*_*uki 10
有点棘手的解决方案,但可以完成工作:
window.onmousedown = (e) => {
if (e.target.classList.contains('line')) {
console.log(e.target.href);
}
}Run Code Online (Sandbox Code Playgroud)
svg .line:hover {
cursor: pointer;
}
.line {
stroke: black;
stroke-width: 2px;
}
.line.stroke {
stroke: transparent;
stroke-width: 6px;
}Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
仅使用一个和一些 JavaScript 的解决方案
<line>会很有趣。
我们可以使用现有的Web API document.elementFromPoint(x, y)。它返回给定点的最顶层元素。
从用户点击点我们可以沿着每个轴移动并<line>使用该方法找到第一个元素。当我们得到一条线或达到最大搜索距离时,我们停止搜索。
在下面的演示中没有创建额外的元素。该变量proximity控制与考虑选择的线的最大距离。
额外功能:突出显示距鼠标指针最近的行。因此,用户可以轻松点击所需的线路,没有任何麻烦。
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;
}Run Code Online (Sandbox Code Playgroud)
svg {
background-color: wheat;
}
.pointer {
cursor: pointer;
}
.highlight {
filter: drop-shadow(0 0 4px black);
}
#log {
user-select: none;
}Run Code Online (Sandbox Code Playgroud)
<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>Run Code Online (Sandbox Code Playgroud)
这只是一个演示代码,您可以删除不需要的东西。如果您不想在接近时显示手,则删除onmousemove逻辑并将其移动到onclick方法中。
只能filter: drop-shadow(...)突出显示非方形形状。否则,您可以更改线宽或颜色等。
只要做数学...
这可能有点过分了,但是这 3 个像素的精确性让我很困扰,所以这里有一个“所有关于数学”的解决方案。
getLinesInRange(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);Run Code Online (Sandbox Code Playgroud)
.closeTo {
filter: drop-shadow(0 0 3px rgba(0,0,0,1));
}Run Code Online (Sandbox Code Playgroud)
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>Run Code Online (Sandbox Code Playgroud)