42

I've been working with an HTML5 document with inline SVG and javascript animation.

I'd like to have a box pop up when the user clicks anywhere, and I'd like the box to go away when the user clicks somewhere that isn't the box. This means I can't use $(window).click(), which works.

I've tried selecting the SVGs on top by giving them class names and using $(".svgclassname").click(), but this doesn't seem to work. Neither does selecting individual ones with $("#svgname").click().

What is the problem?

(When I replace $(".eyesvg") with $(window), a blue box appears near the cursor when the user clicks anywhere in the window.)

Dylan Valade
  • 5,565
  • 6
  • 42
  • 56
Will
  • 5,654
  • 2
  • 23
  • 26

4 Answers4

70

This happens because SVG DOM spec differs a lot from HTML DOM.

SVG DOM is a different dialect, and some properties have same names but mean different things. For example, to get the className of an svg element, you use:

svg.className.baseVal

The properites affected by this are

className is SVGAnimatedString
height,width, x, y, offsetWidth, offsetHeight are SVGAnimatedLength

These Animated properties are structs, with baseVal holding the same value you'd find in HTML DOM and animatedVal holding I am not sure what.

SVG DOM is also missing some properties libraries depend on, such as innerHTML.

This breaks jQuery in many ways, anything that depends on above properties fails.

In general, SVG DOM and HTML DOM do not mix very well. They work together just enough to lure you in, and then things break quietly, and another angel loses its wings.

I wrote a little jQuery extension that wraps SVG elements to make them look more like HTML DOM

(function (jQuery){
    function svgWrapper(el) {
        this._svgEl = el;
        this.__proto__ = el;
        Object.defineProperty(this, "className", {
            get:  function(){ return this._svgEl.className.baseVal; },
            set: function(value){    this._svgEl.className.baseVal = value; }
        });
        Object.defineProperty(this, "width", {
            get:  function(){ return this._svgEl.width.baseVal.value; },
            set: function(value){    this._svgEl.width.baseVal.value = value; }
        });
        Object.defineProperty(this, "height", {
            get:  function(){ return this._svgEl.height.baseVal.value; },
            set: function(value){    this._svgEl.height.baseVal.value = value; }
        });
        Object.defineProperty(this, "x", {
            get:  function(){ return this._svgEl.x.baseVal.value; },
            set: function(value){    this._svgEl.x.baseVal.value = value; }
        });
        Object.defineProperty(this, "y", {
            get:  function(){ return this._svgEl.y.baseVal.value; },
            set: function(value){    this._svgEl.y.baseVal.value = value; }
        });
        Object.defineProperty(this, "offsetWidth", {
            get:  function(){ return this._svgEl.width.baseVal.value; },
            set: function(value){    this._svgEl.width.baseVal.value = value; }
        });
        Object.defineProperty(this, "offsetHeight", {
            get:  function(){ return this._svgEl.height.baseVal.value; },
            set: function(value){    this._svgEl.height.baseVal.value = value; }
        });
    };

    jQuery.fn.wrapSvg = function() {
        return this.map(function(i, el) {
            if (el.namespaceURI == "http://www.w3.org/2000/svg" && !('_svgEl' in el))
                return new svgWrapper(el);
            else
                return el;
            });
        };
})(window.jQuery);

It creates a wrapper around SVG objects that makes them look like HTML DOM to jQuery. I've used it with jQuery-UI to make my SVG elements droppable.

The lack of DOM interoperability between HTML and SVG is a total disaster. All the sweet utility libraries written for HTML have to be reinvented for SVG.

Khalid
  • 4,730
  • 5
  • 27
  • 50
Aleksandar Totic
  • 2,557
  • 25
  • 27
  • 7
    Hey there Aleksander, thanks for this suggestion! The snippet got us pretty far, but didn't work in Firefox 15 due to an error raised when using prototypal inheritance directly with an SVG DOM element. We've modified it to work in Firefox and are using it successfully. Code is here: http://github.com/RedBrainLabs/jquery.wrap-svg – Tim Harper Sep 07 '12 at 21:18
  • Just loading the script in Safari 8.0 I got an error: "SyntaxError: Function statements must have a name." (svg.js, line 1). I had put the script in a separate js file, svg.js, and tried loading it before and after the jQuery file. Same error in both cases. – Andy Swift Jul 28 '14 at 11:35
  • This looks great - but how is it supposed to be used ? – kris Jul 05 '17 at 01:38
5

u can use jquery-svg plugin, like a charm:

<script>
    //get svg object, like a jquery object
    var svg = $("#cars").getSVG();
    //use jquery functions to do some thing
    svg.find("g path:first-child()").attr('fill', color);
</script>
4

sometimes I don't get it... but actually it doesn't work with the class-selector. If you use the id $("#mysvg") or the element $("svg") it does work! Strange....

And it only works when you move the onClick script from the header to the body after the svg element! jquery can only bind the onclick when the element is declared before the binding.

räph
  • 3,634
  • 9
  • 34
  • 41
  • Aha! That works fabulously. Despite jQuery's problem with the class-selector, I'm sure I'll just be able to select multiple ids. Thanks! – Will Jul 21 '10 at 14:55
  • 11
    Note that you can select SVG elements by class by using `$('*[class~="eyesvg"]')` (the [attribute contains word selector](http://api.jquery.com/attribute-contains-word-selector/)). – Phrogz May 03 '11 at 20:26
-1

You have to use CSS-path from div to SVG element to click on the object as per below example:

$('div#layout > svg > #line').click()
Jonas m
  • 2,646
  • 3
  • 22
  • 43
ElRolla
  • 1
  • 1