Theodore John
Theodore John

Reputation: 123

SVG Path On Click Event

Referring to the code-snipplet below about getting data with on click on an svg path here, for my example which is displaying Number 2, I would like it to have the alert button pop up when the cursor is clicked only when close to number 2. My code below is wrong because when cursor is clicked anywhere which is far way from the Number 2, the pop up alert box is still shown. I will really appreciate any help I can get :)

function getKey(button) {
 let key = button.querySelectorAll('path')[0].dataset.key;
 alert('key is ' + key)
}
<div onClick="getKey(this)">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600 " id="svg1">
  <g enable-background="new">
    <path data-key="12345" transform="matrix(1,0,0,-1,0,600)" stroke-width=".74" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 224.34 585.57 L 224.34 586.6 L 225.22 588.68 L 226.1 589.71 L 227.87 590.75 L 231.39 590.75 L 233.15 589.71 L 234.04 588.68 L 234.92 586.6 L 234.92 584.53 L 234.04 582.46 L 232.27 579.35 L 223.46 568.98 L 235.8 568.98 "/>
  </g>
</svg>
</div>

Upvotes: 1

Views: 4968

Answers (1)

Dave Pritlove
Dave Pritlove

Reputation: 2687

this in an element onclick attribute refers to the element

By setting an onclick attribute to the div containing the svg, a click anywhere within the div will call the function, and if this is sent to the function as an argument, it will always refer to the div element.

Once fired, your function is extracting the data attribute of the first path tag inside the div.

Instead, an eventListener can be attached to the path elements directly. When fired an event is created, which has a target property containing a reference to the tag that generated the event. It is that target element from which you need to extract the data attribute.

See: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener

This working snippet demonstrates attaching event listeners to your paths. (I have had to change the line width of your path as the click must hit the line for the event to be triggered, see end note)

let paths = document.querySelectorAll('path');
// paths is an html collection of all paths;
// attach event listeners to each path;
for (let i=0; i<paths.length; i++) {
  paths[i].addEventListener('click', event => alert(event.target.dataset.key));
} 
<div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600 " id="svg1">
  <g enable-background="new">
    <path data-key="12345" transform="matrix(1,0,0,-1,0,600)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 224.34 585.57 L 224.34 586.6 L 225.22 588.68 L 226.1 589.71 L 227.87 590.75 L 231.39 590.75 L 233.15 589.71 L 234.04 588.68 L 234.92 586.6 L 234.92 584.53 L 234.04 582.46 L 232.27 579.35 L 223.46 568.98 L 235.8 568.98 "/>
  </g>
</svg>
</div>

If you have many paths, the above process may not be memory-efficient because the function is duplicated for each of the paths. Instead, a single handler function can be used and called from each of the event listeners. Like this:

let paths = document.querySelectorAll('path');
// paths is an html collection of all paths;
// attach event listeners to each path;
for (let i=0; i<paths.length; i++) {
  paths[i].addEventListener('click', displayAlert);
} 

function displayAlert(event) {
  alert(event.target.dataset.key)
}
<div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600 " id="svg1">
  <g enable-background="new">
    <path data-key="12345" transform="matrix(1,0,0,-1,0,600)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 224.34 585.57 L 224.34 586.6 L 225.22 588.68 L 226.1 589.71 L 227.87 590.75 L 231.39 590.75 L 233.15 589.71 L 234.04 588.68 L 234.92 586.6 L 234.92 584.53 L 234.04 582.46 L 232.27 579.35 L 223.46 568.98 L 235.8 568.98 "/>
  </g>
</svg>
</div>
Note that the displayAlert function is referenced within the event listener without parentheses or argument. The event is passed automatically to the named external function (the declaration for which should include an argument if the automatically passed event is to be used inside the function). In practice, you are unlikely to have memory problems with such a trivial function but I've included this for completeness.

Alternatively, you can attach a single event listener to the div, and check that the target of the event is a path before displaying the data. This works because, although the event is attached to the div, the target of the event is set to whichever descendant of the div was clicked:

let div = document.getElementById('svg-container');

div.addEventListener('click', event => {
  if (event.target.tagName == 'path') alert(event.target.dataset.key)
});
 
<div id="svg-container">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="800pt" height="600pt" viewBox="0 0 800 600 " id="svg1">
  <g enable-background="new">
    <path data-key="12345" transform="matrix(1,0,0,-1,0,600)" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="none" stroke="#000000" d="M 224.34 585.57 L 224.34 586.6 L 225.22 588.68 L 226.1 589.71 L 227.87 590.75 L 231.39 590.75 L 233.15 589.71 L 234.04 588.68 L 234.92 586.6 L 234.92 584.53 L 234.04 582.46 L 232.27 579.35 L 223.46 568.98 L 235.8 568.98 "/>
  </g>
</svg>
</div>

note

If you have to use narrow strokes for your paths, it may be difficult for a user to place the cursor exactly on the line and so clicks may be missed. One way to mitigate this is to duplicate the paths, set the stroke of the the lower (first) one of each pair much wider and make it invisible by using the background colour. In the case of characters like your number 2, it might be simpler to draw an appropriately sized invisible rectangle behind each character and attach event listener to them instead, or as well as, the character paths (remembering to include the data attribute).

Upvotes: 4

Related Questions