bootstrap tooltip\popover - solving inconsistent placement to the left
Asked Answered
K

3

14

I've been working on a project and I've noticed some inconsistency in bootstrap's behavior that I would like to solve.

When a popover (or tooltip, whatever, they're basically the same) is nearing the edge of the screen - if it's a right-sided one, when nearing the edge - it will contract so as not to go offscreen (it only works up to a point, but that's usually enough).

This doesn't happen when the placement is to the left.

i.e.:

right placement:

Normal width:

enter image description here

Close to the edge:

enter image description here

left placement:

Normal width:

enter image description here

close to the edge:

enter image description here

These images are from a small DEMO I wrote to illustrate the problem.

I've messed around with the source code, so far to no avail. I can't seem to place my finger on what exactly causes this behavior in the first place.

Any ideas?

p.s.

I'm using Bootstrap 3.1.1. The new 3.2 does not solve the issue (and I would like to avoid upgrading at this point).


Major Update!

After some digging, I figured out that this has nothing to do with bootstrap - it's simple css - it seems that when you position an element absolutely and push it to the sides it will try and stay withing the screen.

I never knew about this behavior, and it happens automatically - but only to the the direction you're pushing - i.e. a div pushed from the left will contract when reaching the right edge of the screen and vice versa.

It just so happens that popovers are only positioned with the left assignment - which is why we're seeing the inconsistend behavior - when it's pushed to the right it contracts but not the other direction.

So the solution is to assign right instead - sounds simple? Not so much. I took the source code and manipulated it a bit, adding these lines (somewhere arond line 250 in the jsfiddle):

if (placement == 'left' && offset.left < 0) {
    var right = ( $(window).width() + 10 ) - ( offset.left + actualWidth ); 

    offset.left = 0;
    $tip.offset(offset);
    $tip.css({ 'right': right });
}

Seems reasonable, right? If the offset to the left is less than 0 (i.e., it goes offscreen) then calculate the window width and remove from that the left offset and the width of the popover (actualWidth) itself and you get the distance from the right.

Then, make sure to reset the offset left and apply the right positioning. But... it only sorta works - which is to say it only works the second time around.

Check it out for yourself! Hover once, and it's misplaced, pull the mouse to the side and try again and suddenly it's positioned correctly. What the hell?

edit

Ok this seems to come up a lot, so I'll make it clear:

I know about auto placement. I don't want it. I want to control where the popover goes, letting it decide automatically is not a solution to the problem, it's merely avoiding it

Kristiekristien answered 15/7, 2014 at 14:8 Comment(15)
I've added the css-tag. I'm begining to suspect it isn't related to bootstrap at all, but to the default behavior of the right-left css attributes...Kristiekristien
In fact - if I put right: ... on the popover (you know, instead of the usual left) it works just like the left assignment - i.e., contracting near the edges. What the hell is this phenomenon?Kristiekristien
Have you tried setting both right and left offsets?Vicarious
@PabloKarlsson No, but why would that help? The entire thing is built around calculating things from one direction only. If I wanted to add a right offset it would also entail making everything work with it. And it's needless, I only need to calculate things from one direction, really. It's just that I need to add a placement of right in certain cases (see my second demo)Kristiekristien
Is it not possible to set the right offset to none?Vicarious
Im just curious if it is the property on the object that causes the browser to behave differently. I have seen weirder stuff that that in css. So I just wanted to help.Vicarious
Btw could you not switch between left and right tool tip depending on if the tool tip is outside the screen or not? That would be really nice. :DVicarious
@PabloKarlsson I have no idea why setting right offset to none (or anything else for that matter) will have any effect. You're more than welcome to fork my demo and mess with it yourself if you have an idea how to solve the issue. I have no idea what you mean by "the property on the object that causes the browser...". The problem is cross-browser and relates to css (I've explained it pretty extensively). As par your last comment, check out my last edit.Kristiekristien
I will mess around a little.Vicarious
It appears that you've solved the question. Is there anything you still need help with, or are unclear about?Outcast
@AndyM No I haven't. It's almost there - it only works on the second hover (see my comment for Alexander below)Kristiekristien
@MarmiK I'm not using jQuery UI (and I don't intend to), I'm using bootstrap. It's in the title of the question, sheeshKristiekristien
@MarmiK please read the question. The link you provided has nothing to do with the issue I'm talking about (it relates to keeping the tooltip within the view when you scroll or move the mouse, I'm talking about inconsistent positioning near the edges of the screen).Kristiekristien
The issue exists within var actualHeight on line 157. If you look at this fiddle: jsfiddle.net/ctwheels/gwbR2/25 and you hover an element, you will see the actualHeight variable changes from first to second hover (from the value 134 to 486). If you hardcode the value 134 into your actualHeight, you will get the results you look for, however I assume you don't want to be hardcoding your variables.Legroom
To add to my previous comment, if you change the line var actualHeight = $tip[0].offsetHeight to var actualHeight = $(window).offsetHeight you will also get the result you're looking forLegroom
T
1

Ok, I've gotten a little closer.

Once you assign the right in css, the actualWidth and actualHeight will change, so you need to update those variables. Around line 253 in your jsfiddle:

if (placement == 'left' && offset.left < 0) {
    var right = (  $(window).width() + 10) - ( offset.left + actualWidth ); 

    offset.left = 0;
    $tip.offset(offset);
    $tip.css({ 'right': right });
    actualWidth  = $tip[0].offsetWidth;
    actualHeight = $tip[0].offsetHeight;
}

This works the first time you hover, but every time after that, it sets the top to be too high, so you can't read the top of the tooltip.

UPDATE: It appears that having the right value set is messing up the positioning in the applyPlacement function. To fix this, clear the right value before setting the initial actualWidth and actualHeight (around line 225):

$tip.css({ 'right': '' });
// check to see if placing tip in new offset caused the tip to resize itself
var actualWidth  = $tip[0].offsetWidth
var actualHeight = $tip[0].offsetHeight
Tisman answered 6/8, 2014 at 5:2 Comment(2)
YES! That's it! Thank you man. Final Demo. I didn't even need the updated fix because it seems to be working without it. Bounty will be awarded shortly.Kristiekristien
No. I take that back, the fix was necessary. Actually final DEMO. Fantastic answer.Kristiekristien
E
0

I believe this has a lot to do with the browser/client that accesses the webpage. For instance, in order to display the tip's on the proper side (not bunched up or illegible off the the left or right), determine the offsetLeft & offsetTop of the object element with javascript and place it accordingly. You could have different pages for different resolutions.

CSS example for a screen width from 400-720 pixels:

@media screen and (min-width:400px) and (max-width:721px)

some pseudo code:

if (this.offsetLeft < 200)   //if there's not enough room to display the tip
tip.offsetLeft += 200;
Equilibrant answered 5/8, 2014 at 17:41 Comment(0)
C
-1

I think you've basically got it, it's working fine for me.

Just add in the minimum width detection so that it doesn't go too small.

if (/bottom|top/.test(placement)) {
  var delta = 0

  if (offset.left < 0) {
    delta       = offset.left * -2
    offset.left = 0

    $tip.offset(offset)

    actualWidth  = $tip[0].offsetWidth
    actualHeight = $tip[0].offsetHeight
  } 

  this.replaceArrow(delta - width + actualWidth, actualWidth, 'left');

} else {
    if (placement == 'left' && offset.left < 0) {
        var right = (  $(window).width() + 10) - ( offset.left + actualWidth ); 

        offset.left = 0;
        $tip.offset(offset);
        $tip.css({ 'right': right });
    }

    this.replaceArrow(actualHeight - height, actualHeight, 'top');    
}
Cuspidate answered 31/7, 2014 at 20:32 Comment(2)
That's the code I wrote. And it's almost working. If you look closly at the demo I've provided, you'll see that it doesn't work on the first hover, but then it does the second time. It's hard to miss it because there are a lot of icons there, but take one that isn't completely to the side (take the fourth one from the left) and you'll see it (look closely at the arrow).Kristiekristien
Yes it changes from the first hover to the second. I'll take a look this afternoon.Outcast

© 2022 - 2024 — McMap. All rights reserved.