jQuery 引导下拉菜单根据屏幕位置自动下拉?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/21232685/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 01:30:01  来源:igfitidea点击:

bootstrap drop-down menu auto dropup according to screen position?

jquerycsstwitter-bootstraptwitter-bootstrap-3

提问by scooterlord

I was wondering if any of you has what I am asking for ready, to save me from the trouble. What I am looking for is for a dropdown menu to have the dropup class automatically added according to its position on the screen - and also change automatically when the user scrolls or resizes window.

我想知道你们中是否有人准备好了我要的东西,以免我遇到麻烦。我正在寻找的是一个下拉菜单,根据其在屏幕上的位置自动添加下拉类 - 并且在用户滚动或调整窗口大小时自动更改。

What I am looking for is already implemented in the bootstrap-select plugin, but the plugin is too 'heavy' to use.

我正在寻找的东西已经在 bootstrap-select 插件中实现了,但是这个插件太“重”了,无法使用。

回答by Zoltán Tamási

A bit late, but because is quite different from others, here is my solution for bootstrap 3. It applies globally to all dropdowns on the page, also those created or loaded dynamically.

有点晚了,但因为与其他人完全不同,这里是我的 bootstrap 3 解决方案。它全局适用于页面上的所有下拉菜单,也适用于动态创建或加载的下拉菜单。

Note that I had to use the shown.bs.dropdownevent because the dimensions are ready only at that point.

请注意,我必须使用该shown.bs.dropdown事件,因为维度仅在那时准备就绪。

$(document).on("shown.bs.dropdown", ".dropdown", function () {
    // calculate the required sizes, spaces
    var $ul = $(this).children(".dropdown-menu");
    var $button = $(this).children(".dropdown-toggle");
    var ulOffset = $ul.offset();
    // how much space would be left on the top if the dropdown opened that direction
    var spaceUp = (ulOffset.top - $button.height() - $ul.height()) - $(window).scrollTop();
    // how much space is left at the bottom
    var spaceDown = $(window).scrollTop() + $(window).height() - (ulOffset.top + $ul.height());
    // switch to dropup only if there is no space at the bottom AND there is space at the top, or there isn't either but it would be still better fit
    if (spaceDown < 0 && (spaceUp >= 0 || spaceUp > spaceDown))
      $(this).addClass("dropup");
}).on("hidden.bs.dropdown", ".dropdown", function() {
    // always reset after close
    $(this).removeClass("dropup");
});

One could easily improve it with @PeterWone's app heightwizardry if needed, currently this was working well enough for me.

如果需要,可以使用@PeterWone 的应用高度魔法轻松改进它,目前这对我来说已经足够好了。

UPDATE

更新

Here is a fiddle representing this solution: http://jsfiddle.net/3s2efe9u/

这是代表此解决方案的小提琴:http: //jsfiddle.net/3s2efe9u/

回答by nathan-m

I had performance issues with the provided answer on large pages.

我在大页面上提供的答案有性能问题。

I've "improved" it by using getBoundingClientRect(), and adding a new class to specific .btn-group's that contain dropdows., eg. btn-group-dropup.

我通过使用“改进”了它getBoundingClientRect(),并将一个新类添加到.btn-group包含 dropdows 的特定's 中。例如。btn-group-dropup.

Your mileage may vary.

你的旅费可能会改变。

(function() {
  // require menu height + margin, otherwise convert to drop-up
  var dropUpMarginBottom = 100;

  function dropUp() {
    var windowHeight = $(window).height();
    $(".btn-group-dropup").each(function() {
      var dropDownMenuHeight, 
          rect = this.getBoundingClientRect();
      // only toggle menu's that are visible on the current page
      if (rect.top > windowHeight) {
        return;
      }
      // if you know height of menu - set on parent, eg. `data-menu="100"`
      dropDownMenuHeight = $(this).data('menu');
      if (dropDownMenuHeight == null) {
        dropDownMenuHeight = $(this).children('.dropdown-menu').height();
      }
      $(this).toggleClass("dropup", ((windowHeight - rect.bottom) < (dropDownMenuHeight + dropUpMarginBottom)) && (rect.top > dropDownMenuHeight));
    });
  };

  // bind to load & scroll - but debounce scroll with `underscorejs`
  $(window).bind({
    "resize scroll touchstart touchmove mousewheel": _.debounce(dropUp, 100),
    "load": dropUp
  });

}).call(this);

回答by scooterlord

Edit 07 Jan 2019: tidied up the variable names and optimized the code just a bit.

2019 年 1 月 7 日编辑:整理了变量名称并稍微优化了代码。

dropup = function() {
  $(".dropdown-toggle").each(function(){ 
    offsetTop=$(this).offset().top+$(this).height()-$(window).scrollTop();           
    offsetBottom=$(window).height()-$(this).height()-$(this).offset().top+$(window).scrollTop();
    ulHeight=$(this).parents('.btn-group').find('ul').height();

    if ((offsetBottom < ulHeight) && (offsetTop > ulHeight)) {
      parent.addClass('dropup');
    } else {
      parent.removeClass('dropup');
    }
  });
} 

$(window).on('load resize scroll', dropup);

回答by user2407400

I know this is an old question, but it's high up on google results so here's another simple solution that worked for me. Adjust the 150in the ifstatement for however much space your dropdown will need.

我知道这是一个老问题,但它对谷歌结果的影响很大,所以这是另一个对我有用的简单解决方案。调整150if的但多少空间你的下拉列表将需要声明。

var dropUp = function() {
    var windowHeight = $(window).innerHeight();
    var pageScroll = $('body').scrollTop();

    $( ".your-dropdown" ).each( function() {
        var offset = $( this ).offset().top;
        var space = windowHeight - ( offset - pageScroll );

        if( space < 150 ) {
            $( this ).addClass( "dropup" );
        } else  {
            $( this ).removeClass( "dropup" );
        }
    });
}

$(window).load(dropUp);
$(window).bind('resize scroll mousewheel', dropUp);

回答by Peter Wone

Rather than diddling every menu every time something changes clipping bounds, why not take a JIT approach?

与其在每次更改裁剪边界时都重复使用每个菜单,为什么不采用 JIT 方法呢?

<body>
  <div id="viewport-proxy" style="visibility:hidden; 
    position:absolute; height:100%; width:100%; margin:0; padding:0; 
    top:0; left: 0; right:0; bottom:0;"></div>
...

var w = window, d = document, e = d.documentElement;
var g = d.getElementsByTagName('body')[0];
function appHeight() { 
  var a = w.innerHeight || e.clientHeight || g.clientHeight;
  var b = $("#viewport-proxy").height();
  return a < b ? a : b;
};

$(".dropdown").on('show.bs.dropdown', function () {
    var windowHeight = appHeight();
    var rect = this.getBoundingClientRect();
    if (rect.top > windowHeight) return;
    var menuHeight = $(this).children('.dropdown-menu').height();
    $(this).toggleClass("dropup", 
      ((windowHeight - rect.bottom) < menuHeight) && 
      (rect.top > menuHeight));
  });

Obviously you'll have to manage event bindings as menus come and go. In the Durandal app for which Nathan's answer morphed into the above, there is a context menu for each row representing a datum. I create the bindings on data load, keep them in the view-model and destroy them on deactivate. I don't need to recreate them on activate because that calls the data load routine, which in turn creates the bindings.

显然,随着菜单的出现和消失,您必须管理事件绑定。在 Nathan 的答案演变为上述内容的 Durandal 应用程序中,每一行都有一个上下文菜单,代表一个数据。我在数据加载时创建绑定,将它们保存在视图模型中并在停用时销毁它们。我不需要在激活时重新创建它们,因为这会调用数据加载例程,而后者又会创建绑定。

I also added this:

我还添加了这个:

$(window).on("scroll resize", function () { $(".dropdown.open").dropdown("toggle"); });

You put this in shell.js so it's loaded early and affects all views. If it's not already obvious, what this does is close any open dropup when the window scrolls or resizes. This conforms with Windows convention and makes it unnecessary to recompute positioning when resize or scroll events occur.

你把它放在 shell.js 中,所以它会提前加载并影响所有视图。如果这还不是很明显,那么它的作用是在窗口滚动或调整大小时关闭任何打开的下拉菜单。这符合 Windows 约定,并且在发生调整大小或滚动事件时无需重新计算定位。

Note the appHeight function. Even jQuery fails to accurately report window height on mobile phones, but appHeight works correctly on every platform and browser that I've tested so far (IE,Ch,FF desktop, IE,Ch,Sa mobile).

注意 appHeight 函数。甚至 jQuery 也无法准确报告手机上的窗口高度,但 appHeight 在我迄今为止测试过的每个平台和浏览器(IE、Ch、FF 桌面、IE、Ch、Sa 移动)上都能正常工作。

In its current incarnation it measures an absolutely positioned hidden div set to fill the viewport. This works on Android Chrome but not IE, whereas the other method shown in the code works with IE but not Android Chrome, so I use both and use the lower number.

在其当前的化身中,它测量一个绝对定位的隐藏 div 集以填充视口。这适用于 Android Chrome 但不适用于 IE,而代码中显示的另一种方法适用于 IE 但不适用于 Android Chrome,所以我同时使用两者并使用较低的数字。

In my Durandal app the function is implemented as a height method on the app object but for brevity and clarity I flattened the example code.

在我的 Durandal 应用程序中,该函数是作为应用程序对象上的高度方法实现的,但为了简洁和清晰,我将示例代码弄平了。

回答by Matthias Palmer

I found the answer from Zoltán Tamási to be a good starting point but not enough when the dropdown should appear within a div with a vertical scrollbar. I needed the height of that div (the scrollHeight variable in the code below) to figure out if the dropdown will take to much space at the bottom.

我发现 Zoltán Tamási 的答案是一个很好的起点,但当下拉列表出现在带有垂直滚动条的 div 中时还不够。我需要那个 div 的高度(下面代码中的 scrollHeight 变量)来确定下拉列表是否会在底部占用太多空间。

jquery(document).on("show.bs.dropdown", ".dropdown", function () {
  var $ul = jquery(this).children(".dropdown-menu");
  var menuHeight = $ul.height()
  var buttonHeight = jquery(this).children(".dropdown-toggle").height();
  var scrollHeight = $ul.parent().offsetParent().height();
  var menuAnchorTop = $ul.parent().position().top + buttonHeight;

  // how much space would be left at the top
  var spaceUp = (menuAnchorTop - buttonHeight - menuHeight);
  // how much space would be left at the bottom
  var spaceDown = scrollHeight - (menuAnchorTop + menuHeight);
  // Switch to dropup if more space there
  if (spaceDown < 0 && (spaceUp >= 0 || spaceUp > spaceDown))
    jquery(this).addClass("dropup");
}).on("hidden.bs.dropdown", ".dropdown", function() {
  // always reset after close
  jquery(this).removeClass("dropup");
});

回答by karthik Rajendran

I have checked the performance and browser support (chrome,Firefox,Edge,IE 10 and above).

我检查了性能和浏览器支持(chrome、Firefox、Edge、IE 10 及更高版本)。

I have covered both vertical and horizontal auto positioning.

我已经涵盖了垂直和水平自动定位。

Code:

代码:

(function () {
  var dd_obj = {
    jq_win :jQuery(window),
    jq_doc :jQuery(document),
  }
  function selfCalc(ele) {
    var $this = jQuery(ele),
        $dd_menu = $this.children(".dropdown-menu"), 
        ddOffset = $this.offset(),
        ddMenu_posTop = dd_obj.jq_win.outerHeight() - (($this.outerHeight() + ddOffset.top + $dd_menu.outerHeight()) - dd_obj.jq_doc.scrollTop());
        ddMenu_posRight = dd_obj.jq_win.outerWidth() - (($this.outerWidth() + ddOffset.left + $dd_menu.outerWidth()) - dd_obj.jq_doc.scrollLeft());

    (ddMenu_posTop <= 0) ? $this.addClass("dropup") : $this.removeClass("dropup"); 
    (ddMenu_posRight <= 0) ? $this.find('.dropdown-menu').addClass("dropdown-menu-right") : $this.find('.dropdown-menu').removeClass("dropdown-menu-right");
  }
  jQuery('body').on("shown.bs.dropdown", ".dropdown", function () {
    var self = this;
    selfCalc(self)
    dd_obj.jq_win.on('resize.custDd scroll.custDd mousewheel.custDd',function() {
      selfCalc(self)
    })
  }).on("hidden.bs.dropdown", ".dropdown", function() {
      // there is no need to keep the window event after dropdown has closed, 
      // still if we keep the event then there is no use
      dd_obj.jq_win.off('resize.custDd scroll.custDd mousewheel.custDd')
  });
})();