在网页上,使用 SVG 作为锚链接或其他“可点击/可触摸元素”是很常见的。内联 <svg>
也越来越普遍,因为将 SVG 放置在 DOM 中通常很方便,因为您可以使用 CSS 对其进行样式设置,并使用 JS 对其进行脚本处理等等。但这对点击事件意味着什么呢?
包含 SVG 图标的链接可能如下所示
<a href="#0" data-data="something">
<svg ... >
<rect ...>
<svg>
</a>
现在您想将点击事件绑定到该锚链接。在 jQuery 中
$("a").on("click", function(event) {
// `this` will always be the <a>
console.log($(this).data("data"));
// "something"
});
这将完美运行。请注意锚链接上有一个 data-*
属性。这可能是专门为 JavaScript 访问和使用而设置的。就我们目前编写的方式而言,完全没有问题,因为在我们绑定的匿名函数中,this
将始终是该锚链接,该链接具有可用的属性。即使您使用事件委托并调用某个未知位置的函数来处理它,this
也会是该锚链接,并且您可以轻松地获取该 data-*
属性。
但是,假设您要使用一些原始的 JavaScript 事件委托
document.addEventListener('click', doThing);
function doThing(event) {
// test for an element match here
}
您在那里没有对锚链接的简单引用。您需要测试 event.target 以查看它是否确实是锚链接。但在我们的 SVG-in-a-link 的情况下,目标是什么呢?
它可能是
<a>
<svg>
<rect>
您可能只需要检查被点击元素的 tagName,如果知道它是一个子元素,则向上移动链
document.addEventListener('click', doThing);
function doThing(event) {
var el;
// we can check the tag type, and if it's not the <a>, move up.
if (event.target.tagType == "rect") {
// move up TWICE
el = event.target.parentElement.parentElement;
} else if (event.target.tagType == "svg") {
// move up ONCE
el = event.target.parentElement;
} else {
el = event.target;
}
console.log(el.getAttribute("data-data"));
}
但这有点疯狂。它与 HTML 和 SVG 结构的绑定太紧密了。在一些 <path>
周围添加一个 <g>
,这对于分组来说是一件非常好的事情,但它会破坏它。或者 tagType
是 path
而不是 rect
,或者任何其他 DOM 差异。
就我个人而言,我更喜欢一些 CSS 解决方案。
一种方法是在整个锚元素的顶部放置一个伪元素,以便点击保证在锚元素本身上,而不是其内部的任何内容
a {
position: relative;
}
a::after {
content: "";
top: 0;
left: 0;
width: 100%;
height: 100%;
}
这似乎也运行良好
a > svg {
pointer-events: none;
}
pointer-events 通常在 IE 中不起作用(在 11+ 中起作用,但在较低版本中不起作用),但当应用于 SVG 时,它实际上在 IE 9+ 中起作用,而 IE 9+ 是支持 SVG 的 IE 版本。
这是一个展示问题和修复方案的 Pen
查看 Chris Coyier 在 CodePen 上的 Pen owyFj (@chriscoyier)。
重点是
如果在点击目标中使用 SVG,请务必小心在 JavaScript 中获取正确的元素,如果遇到问题,请考虑使用 CSS 覆盖。
使用原始事件委托遇到的此问题几乎适用于锚点中的任何子元素,而不仅仅是 SVG。我在动画按钮上也遇到过类似的问题,其中指针可能在一个元素(例如,图标图形)中开始
mouseDown
,但在另一个元素(例如,文本)中mouseUp
,这意味着它不会触发正确的click
事件。覆盖整个<a>
的全尺寸伪元素是一个非常巧妙的解决方法。svg
内部的事件不会像通常通过 DOM 一样冒泡吗?在这种情况下,您的函数可以以if (event.target.tagType !== "a") { return; }
开头,最终,即使点击落在rect
上,事件也会在a
上触发,因为它会冒泡。也许我遗漏了什么……
它可能确实如此。但在事件委托的情况下,没有内容绑定到
a
。您需要检查目标以确定是否应该执行任何操作。使用 jQuery 进行某种通用的测试非常简单,它与 SVG 的结构无关(尽管伪元素解决方案可能更好)
实际上,我刚刚想起来
.closest
也会检查它调用的元素,因此该if
块不是必需的。我不确定我是否理解正在解决的问题..
在最新的 Firefox 中没有发现任何内容。
我认为 David 几乎快到了——至少有一种情况是我所做的是使它递归,类似于此伪代码
function thisHandler(){
if (this != reqElementType){
return thisHandler(this.parentElement);
}else{
return [我们需要返回的内容]
}
(加上一个小检查以防我们最终到达 DOCUMENT 或 WINDOW 或其他内容。)
Prototype.js 有一个方便的元素方法:up()。
$(eventObject).up('a')
将返回周围的<a>
元素,即使 eventObject 本身就是链接。在使用这种中心事件处理程序时,您可以使用伪类标记所有事件“捕获”元素,例如“evReceiver”
$(eventObject).up('.evReceiver')
您甚至可以将其用于相反的含义,例如忽略标记的控制器(构建自建下拉元素时的一个常见问题)内的所有 mousemove 事件。
当然,使用 jQuery 可以用类似的方法做到这一点,但使用 prototype.js 可以让您更了解底层 DOM、“一切都是对象”的策略和原型扩展,而不是类/方法思维。
还要记住一个主题
全局事件处理程序会导致性能问题。如果您为单个元素注册大量事件处理程序,JS 引擎将在内部处理调度(这很快)。如果您自己进行调度,引擎可能难以优化。
此外,全局事件处理程序会在您根本不想处理的许多元素上触发。
如果可以,请按以下方式进行
为每个要处理的元素注册事件处理程序。不要使用内联(lambda)函数;而是创建通用函数并在事件注册代码中引用它们。此函数缓存对于现代 JS 引擎可能已过时,但对于旧版浏览器肯定更快。
为什么不这样做呢?
因为
<a>
可能不是parentNode
,它可能比这更深。while
循环应该可以解决这个问题。哦,对了!我甚至都没有看到那个。Derp。看起来这可以正常工作。parentNode 与 parentElement 之间有什么区别?我想在这种情况下没有太大区别。
因为“在我那个年代,我们没有花哨的
parentElement
,我们只有parentNode
!”哈哈**我的 javascript 代码中有一些类似的东西**
对我来说运行得很好!;)
您还可以使用
event.currentTarget
检查被点击的元素好吧,与此有点相关,在锚点内使用 SVG,如果链接已被访问,则过渡在基于 webkit 的浏览器中不起作用。有没有人遇到过同样的问题或知道任何解决方法?
测试:http://jsfiddle.net/eEfjS/
我发现,一般来说,将 CSS 过渡应用于 SVG 会出现错误,但这个特定的触发器对我来说是新的。
我想知道你遇到的问题是否与此相关?
developer.mozilla.org/en-US/docs/Web/CSS/Privacy_and_the_:visited_selector
Gecko 和 Webkit 可能对这个安全问题的处理方式不同。
他们上周修复了它!https://code.google.com/p/chromium/issues/detail?id=314892 将在 Chrome 37 中可用
正如 Jeremy T 所说,如果你使用 jQuery,我确信你可以使用 $(event.target).closest(“a”) 来始终选择链接锚元素。用伪 CSS 掩盖它对我来说有点 hacky,但它非常聪明。
如果你愿意牺牲 210 字节的 JavaScript,可以 polyfill `matches` 和 prollyfill `closest`。
然后做任何你想做的事情。
^ 发布到 GitHub。 https://github.com/jonathantneal/closest
为了使 `::after` 伪元素工作,我必须向其添加 `position: absolute`。此外,由于我将其应用于具有圆角的按钮,因此我还必须将伪元素的角也圆角化才能获得正确的点击和悬停区域。
另外,我想补充一点,我开始怀疑 Chris 实际上是一位绝地大师。我在 5 月 1 日遇到了这个问题,在星球大战日(5 月 4 日)之后,他立即发布了解决方案。:D