Reputation: 51
I have 4 SVG circles and I want to toggle their style on hover and on click. I only want one circle at a time to have the style when clicked, i.e the style is removed from one circle when another one is clicked. The hover works using CSS, but my JavaScript isn't quite correct.
It removes the style from one circle when another one is clicked but doesn't let you toggle the style class from the active target circle. Also how can I remove the style from a circle if anything outside the SVG circles is clicked?
var circles = document.querySelectorAll('.svgcircle')
circles = Array.prototype.slice.call(circles);
for (i = 0; i < circles.length; i++) {
(function(index) {
circles[index].addEventListener("click", function() {
var targetcircle = event.target;
for (var j = 0; j < circles.length; j++) {
circles[j].classList.remove("circleTarget");
}
targetcircle.classList.toggle("circleTarget");
})
})(i);
}
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
}
svg {
position: absolute;
top: 35%;
left: 50%;
margin-left: -200px;
padding: 10px;
}
svg circle {
fill: #B5EF8A;
cursor: pointer;
stroke: #56CBF9;
stroke-width: 2px;
}
svg circle:hover {
fill: #fff;
stroke: #56CBF9;
}
.circleTarget {
fill: #fff;
stroke: #56CBF9;
}
<svg height="100" width="400" id="svg">
<circle class="svgcircle" cx="50" cy="50" r="40" />
<circle class="svgcircle" cx="150" cy="50" r="40" />
<circle class="svgcircle" cx="250" cy="50" r="40" />
<circle class="svgcircle" cx="350" cy="50" r="40" />
</svg>
Many Thanks.
Upvotes: 1
Views: 451
Reputation: 4557
This will perform how you expect it to:
let circles = Array.from(document.querySelectorAll('.svgcircle'));
circles.forEach(circle => {
circle.addEventListener("click", ({target}) => {
circles.forEach(c => target !== c && c.classList.remove("circleTarget"));
target.classList.toggle("circleTarget");
})
});
// Remove class if anything else is clicked
document.body.addEventListener('click', ({target}) =>
!Array.from(target.classList).includes('svgcircle')
&& circles.forEach(c => c.classList.remove("circleTarget")));
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
}
svg {
position: absolute;
top: 35%;
left: 50%;
margin-left: -200px;
padding: 10px;
}
svg circle {
fill: #B5EF8A;
cursor: pointer;
stroke: #56CBF9;
stroke-width: 2px;
}
svg circle:hover {
fill: #fff;
stroke: #56CBF9;
}
.circleTarget {
fill: #fff;
stroke: #56CBF9;
}
<svg height="100" width="400" id="svg">
<circle class="svgcircle" cx="50" cy="50" r="40" />
<circle class="svgcircle" cx="150" cy="50" r="40" />
<circle class="svgcircle" cx="250" cy="50" r="40" />
<circle class="svgcircle" cx="350" cy="50" r="40" />
</svg>
Hope this helps,
Upvotes: 1
Reputation: 65806
Your code is clearing out the use of classes on all your circles and then toggling the class on the one that got clicked, but that will always lead to the clicked circle becoming active (because you just cleared out all the classes). You'll need to check to see if the circle being clicked was already active and act based on that.
By using event delegation, you can make the code much simpler. You won't have to set up event handlers on each circle and you can easily check for clicks that were not on circles. This is called event delegation.
Also, since you are converting your circles into an Array, use the Array.forEach()
method to loop, which is much simpler than managing loop indexes.
See comments inline:
var circles = Array.prototype.slice.call(document.querySelectorAll('.svgcircle'));
// Setting the event listener on the document, kills two birds
// with one stone. It removes the need to set click event handlers
// on each circle and it allows for easy checking to see if you
// clicked on anything other than a circle.
document.addEventListener("click", function(evt){
// Check to see if the clicked element was one of the circles:
if(evt.target.classList.contains("svgcircle")){
// It was, so capture whether the clicked circle is active already
let active = evt.target.classList.contains("circleTarget");
removeClass(); // Reset the class usage on all the circles
// If the clicked circle was active, deactivate it.
// Otherwise, activate it:
if(active){
evt.target.classList.remove("circleTarget");
} else {
evt.target.classList.add("circleTarget");
}
} else {
// It wasn't, so clear all the styling from all the circles
removeClass();
}
});
function removeClass(){
// Loop over all the circles and remove the target class
circles.forEach(function(cir){
cir.classList.remove("circleTarget");
});
}
html,
body {
height: 100%;
width: 100%;
overflow: hidden;
}
svg {
position: absolute;
top: 35%;
left: 50%;
margin-left: -200px;
padding: 10px;
}
svg circle {
fill: #B5EF8A;
cursor: pointer;
stroke: #56CBF9;
stroke-width: 2px;
}
svg circle:hover {
fill: #fff;
stroke: #56CBF9;
}
.circleTarget {
fill: #fff;
stroke: #56CBF9;
}
<svg height="100" width="400" id="svg">
<circle class="svgcircle" cx="50" cy="50" r="40" />
<circle class="svgcircle" cx="150" cy="50" r="40" />
<circle class="svgcircle" cx="250" cy="50" r="40" />
<circle class="svgcircle" cx="350" cy="50" r="40" />
</svg>
Upvotes: 2