web-dev-qa-db-ja.com

bootstrap画面の位置に応じたドロップダウンメニューの自動ドロップアップ?

トラブルから私を救うために、あなたが誰かが私が準備を求めているものを持っているかどうか疑問に思っていました。私が探しているのは、ドロップダウンメニューが画面上の位置に応じてドロップアップクラスを自動的に追加し、ユーザーがウィンドウをスクロールまたはサイズ変更したときに自動的に変更されるようにすることです。

私が探しているものはすでにブートストラップ選択プラグインに実装されていますが、プラグインは「重い」ため使用できません。

18
scooterlord

少し遅れますが、他のものとはかなり異なるため、ここにbootstrap 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のアプリの高さ 魔法使いで簡単に改善できますが、現在これで十分に機能します。

更新

このソリューションを表すフィドルは次のとおりです。 http://jsfiddle.net/3s2efe9u/

21
Zoltán Tamási

大きなページで提供された回答でパフォーマンスの問題がありました。

getBoundingClientRect()を使用して「改善」し、ドロップウィンドウを含む特定の.btn-groupに新しいクラスを追加しました。 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'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);
10
nathan-m

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

私はこれが古い質問であることを知っていますが、それはグーグルの結果の上位にあるので、ここに私のために働いた別の簡単な解決策があります。 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);
3
user2407400

何かがクリッピング境界を変更するたびにすべてのメニューを処理するのではなく、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));
  });

明らかに、メニューが出入りするときにイベントバインディングを管理する必要があります。 Nathanの回答が上記にモーフィングされたDurandalアプリには、データを表す各行のコンテキストメニューがあります。データの読み込み時にバインディングを作成し、それらをビューモデルに保持し、非アクティブ化時にそれらを破棄します。起動時にそれらを再作成する必要はありません。これにより、データロードルーチンが呼び出され、バインディングが作成されます。

私もこれを追加しました:

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

これをShell.jsに配置すると、早期に読み込まれ、すべてのビューに影響します。まだ明確でない場合は、ウィンドウがスクロールまたはサイズ変更されたときに開いているドロップアップを閉じます。これはWindowsの規則に準拠しており、サイズ変更またはスクロールイベントが発生したときに位置を再計算する必要がありません。

AppHeight関数に注意してください。 jQueryでも携帯電話のウィンドウの高さを正確に報告できませんが、appHeightは、これまでにテストしたすべてのプラットフォームとブラウザー(IE、Ch、FFデスクトップ、IE、Ch、Saモバイル)で正しく機能します。

現在の具体化では、ビューポートを埋めるために絶対的に配置された非表示のdivセットを測定します。これはAndroid ChromeしかしIEではなく機能しますが、コードに示されている他のメソッドはIEで機能しますが、= Android Chromeなので、両方を使用し、小さい番号を使用します。

私のDurandalアプリでは、関数はアプリオブジェクトの高さメソッドとして実装されていますが、簡潔さと明確さのために、サンプルコードをフラット化しました。

2
Peter Wone

私はZoltánTamá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");
});
0
Matthias Palmer

パフォーマンスとブラウザのサポート(クロム、Firefox、エッジ、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')
  });
})();
0