'How to turn a <circle> (that's inside an svg) into a checkbox?

PART 1 - So I have a bunch of <circle>'s inside a SVG, and I want those circles to be checkboxes. And after that I want to:

PART 2 - When circle 1 (which is now a checkbox) is clicked, then it is checked. But all the other circles now get unchecked.

This is what I've already tried:

PART 1 - Turning the SVG into a checkbox:

<circle opacity="0.5" cx="842" cy="451.814" r="25.582" class="svg_spot" id="1" fill="#FFB60C" >
        <animate attributeName="r" values="25.582; 33; 25.582" keyTimes="0; 0.5; 1" begin="0s"  dur="1s" repeatCount="indefinite" calcMode="linear" />
        <input type="checkbox" id="spot1" name="spot" class="common_selector spot_id" value="spot1">   
</circle>

PART 2 -

$('input[name=spot]').click (function (){
        $(this).attr('checked', true);
        $('input[name=spot]').not(this).attr('checked', false);
});

Thanks for your time guys. Would appreciate any help!



Solution 1:[1]

It is possible to get this working without any Javascript.

How this works:

  • Put the SVG in the label of the input. Clicking on the SVG (ie the label) will thus activate the field.
  • Make the actual <input> field invisible, so that all you see is the label.
  • Style the SVG based on whether the field is selected, by using the :checked pseudo-selector.

// just here to prove that the form value is changing
// prints value of spot field when inputs change
document.querySelectorAll("#spot1, #spot2, #spot3")
        .forEach((elem) => elem.addEventListener("change", (evt) => console.log(document.myform.spot.value)));
.common_selector {
  position: absolute;
  opacity: 0;
}

.common_selector + label svg {
  width: 50px;
  height: 50px;
  fill: red;
}

.common_selector:checked + label svg {
  fill: green;
}
<form name="myform">

<input type="radio" id="spot1" name="spot" class="common_selector spot_id" value="spot1">
<label for="spot1">
  <svg viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="50"/>
  </svg>
</label>

<input type="radio" id="spot2" name="spot" class="common_selector spot_id" value="spot2">
<label for="spot2">
  <svg viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="50"/>
  </svg>
</label>

<input type="radio" id="spot3" name="spot" class="common_selector spot_id" value="spot3">
<label for="spot3">
  <svg viewBox="0 0 100 100">
    <circle cx="50" cy="50" r="50"/>
  </svg>
</label>

</form>

Solution 2:[2]

<input> is not a valid SVG element - it is a HTML element, so this won't work. You can either:

  1. wrap an input element inside a <foreignObject> element and do it that way, or
  2. you could use positioning to place the input element over the circle. But fair warning - form elements haven't always played well when they're positioned over other types of elements. Or
  3. You can manually draw SVG that looks like an input element and use JavaScript to make it behave like one. or
  4. Since you just need a circle, why not wrap the input element in a Div with an appropriate border radius and make a circle that way.

Solution 3:[3]

I hope I understand your question. You can not change an svg element into an input however you can try to mimic one.

// selects all the circles with a class of radio
let inputs = document.querySelectorAll(".radio")
// for every circle
inputs.forEach(i =>{
  //when the circle is clicked
  i.addEventListener("click", ()=>{
   // remove the class checked from all the circles but the clicked one 
   inputs.forEach(j =>{if(i!==j)j.classList.remove("checked") })
   // toggle the class checked on the clicked one 
   i.classList.toggle("checked")
})
})
svg{border:1px solid}
.checked{fill:red}
<svg id="theSVG" viewBox="800 410 300 85" width="300">
<circle class="radio" opacity="0.5" cx="842" cy="451.814" r="25.582"  fill="#FFB60C"  stroke="#FFB60C" stroke-width="10" />   

  
<circle class="radio"  opacity="0.5" cx="950" cy="451.814" r="25.582"  fill="#FFB60C"  stroke="#FFB60C" stroke-width="10" />   
  
<circle class="radio"  opacity="0.5" cx="1050" cy="451.814" r="25.582"  fill="#FFB60C"  stroke="#FFB60C" stroke-width="10" /> 

</svg>

Solution 4:[4]

PART 1: Use <foreignObect> to display any HTML element inside an SVG:

<foreignObject x="20" y="20" width="100" height="100">
   <input type="checkbox" id="spot1" name="spot" class="common_selector spot_id" 
   value="spot1">
</foreignObject>

Then you can use css to hide default styling of this input field and position your circle over it. You can read about it here: https://www.w3schools.com/howto/howto_css_custom_checkbox.asp

PART 2: Use Radio Buttons instead of Checkboxes. Checkboxes allow more than one selection. Radio buttons are what you need here. Read about it here: https://www.w3schools.com/tags/att_input_type_radio.asp

Solution 5:[5]

It sounds like you want to use an SVG as a type of "selector" like a color selector or, in my case a planet selector. Here I have a SVG representing the solar system where the user clicks on a planet to select it. Since it sounds like you only want to select a single item then a radio button is the better option, however the method below should work with either.

As others have suggested the foreignObject element can be used. I had issues getting it work without specifically declaring a prefixed namespaces for the svg and saving the file with a .xhtml extension.

Of course there are plenty of ways to do something similar with javaScript, however a big advantage of using an underlying radio button is that it takes care of all the logic and makes validation easier.

I used an empty div over the circle that is acting as a label for the radio button. Since clicking on the label activates the radio button the circle now functions as a radio button!

The SVG does not show up in the snippet, so the div's are shaded for visibility. The actual radio buttons can be hidden if desired.

div{
border: red solid 1px;
background-color: black;
opacity: 20%;
}
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">

<p>Select a planet</p>

<form name="planetData">

    <input id="venus" name="planet" type="radio" value="venus" />
    <label for="venus">Venus</label>

    <input id="earth" name="planet" type="radio" value="earth" />
    <label for="venus">Earth</label>

    <input id="mars" name="planet" type="radio" value="mars" />
    <label for="venus">Mars</label>

</form>

<svg:svg height="100" width="100" viewbox="0 0 100 100">

    <svg:circle cx="0" cy="50" r="10" fill="yellow"/>
    <svg:circle cx="30" cy="75" r="10" fill="green"/>
    <svg:circle cx="60" cy="30" r="10" fill="blue"/>
    <svg:circle cx="90" cy="60" r="10" fill="red"/>

    <svg:foreignObject x="20px" y="65px" height="20px" width="20px">
        <label for="venus">
            <div style="height: 30px; width: 30px;">
                
            </div>
        </label>
    </svg:foreignObject>

    <svg:foreignObject x="50" y="20" height="20" width="20">
        <label for="earth">
            <div style="height:20px; width: 20px;"></div>
        </label>
    </svg:foreignObject>

    <svg:foreignObject x="80" y="50" height="20" width="20">
        <label for="mars">
            <div style="height:40px; width: 40px;"></div>
        </label>
    </svg:foreignObject>

</svg:svg>


</html>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Paul LeBeau
Solution 2 Michael Mullany
Solution 3
Solution 4 Moosa Saadat
Solution 5 nomad