Es ist ziemlich üblich, SVG innerhalb eines Ankerlinks oder eines anderen „klick-/tippbaren Dings“ auf einer Webseite zu verwenden. Es ist auch zunehmend üblich, dass das SVG inline <svg> ist, da es oft schön ist, das SVG im DOM zu haben, da man es mit CSS gestalten und mit JS und so weiter skripten kann. Aber was bedeutet das für Klickereignisse?
Ein Link mit einem SVG-Icon darin könnte so aussehen
<a href="#0" data-data="something">
<svg ... >
<rect ...>
<svg>
</a>
Jetzt möchten Sie ein Klickereignis an diesen Ankerlink binden. In jQuery
$("a").on("click", function(event) {
// `this` will always be the <a>
console.log($(this).data("data"));
// "something"
});
Das wird einwandfrei funktionieren. Beachten Sie, dass das Ankerlink ein data-*-Attribut hat. Das ist wahrscheinlich speziell dafür da, damit JavaScript darauf zugreifen und es verwenden kann. Kein Problem, wie wir es gerade geschrieben haben, denn innerhalb der anonymen Funktion, die wir gebunden haben, wird this immer dieser Ankerlink sein, der dieses Attribut zur Verfügung hat. Selbst wenn Sie Event-Delegation verwenden und eine Funktion aufrufen, wer-weiß-wo, um sie zu verarbeiten, wird this dieser Ankerlink sein und Sie können dieses data-*-Attribut leicht abgreifen.
Aber nehmen wir an, Sie wollen etwas rohes JavaScript-Event-Delegation rocken
document.addEventListener('click', doThing);
function doThing(event) {
// test for an element match here
}
Sie haben dort keine einfache Referenz auf Ihren Ankerlink. Sie müssen das event.target testen, um zu sehen, ob es überhaupt der Ankerlink ist. Aber im Fall unseres SVG-in-einem-Link, was ist dieses Ziel?
Es könnte entweder sein
- Das
<a> - Das
<svg> - Das
<rect>
Sie müssen vielleicht nur den tagName des angeklickten Elements überprüfen und, wenn Sie wissen, dass es ein Unterelement war, die Kette nach oben gehen
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"));
}
Das ist aber ziemlich verrückt. Es ist zu stark an die HTML- und SVG-Struktur gebunden. Fügt man ein <g> drumherum um einige <path>s, was eine vollkommen in Ordnung Sache zum Gruppieren ist, und es bricht. Oder die tagType ist path und nicht rect, oder irgendein anderer DOM-Unterschied.
Persönlich bevorzuge ich einige CSS-Lösungen.
Eine Möglichkeit ist, ein Pseudo-Element über das gesamte Anker-Element zu legen, damit die Klicks garantiert auf dem Anker-Element selbst landen, nichts darin
a {
position: relative;
}
a::after {
content: "";
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Das scheint auch ziemlich gut zu funktionieren
a > svg {
pointer-events: none;
}
pointer-events funktionieren typischerweise nicht in IE (in 11+ schon, aber nicht darunter), aber es funktioniert tatsächlich, wenn es auf SVG angewendet wird, in IE 9+, was die Version von IE ist, die SVG sowieso unterstützt.
Hier ist ein Pen mit dem Problem demonstriert und die Lösungen
Siehe den Pen owyFj von Chris Coyier (@chriscoyier) auf CodePen.
Punkt ist
Wenn Sie SVG in einem Klickziel verwenden, seien Sie sehr vorsichtig, dass Sie das richtige Element in JavaScript erhalten, und wenn Sie Probleme haben, erwägen Sie eine CSS-Abdeckung.
Dieses Problem mit roher Event-Delegation könnte auf fast jedes Kindelement in einem Anker zutreffen, nicht nur auf SVG. Ich hatte ähnliche Probleme mit animierten Buttons, bei denen der Zeiger
mouseDownin einem Element (z. B. einer Icon-Grafik) starten könnte, abermouseUpin einem anderen Element (z. B. Text) hat, was bedeutet, dass kein ordnungsgemäßesclick-Ereignis ausgelöst wird. Ein vollflächiges Pseudo-Element, um den gesamten<a>abzudecken, ist ein super cleverer Workaround.Blubbert das Ereignis nicht auch innerhalb von
svgnach oben, wie es normalerweise im DOM geschieht? In diesem Fall könnte Ihre Funktion mitif (event.target.tagType !== "a") { return; }beginnen und schließlich, selbst wenn der Klick aufrectlandet, würde das Ereignis auf dasafeuern, wenn es nach oben blubbert.Vielleicht übersehe ich etwas…
Es könnte tatsächlich sein. Aber im Fall der Event-Delegation ist nichts an das
agebunden. Sie müssen das Ziel überprüfen, um zu bestimmen, ob Sie etwas tun sollen.Mit jQuery wäre es ziemlich einfach, einen Catch-all-Test zu haben, der nicht an die Struktur des SVG gebunden ist (obwohl die Pseudo-Element-Lösung wahrscheinlich besser ist)
Eigentlich erinnere ich mich, dass
.closestauch das Element prüft, auf dem es aufgerufen wird, sodass dieserif-Block nicht notwendig ist.Ich bin mir nicht sicher, ob ich das zu lösende Problem verstehe..
Genau Nichts gefunden im neuesten Firefox.
Ich denke, David ist fast am Ziel – bei mindestens einer Gelegenheit habe ich es rekursiv gemacht, so etwas wie dieser Pseudocode
function thisHandler(){
if (this != reqElementType){
return thisHandler(this.parentElement);
}else{
return [was auch immer wir zurückgeben müssen]
}
(Plus eine kleine Prüfung, um auszubrechen, wenn wir irgendwann DOCUMENT oder WINDOW oder was auch immer erreichen.)
Prototype.js hat eine komfortable Element-Methode: up().
$(eventObject).up('a')gibt das umgebende<a>-Element zurück, auch wenn eventObject das Link selbst ist.Bei der Verwendung eines solchen zentralen Event-Handlers könnten Sie alle Event-„Fang“-Elemente mit einer Pseudo-Klasse taggen, z. B. „evReceiver“
$(eventObject).up('.evReceiver')Sie können es sogar mit entgegengesetzter Bedeutung verwenden, z. B. alle mousemove-Ereignisse innerhalb eines getaggten Controllers ignorieren (ein häufiges Problem beim Erstellen selbstgemachter Dropdown-Elemente).
Natürlich gibt es eine ähnliche Möglichkeit, dies mit jQuery zu tun, aber die Verwendung von prototype.js hält Ihr Bewusstsein näher am zugrundeliegenden DOM, der „Alles-ist-ein-Objekt“-Politik und der Prototype-Erweiterung anstelle des Klassen-/Methoden-Denkens.
Ein weiteres Thema, das man sich merken sollte
Globale Event-Handler können zu Leistungsproblemen führen. Wenn Sie viele Event-Handler für einzelne Elemente registrieren, kümmert sich die JS-Engine intern um das Dispatching (was schnell ist). Wenn Sie das Dispatching selbst übernehmen, kann es für die Engine schwieriger sein, zu optimieren.
Zusätzlich feuert ein globaler Event-Handler bei vielen Elementen, die Sie gar nicht behandeln möchten.
Wenn möglich, gehen Sie wie folgt vor
Registrieren Sie Event-Handler für jedes einzelne Element, das Sie behandeln möchten. Verwenden Sie keine Inline-(Lambda)-Funktionen; Erstellen Sie stattdessen allgemeine Funktionen und referenzieren Sie sie in Ihrem Event-Registrierungscode. Dieses Caching von Funktionen mag mit modernen JS-Engines obsolet sein, aber es ist definitiv schneller mit älteren Browsern.
Warum nicht einfach so?
Weil das
<a>möglicherweise nicht derparentNodeist, es könnte tiefer liegen.Die
while-Schleife sollte sich darum kümmern.Oh richtig! Das habe ich gar nicht gesehen. Dumm. Sieht so aus, als ob das gut funktionieren würde. Gibt es einen Grund für parentNode im Vergleich zu parentElement? Ich denke, in diesem Fall ist es nicht wichtig.
Weil „früher, als wir noch kein schnelles
parentElementhatten, hatten wir nurparentNode!“ haha**Ich habe in meinem JavaScript-Code so etwas
und es funktioniert ziemlich gut für mich! ;)
Sie können auch das angeklickte Element überprüfen, indem Sie
event.currentTargetverwendenNun, ein wenig im Zusammenhang damit, wenn man ein SVG in einem Anker verwendet, wenn der Link bereits besucht wurde, funktioniert die Übergang in **Webkit**-basierten Browsern nicht. Hat jemand das gleiche Problem oder kennt einen Workaround?
Zum Testen: http://jsfiddle.net/eEfjS/
Ich habe festgestellt, dass das Anwenden von CSS-Übergängen auf SVG generell fehlerhaft sein kann, aber dieser spezielle Auslöser ist mir neu.
Ich frage mich, ob das, was Sie erleben, damit zusammenhängen könnte?
developer.mozilla.org/en-US/docs/Web/CSS/Privacy_and_the_:visited_selector
Gecko und Webkit könnten diese Sicherheitsbedenken unterschiedlich behandeln.
Sie haben es letzte Woche behoben! https://code.google.com/p/chromium/issues/detail?id=314892 Wird in Chrome 37 verfügbar sein
Wie Jeremy T sagte, wenn Sie jQuery verwenden, bin ich ziemlich sicher, dass Sie einfach $(event.target).closest(„a“) verwenden könnten, um immer das Link-Anker-Element auszuwählen. Es mit einem pseudo-CSS zu maskieren, erscheint mir etwas hackelig, aber es ist sehr clever.
Wenn Sie bereit sind, 210 Bytes JavaScript zu opfern, polyfillen Sie
matchesund prollyfillen Sieclosest.Und tun Sie, was Sie wollen.
^ auf GitHub gepostet. https://github.com/jonathantneal/closest
Damit das
::after-Pseudo-Element funktioniert, musste ichposition: absolutehinzufügen. Außerdem musste ich, da ich es auf einen Button mit abgerundeten Ecken anwende, auch die Ecken des Pseudo-Elements abrunden, um richtige Klick- & Hover-Bereiche zu erhalten.Außerdem möchte ich nur hinzufügen, dass ich langsam vermute, dass Chris tatsächlich ein Jedi-Meister ist. Ich stieß am 1. Mai auf dieses Problem, und unmittelbar nach Star Wars Tag (4. Mai) postete er die Lösung. :D