web-dev-qa-db-ja.com

表示されていない場合にのみ要素までスクロール-jQuery

私はこれのバリエーションが何度か尋ねられたことを知っています。私はSOをしばらくの間ブラウジングしていますが、何か間違っているか、必要なものが見つからないかのどちらかです。

ネストされたコメントの構造があります。これは Facebookコメントプラグイン とほぼ同じです。replyをクリックすると、テキストエリアとボタンのある小さなフォームが下部に表示されますコメント。

繰り返しになりますが、動作は Facebookコメントプラグイン と同じであり、新しく追加されたtextareaをスクロールして表示する場合も同じです。

私は scrollTo プラグインを試してみましたが、スムーズに動作しますが、ページの一番下まで手動でスクロールしても、スクロールアニメーションは常にスクロール位置をリセットします上から始めます

参考までに、これはscrollToを呼び出す方法です。

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

どこ addReply_1は、フォームを含むdivです。フォーム自体とtextareaまでスクロールしました。同じ結果。

要素が表示されていない場合にのみ、要素までスクロールする方法はありますか?

jQueryを使用して要素にスクロールする のように、SOで提供される多くのソリューションを試してみましたが、期待どおりに動作するようには見えません。でも jQueryを使用して特定の要素にスクロールする または スクロール後に要素が表示されるかどうかを確認する は、同じ「ジャンプ」動作を表示します。


更新:動作を示すオンラインデモ

私が不平を言っている動作を示すHTMLデモページをアップロードしました: http://www.wouldbebetter.com/demo/comment-demo.htm

ページの一番下までスクロールし、Replyリンクをクリックして、私が参照している「ジャンプ」スクロールを確認してください。

このデモでは、@ Robert Koritnikの回答のscrollintoviewプラグインを使用していますが、たとえばScrollToを使用しても、動作は同じです。

35
Sergi Papaseit

はい、スクロール可能な祖先の可視境界内にない場合にのみ要素にスクロールする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は、管理可能なコストがかかるマスターページを使用しています。つまり、実行時にどの要素がスクロール可能になるかわかりません。しかし、私はどの要素を見たいのか知っています。したがって、このプラグイン。また、さまざまなシナリオをサポートするscrollTo()プラグインと比較して、かなり単純化されています。ほとんどの場合、開発者は1つ(または非常に限られた数)のみを使用する傾向があります。

追加の観察

デフォルトのリンククリック処理防止

私のプラグインを使用すると、これらの返信ボックスを追加するときにちらつきが発生するため、かなり問題になります。問題は、リンクのクリックが実際に実行されることです。ページをスムーズに機能させるには、これを防ぐ必要があります。

  1. 次の2つの方法のいずれかで、リンクにクリックイベントを設定します。

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

主な考え方は、ブラウザがリンクのクリックを処理しないようにすることです。これにより、ブラウザーはページはめ込みアンカーを探し、アンカーが見つからないため、上部に自動スクロールします。次に、要素にスクロールするように指示します。

重複した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();
}
_
31
Robert Koritnik

最新のブラウザはすべてこれをサポートしています。訪問: 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]);
23
Magu

同じ問題がありました...いくつかの回答を確認した後、これを行うために思いついたのは次のとおりです...別のプラグインをプルダウンせずに。

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) for IE <8サポート

22
iandisme

要素がコンテナ内に表示されることを確認するには:

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

@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") );
2
bmode

私はkofifus( https://stackoverflow.com/a/43010437/1075062 )からの回答を使用しますが、多くの場合、コンテナーが何であるかわからないため、(- 最初のスクロール可能な親を見つける )それを見つける。私は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();
            }
        }
    }
}
0
Etherman

Element.scrollIntoViewIfNeeded()

MDN Web Docs によると:

Element.scrollIntoViewIfNeeded()メソッドは、現在の要素がまだブラウザの表示領域内にない場合、ブラウザウィンドウの表示領域にスクロールします窓。要素がブラウザウィンドウの表示領域内に既にある場合、スクロールは行われません。このメソッドは、標準の Element.scrollIntoView() メソッドの独自のバリエーションです。

例(jQueryを使用):

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

注:この機能は非標準であり、標準トラックにはないため、本番環境で使用する前に注意してください。ブラウザのサポートについては Can I use ... を参照してください。

0
Grant Miller

ここでのすべての回答は古く、jQueryの最新バージョンではおそらく機能していないようです(おそらく、たとえば、position()関数とoffset()関数の変更が原因です)、または制限が多すぎて、必要な状況で機能しません。たとえば、コードがiframe内にある場合、上記の回答はどれも機能しないようです。

私が気付いた最大のことの1つは、すべてのオブジェクトがコンテナーオブジェクトの通常の高さを使用していたことです。これは、コンテナーオブジェクト全体がウィンドウに表示されている限り問題なく機能しますが、コンテナーオブジェクトがhtmlオブジェクト自体と高さの場合表示されているものをはるかに下回ると、コードのスクロールダウン部分が機能しなくなります。代わりに、アルゴリズムは、オブジェクトが画面上で適切に機能するために、オブジェクトの画面上の表示高さを使用する必要があります( jQuery でdivの表示高さを取得するを参照)。

私は、はるかに堅牢で、より多くの状況で機能する独自のソリューションを作成することになりました。

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-$ targetの要素を含むjQueryオブジェクト(つまり、DOM内のスクロールバーを持つ要素)。デフォルトは、$ target要素を含み、iframeウィンドウを選択するのに十分スマートなウィンドウです。プロパティ名に$を含めることを忘れないでください。

options.padding-オブジェクトがスクロールされて表示されるときに、オブジェクトの上または下に追加するピクセル単位のパディング。この方法では、ウィンドウの端に対して正しくありません。デフォルトは20です。

options.instant-trueに設定すると、jQuery animateは使用されず、スクロールがすぐに正しい場所に表示されます。デフォルトはfalseです。

options.animationOptions-jQueryアニメーション関数に渡したいjQueryオプション( http://api.jquery.com/animate/ を参照)。これにより、アニメーションの継続時間を変更したり、スクロールが完了したときにコールバック関数を実行したりできます。これはoptions.instantはfalseに設定されています。インスタントアニメーションが必要だがコールバックがある場合は、options.animationOptions.duration = 0を使用する代わりにoptions.instant = true

0
dallin

私はあなたが何を望んでいるのか理解したかどうかはわかりませんが、これがそれであるか、それに近いか、私が完全に迷っているかを確認します。

<!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>
0
msmafra