如何确保 Twitter Bootstrap 弹出窗口可见?

17

有没有人知道Twitter Bootstrap的popover组件的一个扩展,可以动态地更改放置选项以确保弹出框显示在屏幕上?


可能是推特Bootstrap弹出框位置问题的重复。虽然不是完全相同,但足够相关。剩下的问题是数学问题。 - Steve Mitcham
2个回答

24
摆放位置可以是一个函数而不是一个字符串。关于自动布局的例子,由 fat 编写,然后由 wleeper 移植到最新版本的 Bootstrap中,在项目的一个 github 问题中可以找到: https://github.com/twitter/bootstrap/issues/345
这里是将 CoffeeScript 编译为 JavaScript 的结果:
$("a[rel=popover]").popover({
  placement: function(tip, element) {
    var $element, above, actualHeight, actualWidth, below, boundBottom, boundLeft, boundRight, boundTop, elementAbove, elementBelow, elementLeft, elementRight, isWithinBounds, left, pos, right;
    isWithinBounds = function(elementPosition) {
      return boundTop < elementPosition.top && boundLeft < elementPosition.left && boundRight > (elementPosition.left + actualWidth) && boundBottom > (elementPosition.top + actualHeight);
    };
    $element = $(element);
    pos = $.extend({}, $element.offset(), {
      width: element.offsetWidth,
      height: element.offsetHeight
    });
    actualWidth = 283;
    actualHeight = 117;
    boundTop = $(document).scrollTop();
    boundLeft = $(document).scrollLeft();
    boundRight = boundLeft + $(window).width();
    boundBottom = boundTop + $(window).height();
    elementAbove = {
      top: pos.top - actualHeight,
      left: pos.left + pos.width / 2 - actualWidth / 2
    };
    elementBelow = {
      top: pos.top + pos.height,
      left: pos.left + pos.width / 2 - actualWidth / 2
    };
    elementLeft = {
      top: pos.top + pos.height / 2 - actualHeight / 2,
      left: pos.left - actualWidth
    };
    elementRight = {
      top: pos.top + pos.height / 2 - actualHeight / 2,
      left: pos.left + pos.width
    };
    above = isWithinBounds(elementAbove);
    below = isWithinBounds(elementBelow);
    left = isWithinBounds(elementLeft);
    right = isWithinBounds(elementRight);
    if (above) {
      return "top";
    } else {
      if (below) {
        return "bottom";
      } else {
        if (left) {
          return "left";
        } else {
          if (right) {
            return "right";
          } else {
            return "right";
          }
        }
      }
    }
  }
});

对于我来说它运行良好,除了一个情况:如果该项位于右上角,则没有良好的弹出位置是其中之一的选项,它将部分显示在屏幕外。


4
这是一个好的回答,但并没有完全解决问题。提示框的宽度和高度在 actualWidthactualHeight 中被硬编码了,没有办法在渲染之前动态地获取提示框的大小。 - Björn Lindqvist
@BjörnLindqvist 这是一个很好的观点。我已经思考了一段时间,但似乎除了调整值以匹配您的弹出窗口(假设它是常见大小)之外,这似乎是一个难题。可以在隐藏的iframe中呈现它或进行其他一些恶作剧来确定大小,但我想不到一个干净的方法来解决这个问题。 - Cymen
如果您需要翻转/重新定位Twitter Bootstrap弹出框以确保其可见性,那么这是一个很好的解决方案。 - Ryan Charmley
@BjörnLindqvist 这是我目前遇到的问题。能够动态传入弹出窗口的实际宽度和高度将会很不错。 - kplus
1
这很不错,但它将覆盖默认的定位。稍微调整一下,您可以从元素中获取data-placement并优先使用它。 - Chris Haines
值得再次提到的是,最初编写此代码的人是为了作为示例。以一种能够满足所有人的通用方式解决这个问题是很困难的,其中涉及到权衡。幸运的是,您可以根据自己的需求编写函数来确定放置位置。 - Cymen

13

对于那些希望选择默认放置位置(使用元素上的data-placement属性)的人,我已经改编了Cymen的绝妙答案。

我还确保不会不必要地计算边界,因此它应该更加高效。

$('[data-toggle="popover"]').each(function() {
    var trigger = $(this);
    trigger.popover({
        animation: true,
        delay: { show: 0, hide: 0 },
        html: true,
        trigger: 'hover focus',
        placement: getPlacementFunction(trigger.attr("data-placement"), 283, 117)
    });
});

var getPlacementFunction = function (defaultPosition, width, height) {
    return function (tip, element) {
        var position, top, bottom, left, right;

        var $element = $(element);
        var boundTop = $(document).scrollTop();
        var boundLeft = $(document).scrollLeft();
        var boundRight = boundLeft + $(window).width();
        var boundBottom = boundTop + $(window).height();

        var pos = $.extend({}, $element.offset(), {
            width: element.offsetWidth,
            height: element.offsetHeight
        });

        var isWithinBounds = function (elPos) {
            return boundTop < elPos.top && boundLeft < elPos.left && boundRight > (elPos.left + width) && boundBottom > (elPos.top + height);
        };

        var testTop = function () {
            if (top === false) return false;
            top = isWithinBounds({
                top: pos.top - height,
                left: pos.left + pos.width / 2 - width / 2
            });
            return top ? "top" : false;
        };

        var testBottom = function () {
            if (bottom === false) return false;
            bottom = isWithinBounds({
                top: pos.top + pos.height,
                left: pos.left + pos.width / 2 - width / 2
            });
            return bottom ? "bottom" : false;
        };

        var testLeft = function () {
            if (left === false) return false;
            left = isWithinBounds({
                top: pos.top + pos.height / 2 - height / 2,
                left: pos.left - width
            });
            return left ? "left" : false;
        };

        var testRight = function () {
            if (right === false) return false;
            right = isWithinBounds({
                top: pos.top + pos.height / 2 - height / 2,
                left: pos.left + pos.width
            });
            return right ? "right" : false;
        };

        switch (defaultPosition) {
            case "top":
                if (position = testTop()) return position;
            case "bottom":
                if (position = testBottom()) return position;
            case "left":
                if (position = testLeft()) return position;
            case "right":
                if (position = testRight()) return position;
            default:
                if (position = testTop()) return position;
                if (position = testBottom()) return position;
                if (position = testLeft()) return position;
                if (position = testRight()) return position;
                return defaultPosition;
        }
    }
};

1
这非常出色,只需要最少的更新就能够很好地运行。谢谢! - JazzyWhit
无法确定,你需要尝试一下。 - Chris Haines

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接