Выпадающее меню выпадающего меню самозапуска в соответствии с положением экрана?

Мне было интересно, есть ли у кого-то из вас то, о чем я прошу, чтобы спасти меня от этой проблемы. Я ищу для выпадающего меню, чтобы класс dropup автоматически добавлялся в соответствии с его положением на экране - а также автоматически менялся, когда пользователь прокручивает или изменяет размер окна.

То, что я ищу, уже реализовано в плагине bootstrap-select, но плагин слишком "тяжелый" для использования.

Ответ 1

Немного поздно, но, поскольку это сильно отличается от других, вот мое решение для загрузки. 3. Оно применяется глобально ко всем выпадающим спискам на странице, также созданным или загружаемым динамически.

Обратите внимание, что мне пришлось использовать событие 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");
});

Можно легко улучшить его с помощью @PeterWone app height wizardry, если это необходимо, в настоящее время это работает для меня достаточно хорошо.

ОБНОВЛЕНИЕ

Вот скрипка, представляющая это решение: http://jsfiddle.net/3s2efe9u/

Ответ 2

У меня были проблемы с производительностью с предоставленным ответом на больших страницах.

Я "улучшил" его, используя getBoundingClientRect(), и добавив новый класс к определенному .btn-group, который содержит dropdows., например. btn-group-dropup.

Ваш пробег может отличаться.

(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 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);

Ответ 3

Редактировать 07 января 2019 года: привести в порядок имена переменных и немного оптимизировать код.

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);

Ответ 4

Я знаю, что это старый вопрос, но он высоко оценивает результаты Google, поэтому еще одно простое решение, которое сработало для меня. Откорректируйте 150 в инструкции if для того, чтобы в любом месте вам понадобилось выпадающее меню.

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);

Ответ 5

Вместо того, чтобы делать каждое меню каждый раз, когда что-то меняет границы отсечения, почему бы не использовать 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));
  });

Очевидно, вам нужно будет управлять привязками событий, поскольку меню приходят и уходят. В приложении Durandal, для которого Натан отвечает morphed в выше, есть контекстное меню для каждой строки, представляющей датум. Я создаю привязки при загрузке данных, сохраняю их в модели представления и уничтожаю их при деактивации. Мне не нужно воссоздавать их при активации, потому что это вызывает процедуру загрузки данных, которая, в свою очередь, создает привязки.

Я также добавил:

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

Вы помещаете это в shell.js, чтобы он загружался раньше и затрагивал все виды. Если это еще не очевидно, то, что это делает, закрывает любое раскрывающееся окно, когда окно прокручивается или изменяется. Это соответствует соглашению Windows и не требует перекомпоновки позиционирования при изменении размера или прокрутки событий.

Обратите внимание на функцию appHeight. Даже jQuery не может точно сообщить высоту окна на мобильных телефонах, но appHeight работает правильно на каждой платформе и браузере, которые я тестировал до сих пор (IE, Ch, FF desktop, IE, Ch, Sa mobile).

В своем текущем воплощении он измеряет абсолютно расположенное скрытое множество div, чтобы заполнить область просмотра. Это работает в Android Chrome, но не в IE, тогда как другой метод, показанный в коде, работает с IE, но не с Android Chrome, поэтому я использую оба и использую меньшее число.

В моем приложении Durandal функция реализована как метод высоты для объекта приложения, но для краткости и ясности я сгладил код примера.

Ответ 6

Я нашел ответ от 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");
});

Ответ 7

Я проверил производительность и поддержку браузера (Chrome, Firefox, Edge, IE 10 и выше).

Я охватил как вертикальное, так и горизонтальное автоматическое позиционирование.

Код:

(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')
  });
})();