3
votes

I'm working with a popover on all circles in my SVG file. Everything seems to work fine at first, if you click on a cirlce popover then it appears where I want it to appear. If you scroll when te popover is already open then it follows the circle like it should and stays in the right place. But when the popover is hidden and you click it after you scrolled down it places my popover way too high up.

Here's a screenshot how it looks if we click the circle without scrolling:

Popover without scroll

Here's a screenshot how the popover positions itself when we click it after we scrolled down a little: Popover with scroll

My SVG file looks like this (left some layers out so it doesn't get unnecessary long):

<svg id="svg3336" xmlns="http://www.w3.org/2000/svg" height="542" viewBox="0 0 800 542" width="800" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"  >
<g id="zeemanGroep" fill="#d40000" class="bluetooth">
        <circle id="zeeman" r="0" cy="95.064" cx="111.3" fill-opacity="0.5" />
        <text id="zeemantext" y="95.064" x="111.3" text-anchor="middle" stroke="#000000" stroke-width="2px" dy=".3em" ></text>
</g>

<g id="mediamarktGroep" fill="#d40000" class="jpurse" >
    <circle id="mediamarkt" r="0" cy="383.65" cx="568.57" fill-opacity="0.5"/>
    <text id="mediamarkttext" y="383.65" x="568.57" text-anchor="middle" stroke="#51c5cf" stroke-width="2px" dy=".3em"></text>
</g>


<g id="blokkerGroep" fill="#d40000" class="wifi" >
    <circle id="blokker" r="0" cy="135.8" cx="542.18" fill-opacity="0.5"/>
    <text id="blokkertext" y="135.8" x="542.18" text-anchor="middle" stroke="#51c5cf" stroke-width="2px" dy=".3em"></text>
</g>
<g id="popupGroep" fill="#d40000" class="wifi" >
    <circle id="popup" r="0" cy="106.54" cx="689.62" fill-opacity="0.5"  />
    <text id="popuptext" y="106.54" x="689.62" text-anchor="middle" stroke="#51c5cf" stroke-width="2px" dy=".3em"></text>
</g>

The svg gets loaded in a div inside my html with the help of d3. I also use this function to place a popover on all the groups that have a circle inside of them.

Here's a part of the javascript code:

d3.xml("images/FloorplanScanners.svg", function (error, xml) {
if (error)
    throw error;
var importedNode = document.importNode(xml.documentElement, true);
var clone = importedNode.cloneNode(true);
document.querySelector("svg").appendChild(clone);

var circles = document.getElementsByTagName("circle");
console.log(circles);
for (i = 0; i < circles.length; i++)
{
    var circle = circles[i];
    var group = circle.parentElement;
    var titel = circle.id;
    var content = berekenPopovercontent(circle);
    d3.select(group).each(function(){
    $(this).popover({title:titel, content: content, container:"body", html:true});  
});
}});

The last two lines in my javascript are the ones that place the popover.

Where's it going wrong, I have a feeling it has something to do with the "container:'body'" part. But without this it's not possible to put this popover on my svg elements.

Edit: When scrolling to the right the same thing happens but now the popover places itself too much to the left.

3

3 Answers

3
votes

I ran into the same problem with tooltips on SVG elements when the page is scrolled. Turns out, the problem has been raised in bootstrap 3.3.7 as issue 20381. The change happened in bootstrap 3.3.7. Rolling back to 3.3.6 in my project made the tooltips render in the proper position again.

Essentially the change done to resolve 20280 doesn't make any position adjustments for SVG elements. Since it looks like bootstrap maintainers won't merge any pull requests to 3.x, I'm using a small override file so I can still import bootstrap into my project via npm/yarn/webpack:

import jQuery from 'jquery/dist/jquery'

/*
 * Override the bootstrap 3.3.7 Tooltip.getPosition() function to fix
 * position issues with SVG elements when the page/viewport is scrolled.
 */
;(function ($) {
  const extension = {
    getPosition: function ($element) {
      $element = $element || this.$element

      var el = $element[0]
      var isBody = el.tagName === 'BODY'

      var elRect = el.getBoundingClientRect()
      if (elRect.width == null) {
        // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
        elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
      }

      var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
      var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null

      /*
       * Avoid using $.offset() since it gives incorrect results on SVG elements. Directly compute scroll
       * adjusted position like $.offset() and angular-ui-bootstrap's $position.offset() does.
       *
       * See issues: https://github.com/twbs/bootstrap/issues/20280
       *             https://github.com/twbs/bootstrap/issues/20381
       *             https://github.com/twbs/bootstrap/issues/21855
       */
      var elOffset = isBody
        ? { top: 0, left: 0 }
        : { top: elRect.top + (window.pageYOffset || document.documentElement.scrollTop),
            left: elRect.left + (window.pageXOffset || document.documentElement.scrollLeft) }

      return $.extend({}, elRect, scroll, outerDims, elOffset)
    }
  }

  $.extend(true, $.fn.tooltip.Constructor.prototype, extension)
}(jQuery))

It's also available as a gist.

2
votes

I had the same issue with Bootstrap and SVG, after scroll the position was not refreshed. There is a line in bootstrap.js which avoids offset when it is an SVG element. I've commented the parts out and it is now working well.

This is the part you have to modify: Original:

var isSvg = window.SVGElement && el instanceof window.SVGElement

Modified, always false:

var isSvg = false;//window.SVGElement && el instanceof window.SVGElement

Bootstrap v3.3.7 jQuery v2.1.3

Hope this helps!

0
votes

I'm not able to reproduce your problem with Bootstrap's popover.

I guess your tooltip ignores the window.scrollX and/or window.scrollY offset when the page is scrolled. But I'm not able to reproduce it. In my case it doesn't matter what I specify as container. (Tested in Chrome, Firefox, and Safari)

I made a little JSFiddle you can find here.

Perhaps you can spot something there. If this doesn't help, maybe you can build your own tooltip. Here's another small JSFiddle without Bootstrap, but simple tooltips.