仅在不在视图中时滚动到元素 - jQuery

时间:2011-04-16 09:34:54

标签: jquery html scroll

我知道有几次这样的变化;我已经浏览了一段时间,但要么我做错了,要么找不到我需要的东西。

我有一个嵌套注释的结构,与Facebook Comments plugin几乎相同,只要点击reply,就会在注释的底部出现一个带有textarea的小表单和一个按钮。< / p>

同样,行为与Facebook Comments plugin相同,我想在滚动新添加的textarea进入视图时实现相同的目标。

我尝试了scrollTo插件,它运行顺畅但是,即使我手动滚动到页面的最底部,滚动动画也将始终重置滚动位置并从顶

为了记录,我正在调用scrollTo:

$.scrollTo($('#addReply_1'), 800);

其中addReply_1是包含表单的div。我已经向表格本身和textarea滚动。相同的结果。

有没有办法滚动到元素只有它已经不可见?

我尝试过在SO上提供的许多解决方案,例如Scroll to an element using jQuery,但似乎没有任何行为符合要求;偶数Scroll to a particular element w/ jQueryCheck if element is visible after scrolling会显示相同的“跳跃”行为。


更新:显示行为的在线演示

我上传了一个html演示页面,其中显示了我抱怨的行为:http://www.wouldbebetter.com/demo/comment-demo.htm

只需滚动到页面底部,然后点击任意Reply链接即可查看我所指的“跳跃”滚动。

请注意,此演示使用@Robert Koritnik的答案的scrollintoview插件,但如果我使用ScrollTo,则行为相同。

10 个答案:

答案 0 :(得分:30)

是的,有一个jQuery插件只有当它不在可滚动祖先的可见边界内时才滚动到元素。我写过一个完全符合你的要求。与scrollTo()相比,您可能会发现它更容易使用,因为您只需要提供您想要看到的元素。

我可以复制粘贴代码,但由于我不时添加一些附加内容,最好将您链接到blog post,在那里您可以找到与编程滚动和最新插件代码相关的所有细节。程序化滚动可能会让用户和整个用户界面体验分散注意力,因此我认为这将是一个有趣的阅读。

用法

插件非常简单易用:

$("#ElementToScrollIntoView").scrollintoview();

插件会自动找到最近的可滚动祖先并相应地滚动它(如果需要的话)。您可以使用此插件的一些其他设置,这就是它们的样子:

scrollintoview: function (options) {
    /// <summary>Scrolls the first element in the set into view by scrolling its closest scrollable parent.</summary>
    /// <param name="options" type="Object">Additional options that can configure scrolling:
    ///        duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds)
    ///        direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both")
    ///        complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled)
    /// </param>
    /// <return type="jQuery">Returns the same jQuery set that this function was run on.</return>

我在我的Sharepoint 2010网站上使用此插件,在我提供长表格数据的页面上。每当我向这个表添加一个新项目(行)时,我还会滚动到这个新记录并突出显示它,这样用户就可以立即看到新记录。

Sharepoint也是我决定不手动提供可滚动元素而是以编程方式查找它的原因。 Sharepoint使用admin costumizable主页,这意味着我不知道哪个元素在运行时可以滚动。但我确实知道我想看哪个元素。因此这个插件。与支持各种不同场景的scrollTo()插件相比,它也相当简化。大多数时候,开发人员往往只使用一个(或者数量非常有限)。

其他观察

默认链接点击处理预防

使用我的插件仍然会让它变得有问题,因为在添加这些回复框时会有一些闪烁。问题是您的链接点击实际执行。您应该阻止这种情况,以使您的页面顺利运行:

  1. 可以通过以下两种方式之一在链接上设置点击事件:

    <a href="javascript:void AddReplyForm(44); return false;">Reply</a>
    

    <a href="#" onclick="void AddReplyForm(44); return false;">Reply</a>
    
  2. 更好的方法是在文档准备就绪时运行它:

    $(function() {
        $("a").click(function(evt) {
            evt.preventDefault();
        });
    });
    
  3. 主要思想是阻止浏览器处理链接点击。因为这会使浏览器查找页内锚点,因为它找不到,所以它会自动滚动到顶部。然后你告诉它滚动到你的元素。

    重复ID

    创建回复表单时,添加新元素和新元素,但它们都具有相同的ID。您应该避免这样做或使用其他方法。您可以通过向BindClick()函数提供元素来完全删除ID。主回复生成函数也可以看起来像这样(这个函数是以完全不需要元素ID的方式编写的):

    function AddReplyForm(topCommentID)
    {
        var el = $(addReplyForm).appendTo('#comment_' + topCommentID + ' .right');
        BindClick(el); // mind this !! you provide the element to remove
        el.scrollintoview();
    }
    

答案 1 :(得分:21)

有同样的问题......在回顾几个答案之后,这就是我提出的做法...而不是拉下另一个插件。

function scrollIntoViewIfNeeded($target) {
    if ($target.position()) {
        if ($target.position().top < jQuery(window).scrollTop()){
            //scroll up
            $('html,body').animate({scrollTop: $target.position().top});
        }
        else if ($target.position().top + $target.height() >
            $(window).scrollTop() + (
                window.innerHeight || document.documentElement.clientHeight
            )) {
            //scroll down
            $('html,body').animate({scrollTop: $target.position().top -
                (window.innerHeight || document.documentElement.clientHeight)
                    + $target.height() + 15}
            );
        }
    }
}

最后一行的“15”只是额外的填充 - 您可能需要调整它,或将其添加到向上滚动行。

编辑:将window.innerHeight更改为(window.innerHeight || document.documentElement.clientHeight)以获取IE&lt; 8支持

答案 2 :(得分:19)

所有现代浏览器都支持此功能。访问:http://caniuse.com/#search=scrollIntoView

function scrollIntoViewIfNeeded(target) {
    var rect = target.getBoundingClientRect();
    if (rect.bottom > window.innerHeight) {
        target.scrollIntoView(false);
    }
    if (rect.top < 0) {
        target.scrollIntoView();
    } 
}

<强>更新

目标必须是元素。如果你使用jQuery调用这样的函数:

scrollIntoViewIfNeeded($(".target")[0]);

答案 3 :(得分:4)

确保elem在容器内可见:

let rectElem = elem.getBoundingClientRect(), rectContainer=container.getBoundingClientRect();
if (rectElem.bottom > rectContainer.bottom) elem.scrollIntoView(false);
if (rectElem.top < rectContainer.top) elem.scrollIntoView();

答案 4 :(得分:2)

我稍微修改了@iandisme的答案并将其作为一个小的jquery插件包装起来:

(function ($) {
'use strict';

$.fn.scrollToSimple = function ($target) {
    var $container = this.first();      // Only scrolls the first matched container

    var pos = $target.position(), height = $target.outerHeight();
    var containerScrollTop = $container.scrollTop(), containerHeight = $container.height();
    var top = pos.top + containerScrollTop;     // position.top is relative to the scrollTop of the containing element

    var paddingPx = containerHeight * 0.15;      // padding keeps the target from being butted up against the top / bottom of the container after scroll

    if (top < containerScrollTop) {     // scroll up                
        $container.scrollTop(top - paddingPx);
    }
    else if (top + height > containerScrollTop + containerHeight) {     // scroll down
        $container.scrollTop(top + height - containerHeight + paddingPx);
    }
};
})(jQuery);

我删除了对.animate的调用,因为我正在寻找即时滚动。我还添加了滚动任何(可滚动)容器的功能,而不仅仅是窗口。用法示例:

// scroll the window so #target  is visible
$(window).scrollToSimple( $("#target") );

// scroll the scrollable element #container so that #target is visible
$("#container").scrollToSimple( $("#target") );

答案 5 :(得分:0)

我不知道我是否理解你想要的东西,但看看是不是这样,接近或者如果我完全迷失了:

<!DOCTYPE html>
<html>
    <head>
        <title>Scroll To Reply</title>
        <meta charset="utf-8" />
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function(){
                var $contextarea = $('#contextform textarea');
                $('a.reply').live('click',function(){
                    $(this).closest('p').css({'margin-top':'300px;'});
                    $('#contextform').appendTo($(this).closest('p'));
                    $('#contextform').slideDown(1000);//.css({'display':'block'});
                    $(this).closest('p').attr('id','scrolltome');
                    $('html,body').animate({slideDown: $('#scrolltome').offset().top}, 2000);
                });
                $('.sendreply').live('click',function(){
                    if($contextarea.val() == null || $contextarea.val() == '') {
                        alert('textarea is empty!');
                    } else {
                        $('#contextform').slideUp(800);
                    }
                });

                //                $('button').click(function() {
                //                    $('html,body').animate({scrollTop: $('#body').offset().top}, 2000);//scrolls to the top
                //                });

            });
        </script>
        <style type="text/css">
            body{
                font-size:25px;
                font-family: 'Arimo', Arial, sans-serif;
            }
            #contextform {
                display:none;
                width:500px;
                height: 150px;
                background: #0489B7;
                padding: 5px
            }
            #contextform textarea {
                width: 490px;
                height: 90px;
            }
        </style>
    </head>
    <body id="body">
        <h1>Scroll to reply</h1>
        <div id="contextform">
            <form method="post" action="">
                <textarea id="textarea"></textarea>
                <button type="button" class="sendreply">Send Reply</button>
            </form>
            <a name="myAnchor" id="myAnchor">anchor</a>
        </div>
        <ol>
            <?php for ($i = 0; $i < 20; $i++) { ?>
                <li><p>The quick brown fox jumps over the lazy dog
                        <a href="#scrolltome" class="reply">Reply</a>
                    </p></li>
            <?php } ?>
        </ol>
    </body>
</html>

答案 6 :(得分:0)

这里的每个答案似乎都已过时,不再使用现代版本的jQuery(例如,可能是由于position()和offset()函数的更改),或者太有限而无法在我需要它们的情况下使用。例如,如果您的代码在iframe中,上述答案似乎都不起作用。

我注意到的最大的事情之一就是它们全部使用了容器对象的正常高度,只要整个容器对象在窗口中可见,它就可以正常工作,但是当您的容器对象是html对象本身时,并且高度扩展到远低于显示的高度,代码的向下滚动部分不再起作用。相反,该算法需要使用对象在屏幕上的可见高度才能使其正常工作(请参见Get the visible height of a div with jQuery)。

我最终编写了自己的解决方案,该解决方案似乎更加健壮并且可以在更多情况下使用:

function scrollIntoViewIfNeeded($target, options) {

    var options = options ? options : {},
    $win = $($target[0].ownerDocument.defaultView), //get the window object of the $target, don't use "window" because the element could possibly be in a different iframe than the one calling the function
    $container = options.$container ? options.$container : $win,        
    padding = options.padding ? options.padding : 20,
    elemTop = $target.offset().top,
    elemHeight = $target.outerHeight(),
    containerTop = $container.scrollTop(),
    //Everything past this point is used only to get the container's visible height, which is needed to do this accurately
    containerHeight = $container.outerHeight(),
    winTop = $win.scrollTop(),
    winBot = winTop + $win.height(),
    containerVisibleTop = containerTop < winTop ? winTop : containerTop,
    containerVisibleBottom = containerTop + containerHeight > winBot ? winBot : containerTop + containerHeight,
    containerVisibleHeight = containerVisibleBottom - containerVisibleTop;

    if (elemTop < containerTop) {
        //scroll up
        if (options.instant) {
            $container.scrollTop(elemTop - padding);
        } else {
            $container.animate({scrollTop: elemTop - padding}, options.animationOptions);
        }
    } else if (elemTop + elemHeight > containerTop + containerVisibleHeight) {
        //scroll down
        if (options.instant) {
            $container.scrollTop(elemTop + elemHeight - containerVisibleHeight + padding);
        } else {
            $container.animate({scrollTop: elemTop + elemHeight - containerVisibleHeight + padding}, options.animationOptions);
        }
    }
}

$target是一个jQuery对象,其中包含您希望在需要时滚动到视图中的对象。

options(可选)可以包含在对象中传递的以下选项:

options.$container-一个jQuery对象,指向$ target的包含元素(换句话说,带有滚动条的dom中的元素)。默认为包含$ target元素的窗口,并且足够聪明以选择iframe窗口。请记住在属性名称中包含$。

options.padding-滚动到视图中时对象上方或下方添加的像素填充。这样,它就不能紧贴窗口边缘。默认值为20。

options.instant-如果设置为true,则将不使用jQuery animate,并且滚动条将立即弹出到正确的位置。默认为false。

options.animationOptions-您希望传递给jQuery animate函数的所有jQuery选项(请参见http://api.jquery.com/animate/)。这样,您可以更改动画的持续时间,或者在滚动完成后执行回调函数。仅在options.instant设置为false时有效。如果您需要即时动画但需要回调,请设置options.animationOptions.duration = 0而不是使用options.instant = true

答案 7 :(得分:0)

Element.scrollIntoViewIfNeeded()

根据MDN Web Docs

  

Element.scrollIntoViewIfNeeded() 方法将当前元素滚动到浏览器窗口的可见区域(如果尚未位于浏览器窗口的可见区域内)。如果该元素已经在浏览器窗口的可见区域内,则不会发生滚动。此方法是标准Element.scrollIntoView()方法的专有变体。

示例(使用jQuery):

$('#example')[0].scrollIntoViewIfNeeded();
  

注意:此功能是非标准功能,不在标准范围内,因此在生产中使用此功能之前请务必谨慎。   有关浏览器支持,请参见Can I use...

答案 8 :(得分:0)

我使用kofifus(https://stackoverflow.com/a/43010437/1075062)的答案,但在许多情况下,我不知道容器是什么,所以我使用(Find first scrollable parent)的答案来找出答案。我使用jQuery UI,所以我可以使用.scrollParent()方法(如果需要,可以在链接的问题中找到它的端口)。我还使用了专有的scrollIntoViewIfNeeded(如果存在)(在许多现代浏览器中都存在),因此,当前仅FireFox和Opera Mini(和旧版浏览器)(https://caniuse.com/#feat=scrollintoviewifneeded)才需要自定义代码。

(代码是TypeScript)

/**
 * Scroll the element into view if not already visible.
 *
 * https://caniuse.com/#feat=scrollintoviewifneeded
 * https://stackoverflow.com/questions/5685589/scroll-to-element-only-if-not-in-view-jquery
 * https://stackoverflow.com/questions/35939886/find-first-scrollable-parent
 */
public static ScrollIntoViewIfNeeded(element: Element): void
{
    if (element)
    {
        // Proprietary method, available in many modern browsers
        if ((<any>element).scrollIntoViewIfNeeded)
        {
            (<any>element).scrollIntoViewIfNeeded();
        }
        else
        {
            let $element = $(element);

            // jQuery UI scrollParent method available?
            if ($element.scrollParent)
            {
                let $parent = $(element).scrollParent();
                let rectElem = element.getBoundingClientRect();
                let rectContainer = $parent[0].getBoundingClientRect();

                if (rectElem.bottom > rectContainer.bottom || rectElem.top < rectContainer.top)
                {
                    element.scrollIntoView();
                }
            }
            else if (element.scrollIntoView)
            {
                element.scrollIntoView();
            }
        }
    }
}

答案 9 :(得分:-2)

这不需要JQuery。

此函数只显示指定的元素:

function scrollIntoView(elm) {
    if(elm) {
        let bnd=elm.getBoundingClientRect();
        if     (bnd.top<0                    ) { elm.scrollIntoView(true );      }
        else if(bnd.bottom>window.innerHeight) { elm.scrollIntoView(bnd.top<=0); }
        }
    return elm;
    }

以下功能更强大的功能允许将所需元素的容器滚动到视图中,还可以轻松确保整个容器可见,从而避免半遮挡的视觉效果。

/**
  * Scroll the specified element into view, optionally first searching for a specific container and
  * first making that visible. This function does it's best to scroll the entire container into view
  * but ultimately ensures that as much of the element as fits in the viewport will be visible.
  *
  * #### Arguments:
  *
  *     elm (DOMElement)    The element to make visible.
  *     contag (string)     Optional name of a container tag. Ignored if blank/null/omitted.
  *     conprp (string)     Optional name of a container property to also match. Ignored if blank/null/omitted.
  *     conval (string)     Optional value of the container property. Ignored if `conprp` is not supplied; defaults to "" if omitted.
  */
function scrollIntoView(elm,contag,conprp,conval) {
    if(elm) {
        if(contag || conprp) {
            let con;
            if(conval==null) { conval=""; }
            for(con=elm; con!=null && con.tagName!="BODY"; con=con.parentNode) {
                if((!contag || contag==con.tagName) && (!conprp || con[conprp]==conval)) {
                    break;                                                                          // matched container tag and property
                    }
                }
            if(con) { scrollIntoView(con); }
            }

        let bnd=elm.getBoundingClientRect();

        if     (bnd.top<0                    ) { elm.scrollIntoView(true );      }
        else if(bnd.bottom>window.innerHeight) { elm.scrollIntoView(bnd.top<=0); }
        }
    return elm;
    }

这样可以很容易地在向上滚动时显示:

enter image description here

而不是:

enter image description here