Jquery thinks my <g> element is visible, even when display is set to none and/or visibility is set to hidden

Viewed 133

Please take a look at the example I've created below. When you click the button, the <g> is toggled and the green dot gets displayed or hidden.

Oddly though, the is(':visible') is ALWAYS returning true, even when the visibility is set to hidden.

Why is this happening?

console.log('Jquery running...')

$("button").click( () => {
  var prev = $("g").attr('visibility');
  if (prev === 'hidden') {
$('g').attr('visibility', 'visible');
  } else {
$('g').attr('visibility', 'hidden');
  }

  console.log('element is visible:', el.is(':visible'));
});

var el = $('g');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>My Example</p>

<svg width="20" height="30">

<g>
<circle cx="10" cy="10" r="10" fill="green"></circle>
</g>
</svg>

<button> Toggle Dot </button>

UPDATE

I also tried setting display to none earlier and it didn't work either

2 Answers

There are two separate cases that warrant attention:


visibility: hidden

Elements with visibility: hidden are, according to the JQuery documentation, still considered visible by the :visible selector:

Elements with visibility: hidden or opacity: 0 are considered visible, since they still consume space in the layout.


display: none

Elements with display: none don't consume space in the layout, so theoretically should not be selected by the :visible selector. When we look at the JQuery code used to determine whether an element is visible or not, it bases itself on three values:

jQuery.expr.pseudos.visible = function( elem ) { 
        return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
};

At least in Chrome, offsetWidth and offsetHeight are undefined for SVG elements.

That leaves us with the client rects, and this is where the problem seems to lie, as there is still a client rect associated with the g element that has its display set to none:

console.log('Jquery running...')

$('button').click(() => {
  var prev = $('g').css('display');
  if (prev === 'none') {
    $('g').css('display', 'block');
  } else {
    $('g').css('display', 'none');
  }
  
  console.log(el[0].getClientRects().length); // 1
})

var el = $('g');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>My Example</p>

<svg width="20" height="30">

<g>
<circle cx="10" cy="10" r="10" fill="green"></circle>
</g>
</svg>

<button> Toggle Dot </button>


Looks like a bug in Chrome, because it works correctly in Firefox. There does, however, seem to be confusion about what getClientRects() needs to return in case of inner SVG elements.

Toggling visibility of the circle works

Jquery source code:

jQuery.expr.pseudos.visible = function( elem ) {
    return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
};

Here offsetWidth, offsetHeight, getClientRects() returns null for g element

console.log('Jquery running...')

$("button").click(() => {
  el.toggle();

  console.log('element is visible:', el.is(':visible'));
});

var el = $('g circle');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<p>My Example</p>

<svg width="20" height="30">

<g>
<circle cx="10" cy="10" r="10" fill="green"></circle>
</g>
</svg>

<button> Toggle Dot </button>

Related