Javascript 检测下拉导航是否会离开屏幕并重新定位

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11512032/
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-24 06:05:36  来源:igfitidea点击:

Detect if dropdown navigation would go off screen and reposition it

javascriptjqueryhtmlcss

提问by Wesley Murch

I've got your typical dropdown navigation, and I'm trying to make sure the drop menu links are always accessible and visible:

我有你典型的下拉导航,我正在努力确保下拉菜单链接始终可访问和可见:

<li><a href="#">Link 1</a>
    <ul>
        <li><a href="#">Link 1</a></li>
        <li><a href="#">Link 2</a></li>
        <li><a href="#">Link 3</a></li>
    </ul>
</li>
<li><a href="#">Link 2</a>
    <ul>
        <li><a href="#">Link 1</a></li>
        <li><a href="#">Link 2</a></li>
        <li><a href="#">Link 3</a></li>
    </ul>
</li>
<!-- etc. -->
</ul>

The CSS really isn't anything special (colors and backgrounds removed):

CSS 真的没什么特别的(颜色和背景被移除了):

.dropdown,
.dropdown li,
.dropdown ul {
    list-style:none;
    margin:0;
    padding:0;
}
.dropdown {
    position:relative;
    z-index:10000;
    float:left;
    width:100%;
}
.dropdown ul {
    position:absolute;
    top:100%;
    visibility:hidden;
    display:none;
    width:16em;
}
.dropdown ul ul {
    top:0;
    left:100%;
}
.dropdown li {
    position:relative;
    float:left;
}
.dropdown li:hover{
    z-index:910;
}
.dropdown ul:hover,
.dropdown li:hover > ul,
.dropdown a:hover + ul,
.dropdown a:focus + ul {
    visibility:visible;
    display:block;
}
.dropdown a {
    display:block;
    padding:1em 2em;
}
.dropdown ul li {
    width:100%;
}

There are an unknown number of top level links (they are created by the user). The problem I'm having is that sometimes the drop menus (which go to the right) will go off screen if the top level link is too far to the right. I added this bit of CSS to compensate:

有未知数量的顶级链接(它们是由用户创建的)。我遇到的问题是,如果顶级链接离右侧太远,有时下拉菜单(向右)会离开屏幕。我添加了这一点 CSS 来弥补:

.dropdown > li:last-child ul { /* ...or use a class on the last link for IE */
    right:0;
}

Now the last one goes to the left instead of off screen, which is nice, but there are a few issues:

现在最后一个移到左边而不是屏幕外,这很好,但有几个问题:

  1. I don't always need these styles for the last link since it isn't always at the edge of the screen (like if there are only 3 links).
  2. When the browser window is resized the links stack on top of each other (by design). Sometimes links in the middle of the sequence end up on the right edge, and their dropdowns are cut off.
  3. Sometimes the "next to last" link's menu will go outside the boundary as well.
  1. 我并不总是需要最后一个链接的这些样式,因为它并不总是在屏幕的边缘(就像只有 3 个链接一样)。
  2. 当浏览器窗口调整大小时,链接堆叠在彼此的顶部(按设计)。有时序列中间的链接会在右边缘结束,并且它们的下拉列表被切断。
  3. 有时,“倒数第二”链接的菜单也会超出边界。

Resize the panel in this demo to see what I mean (the red area is considered "off screen")http://jsfiddle.net/G7qfq/

调整此演示中的面板大小以了解我的意思(红色区域被视为“屏幕外”)http://jsfiddle.net/G7qfq/

I've struggled with this annoyingly common problem for years and have never found a satisfactory solution. Is there any way to check if the drop menu would go off screen, and if so, add/remove a classname or something so I can keep it on screen with CSS?

多年来,我一直在与这个恼人的常见问题作斗争,但从未找到令人满意的解决方案。有什么方法可以检查下拉菜单是否会离开屏幕,如果是,请添加/删除class名称或其他内容,以便我可以使用 CSS 将其保留在屏幕上?

One clue I might use is that if a menu doesgo off screen, it always produces a vertical scroll bar at the bottom of the window, but I'm not sure how to use that knowledge. I tried the accepted answer to this questionabout detecting vertical scroll bars, but for some reason it always returns true, and always adds the "edge" class (maybe there's an issue with the timing?):

我可能使用的一个线索是,如果菜单确实离开屏幕,它总是会在窗口底部生成一个垂直滚动条,但我不确定如何使用该知识。我尝试了这个关于检测垂直滚动条的问题的公认答案,但由于某种原因它总是返回true,并且总是添加“边缘”类(也许时间有问题?):

$(".dropdown li").on('mouseenter mouseleave', function (e) {

    // Get the computed style of the body element
    var cStyle = document.body.currentStyle||window.getComputedStyle(document.body, "");

    // Check the overflow and overflowY properties for "auto" and "visible" values
    hasVScroll = cStyle.overflow == "visible" 
             || cStyle.overflowY == "visible"
             || (hasVScroll && cStyle.overflow == "auto")
             || (hasVScroll && cStyle.overflowY == "auto");

    if (hasVScroll) {
        $(this).addClass('edge');
    } else {
        $(this).removeClass('edge');
    }
});?

Demo with the javascript: http://jsfiddle.net/G7qfq/2/

使用 javascript 演示:http: //jsfiddle.net/G7qfq/2/

Really, I don't want to see a vertical scroll bar even for a split second so I'm not sure that's the way to go, plus there could be false positives (scroll bar for some other reason).

真的,我不想看到垂直滚动条,即使是一瞬间,所以我不确定这是要走的路,而且可能存在误报(滚动条出于其他原因)。

I also tried the solution in this answerwhich I admit, I don't quite understand, and couldn't get it to work: http://jsfiddle.net/G7qfq/3/

我也尝试了这个答案中的解决方案,我承认,我不太明白,并且无法让它工作:http: //jsfiddle.net/G7qfq/3/

$(".dropdown li").on('mouseenter mouseleave', function (e) {

    var elm = $('ul:first', this);
    var off = elm .offset();
    var t = off.top;
    var l = off.left;
    var h = elm.height();
    var w = elm.width();
    var docH = $(window).height();
    var docW = $(window).width();

    var isEntirelyVisible = (t > 0 && l > 0 && t + h < docH && l+ w < docW);

    if ( ! isEntirelyVisible ) {
        $(this).addClass('edge');
    } else {
        $(this).removeClass('edge');
    }
});?

I assume the solution requires javascript, and I am using jQuery, but I haven't got a clue how to approach the problem. Any ideas?

我认为该解决方案需要 javascript,并且我使用的是 jQuery,但我不知道如何解决这个问题。有任何想法吗?

回答by r0m4n

I think you were almost there...

我想你快到了...

You should really only be interested in the calculations involved in the width. If the width of the dropdown element and the offset of that element is greater than the width of the container, you want to switch your menu.

您真的应该只对宽度中涉及的计算感兴趣。如果下拉元素的宽度和该元素的偏移量大于容器的宽度,则需要切换菜单。

$(function () {
    $(".dropdown li").on('mouseenter mouseleave', function (e) {
        if ($('ul', this).length) {
            var elm = $('ul:first', this);
            var off = elm.offset();
            var l = off.left;
            var w = elm.width();
            var docH = $(".container").height();
            var docW = $(".container").width();

            var isEntirelyVisible = (l + w <= docW);

            if (!isEntirelyVisible) {
                $(this).addClass('edge');
            } else {
                $(this).removeClass('edge');
            }
        }
    });
});

http://jsfiddle.net/G7qfq/582/

http://jsfiddle.net/G7qfq/582/

回答by Adrian

Here is a function that can be used for menus that fly-out to the right, or down (based off @r0m4n's code):

这是一个可用于向右或向下飞出的菜单的函数(基于@r0m4n 的代码):

function fixFlyout (containerElement, parentElement,flyoutElement,flyoutDirection) {
    $(parentElement).on('mouseenter mouseleave', function (e) {
        var element = $(flyoutElement, this);
        var offset = element .offset();
        switch(flyoutDirection) {
            case 'down':
                var top = offset.top;
                var height = element.height();
                var windowHeight = $(containerElement).height();
                var isEntirelyVisible = (top + height <= windowHeight);
                break;
            case 'right':
                var left = offset.left;
                var width = element.width();
                var windowWidth = $(containerElement).width();
                var isEntirelyVisible = (top + width <= windowWidth);
                break;
        }
        if (!isEntirelyVisible ) {
            $(element).addClass('edge');
        } else {
            $(element).removeClass('edge');
        }
    });
}
//Level 1 Flyout
fixFlyout(containerElement = '.header',parentElement = '.header .navigation>.menu>.expanded',flyoutElement = '.menu:first',flyoutDirection = 'down');

回答by RBILLC

I was not able to get addClass('edge') to work correctly but I was able to modify the CSS for the element in question to achieve the behavior. (only slightly modified from r0m4n):

我无法让 addClass('edge') 正常工作,但我能够修改相关元素的 CSS 以实现该行为。(仅从 r0m4n 稍作修改):

        //Align submenu to right if cut-off from window
        $(function () {
            $(".dropdown").on('mouseenter mouseleave', function (e) {
                if ($('.dropdown-content', this).length) {
                    var dropdownElement = $('.dropdown-content:first', this);
                    var elementOffset = dropdownElement.offset();
                    var elementOffsetLeft = elementOffset.left;
                    var elementWidth = dropdownElement.width();
                    var pageHeigth = $(".show-on-scroll-wrapper").height();
                    var pageWidth = $(".show-on-scroll-wrapper").width();
                    //if left offset + width of dropdown is bigger than container width then it is cut-off
                    if ((elementOffsetLeft + elementWidth) > pageWidth) {
                        //Align Right
                        $(".dropdown-content").css({ "left":"auto", "right":"0", "margin-right":"-10px;"});
                    } else {
                        //Align Left
                        $(".dropdown-content").css({ "left":"0", "right":"auto", "margin-left": "-10px;" });
                    }
                }
            });
        });