DOM要素が現在のビューポートに表示されているかどうかを確認するにはどうすればよいですか?


949

DOM要素(HTMLドキュメント内)が現在表示されている(ビューポートに表示されている)かどうかを確認する効率的な方法はありますか?

(質問はFirefoxに関するものです。)


あなたが目に見えることによって何を意味するかに依存します。スクロール位置が指定されている場合、現在ページに表示されているという意味であれば、要素のyオフセットと現在のスクロール位置に基づいて計算できます。
roryf 2008

18
明確化:現在表示されている長方形内のように表示されます。非表示または表示ではないように表示:なし?Zオーダーの他の背後にないように見えますか?
アダムライト

6
現在表示されている長方形の中のように。ありがとう。言い換え。
benzaita 2008

2
ここでのすべての回答は、親要素の可視領域の外側(オーバーフローが非表示になっている)であるが、ビューポート領域の内側にある項目に対して誤検出を与えることに注意してください。
アンディE

1
100万の回答があり、ほとんどが途方もなく長いです。2つのライナーについては、こちらをご覧ください
レオニース

回答:


347

更新:時間の経過とともに、ブラウザーも進化しています。この手法は推奨されなくなりました。InternetExplorer 7より前のバージョンをサポートする必要がない場合は、Danのソリューションを使用してください。

元のソリューション(現在は古い):

これにより、要素が現在のビューポートに完全に表示されるかどうかが確認されます。

function elementInViewport(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top >= window.pageYOffset &&
    left >= window.pageXOffset &&
    (top + height) <= (window.pageYOffset + window.innerHeight) &&
    (left + width) <= (window.pageXOffset + window.innerWidth)
  );
}

これを変更して、要素の一部がビューポートに表示されているかどうかを確認するだけです。

function elementInViewport2(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top < (window.pageYOffset + window.innerHeight) &&
    left < (window.pageXOffset + window.innerWidth) &&
    (top + height) > window.pageYOffset &&
    (left + width) > window.pageXOffset
  );
}

1
投稿された元の機能に誤りがありました。el ...を再割り当てする前に、幅/高さを保存する必要がありました
Prestaul

15
要素がスクロール可能なdivにあり、ビューの外にスクロールされた場合はどうなりますか?
amartynov 2011年

2
以下のスクリプトの新しいバージョンを確認してください
Dan

1
@amartynovの質問にも興味があります。誰かが祖先要素のオーバーフローのために要素が非表示になっているかどうかを簡単に確認する方法を知っていますか?子がどれだけ深くネストされているかに関係なく、これを検出できる場合のボーナス。
Eric Nguyen

1
DOMを介した@deadManNの再帰は、非常に低速です。それは十分な理由ですが、ブラウザーベンダーはgetBoundingClientRect、特に要素の座標を見つける目的で作成しました...なぜそれを使用しないのですか?
Prestaul 2015

1402

現在、ほとんどのブラウザーはgetBoundingClientRectメソッドをサポートしていますが、これがベストプラクティスになっています。古い回答の使用は非常に遅く正確なくいくつかのバグがあります。

正しいと選択されたソリューションは、ほとんど正確ではありません。あなたはそのバグについてもっと読むことができます


このソリューションは、Internet Explorer 7以降、iOS 5以降のSafari、Android 2.0(Eclair)以降、BlackBerry、Opera Mobile、Internet Explorer Mobile 9でテストされています。


function isElementInViewport (el) {

    // Special bonus for those using jQuery
    if (typeof jQuery === "function" && el instanceof jQuery) {
        el = el[0];
    }

    var rect = el.getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /* or $(window).height() */
        rect.right <= (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */
    );
}

使い方:

上記の関数が呼び出された瞬間に正しい答えが返されることを確認できますが、イベントとしての要素の可視性の追跡についてはどうでしょうか。

<body>タグの下部に次のコードを配置します。

function onVisibilityChange(el, callback) {
    var old_visible;
    return function () {
        var visible = isElementInViewport(el);
        if (visible != old_visible) {
            old_visible = visible;
            if (typeof callback == 'function') {
                callback();
            }
        }
    }
}

var handler = onVisibilityChange(el, function() {
    /* Your code go here */
});


// jQuery
$(window).on('DOMContentLoaded load resize scroll', handler);

/* // Non-jQuery
if (window.addEventListener) {
    addEventListener('DOMContentLoaded', handler, false);
    addEventListener('load', handler, false);
    addEventListener('scroll', handler, false);
    addEventListener('resize', handler, false);
} else if (window.attachEvent)  {
    attachEvent('onDOMContentLoaded', handler); // Internet Explorer 9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

DOMを変更すると、要素の表示が変更される可能性があります。

ガイドラインと一般的な落とし穴:

多分あなたはページズーム/モバイルデバイスのピンチを追跡する必要がありますか?jQueryはズーム/ピンチクロスブラウザーを処理する必要があり、そうでない場合は最初または2番目必要があります。リンクが役立ちます。

DOM変更すると、要素の表示に影響を与える可能性があります。あなたはそれを制御し、handler()手動で呼び出す必要があります。残念ながら、クロスブラウザはありませんonrepaintイベント。一方、これにより、要素の可視性を変更する可能性のあるDOM変更に対してのみ最適化を行い、再チェックを実行できます。

保証CSSが適用されていないため、jQuery $(document).ready()内でのみ使用しないでください。この時点でれ。コードはハードドライブのCSSでローカルに動作しますが、リモートサーバーに配置すると失敗します。

DOMContentLoadedが呼び出された後、スタイルが適用されます、画像はまだ読み込まれていません。したがって、window.onloadイベントリスナーを追加する必要があります。

ズーム/ピンチイベントはまだキャッチできません。

最後の手段は、次のコードです。

/* TODO: this looks like a very bad code */
setInterval(handler, 600);

あなたは素晴らしい機能ページを使用することができますWebページのタブがアクティブで表示されているかどうかを気にする場合は、HTML5 APIをます。

TODO:このメソッドは2つの状況を処理しません。


6
私はこのソリューションを使用しています(ただし、「ボトム」タイプミスに注意してください)。また、検討している要素に画像が含まれる場合に注意する点もあります。Chromeは(少なくとも)画像が読み込まれるのを待って、boundingRectangleの正確な値を取得する必要があります。Firefoxにはこの「問題」がないようです
Claudio

4
ボディ内のコンテナでスクロールを有効にしているときに機能しますか?たとえば、ここでは機能しません-agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport(document.getElementById( "innerele"))。innereleは、スクロールが有効になっているコンテナ内に存在します。
再度、

70
計算では、要素が画面よりも小さいと想定しています。高または幅の要素がある場合は、使用する方がより正確かもしれませんreturn (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));
Roonaan

11
ヒント:jQueryを使用してこれを実装しようとしている場合isElementInViewport(document.getElementById('elem'))は、jQueryオブジェクト(たとえばisElementInViewport($("#elem)))ではなく、HTML DOMオブジェクト(たとえば)を渡すことを覚えておいてください。同等のjQueryは、次の[0]ように追加しますisElementInViewport($("#elem)[0])
ジミニー2014

11
el is not defined
M_Willett

176

更新

最新のブラウザーでは、以下の利点を提供するIntersection Observer APIをチェックすることをお勧めします。

  • スクロールイベントをリッスンするよりも優れたパフォーマンス
  • クロスドメインiframeで機能
  • 要素が別の要素を妨害/交差しているかどうかを知ることができます

Intersection Observerは本格的な標準になりつつあり、Chrome 51以降、Edge 15以降、Firefox 55以降で既にサポートされており、Safariの開発中です。利用可能なポリフィルもあります。


前の答え

Dan提供する回答にはいくつかの問題があり、状況によっては不適切なアプローチになる場合があります。これらの問題のいくつかは、下の方にある彼の回答で指摘されています。彼のコードは、

  • テストされている要素の前にある別の要素によって非表示
  • 親要素または祖先要素の表示領域外
  • CSS clipプロパティを使用して非表示にした要素またはその子

これらの制限は、次の簡単なテストの結果に示されています。

isElementInViewportを使用したテストの失敗

ソリューション: isElementVisible()

これらの問題の解決策を以下に示します。以下のテスト結果とコードの一部について説明しています。

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || document.documentElement.clientWidth,
        vHeight  = window.innerHeight || document.documentElement.clientHeight,
        efp      = function (x, y) { return document.elementFromPoint(x, y) };     

    // Return false if it's not in the viewport
    if (rect.right < 0 || rect.bottom < 0 
            || rect.left > vWidth || rect.top > vHeight)
        return false;

    // Return true if any of its four corners are visible
    return (
          el.contains(efp(rect.left,  rect.top))
      ||  el.contains(efp(rect.right, rect.top))
      ||  el.contains(efp(rect.right, rect.bottom))
      ||  el.contains(efp(rect.left,  rect.bottom))
    );
}

テストに合格: http : //jsfiddle.net/AndyE/cAY8c/

そしてその結果:

isElementVisibleを使用したテストに合格

その他の注意事項

ただし、この方法には独自の制限がないわけではありません。たとえば、同じ場所にある別の要素よりもz-indexが低い状態でテストされている要素は、前の要素が実際にはその一部を隠していない場合でも、非表示として識別されます。それでも、この方法には、Danのソリューションではカバーできないいくつかのケースでの用途があります。

element.getBoundingClientRect()document.elementFromPoint()はどちらもCSSOMワーキングドラフト仕様の一部であり、少なくともIE 6以降とほとんどのデスクトップブラウザで(完全ではありませんが)長い間サポートされています。これらの関数については、Quirksmodeを参照してくださいしてください。

contains()によって返される要素document.elementFromPoint()が、可視性をテストする要素の子ノードであるかどうかを確認するために使用されます。返された要素が同じ要素である場合にもtrueを返します。これにより、チェックがより堅牢になります。すべての主要ブラウザでサポートされており、Firefox 9.0が最後に追加されました。古いFirefoxサポートについては、この回答の履歴を確認してください。

要素の周囲のより多くのポイントをテストして、可視性を確認したい場合、つまり、要素が50%以上カバーされていないことを確認したい場合は、回答の最後の部分を調整するのにそれほど時間はかかりません。ただし、すべてのピクセルをチェックして100%表示されていることを確認すると、おそらく非常に遅くなることに注意してください。


2
doc.documentElement.clientWidthを使用するつもりでしたか?代わりに「document.documentElement」にする必要がありますか?別の注記では、これはCSSの 'clip'プロパティを使用してアクセシビリティーの
Kevinランピング

@klamping:良いキャッチ、ありがとう。のdocエイリアスとして使用していたコードから直接コピーしましたdocument。ええ、私はこれをエッジケースのためのまともな解決策と考えたいです。
アンディE

2
私にとってそれは機能していません。しかし、前の回答のinViewport()はFFで機能しています。
Satya Prakash

9
バウンディング角が期待される要素を返さない場合がありますように、また、あなたが丸みを帯びた角を持っているか、適用される変換場合、要素の中心が表示されていることを確認することが有益であり得る:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
ジャレド

2
私の入力では機能しませんでした(Chrome Canary 50)。なぜだかわからない、おそらくネイティブの丸みを帯びたコーナー?el.contains(efp(rect.left + 1、rect.top + 1))を機能させるために、座標を少し減らす必要がありました|| el.contains(efp(rect.right-1、rect.top + 1))|| el.contains(efp(rect.right-1、rect.bottom-1))|| el.contains(efp(rect.left + 1、rect.bottom-1))
Rayjax

65

ダンの答えを試してみました。ただし、境界を決定するために使用される代数は、要素がビューポートサイズ以下であり、完全にビューポート内にある必要があることを意味しtrue、簡単に偽陰性につながります。要素がビューポートにあるかどうかを判断したい場合、ライアンブの答えは近いですが、テストされる要素はビューポートと重なるはずなので、これを試してください:

function isElementInViewport(el) {
    var rect = el.getBoundingClientRect();

    return rect.bottom > 0 &&
        rect.right > 0 &&
        rect.left < (window.innerWidth || document.documentElement.clientWidth) /* or $(window).width() */ &&
        rect.top < (window.innerHeight || document.documentElement.clientHeight) /* or $(window).height() */;
}

33

公共サービスとして:
正しい計算(特に、携帯電話の画面ではウィンドウにすることができます)、正しいjQueryテスト、およびisElementPartiallyInViewportを追加したDanの回答:

ちなみに、window.innerWidthとdocument.documentElement.clientWidthの違いは、clientWidth / clientHeightにはスクロールバーが含まれていないのに対して、window.innerWidth / Heightには含まれていることです。

function isElementPartiallyInViewport(el)
{
    // Special bonus for those using jQuery
    if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
        el = el[0];

    var rect = el.getBoundingClientRect();
    // DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
    var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
    var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

    // http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
    var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
    var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);

    return (vertInView && horInView);
}


// http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
function isElementInViewport (el)
{
    // Special bonus for those using jQuery
    if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
        el = el[0];

    var rect = el.getBoundingClientRect();
    var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
    var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

    return (
           (rect.left >= 0)
        && (rect.top >= 0)
        && ((rect.left + rect.width) <= windowWidth)
        && ((rect.top + rect.height) <= windowHeight)
    );
}


function fnIsVis(ele)
{
    var inVpFull = isElementInViewport(ele);
    var inVpPartial = isElementPartiallyInViewport(ele);
    console.clear();
    console.log("Fully in viewport: " + inVpFull);
    console.log("Partially in viewport: " + inVpPartial);
}

テストケース

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">
    <title>Test</title>
    <!--
    <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
    <script src="scrollMonitor.js"></script>
    -->

    <script type="text/javascript">

        function isElementPartiallyInViewport(el)
        {
            // Special bonus for those using jQuery
            if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
                el = el[0];

            var rect = el.getBoundingClientRect();
            // DOMRect { x: 8, y: 8, width: 100, height: 100, top: 8, right: 108, bottom: 108, left: 8 }
            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
            var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

            // http://stackoverflow.com/questions/325933/determine-whether-two-date-ranges-overlap
            var vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
            var horInView = (rect.left <= windowWidth) && ((rect.left + rect.width) >= 0);

            return (vertInView && horInView);
        }


        // http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
        function isElementInViewport (el)
        {
            // Special bonus for those using jQuery
            if (typeof jQuery !== 'undefined' && el instanceof jQuery) 
                el = el[0];

            var rect = el.getBoundingClientRect();
            var windowHeight = (window.innerHeight || document.documentElement.clientHeight);
            var windowWidth = (window.innerWidth || document.documentElement.clientWidth);

            return (
                   (rect.left >= 0)
                && (rect.top >= 0)
                && ((rect.left + rect.width) <= windowWidth)
                && ((rect.top + rect.height) <= windowHeight)
            );
        }


        function fnIsVis(ele)
        {
            var inVpFull = isElementInViewport(ele);
            var inVpPartial = isElementPartiallyInViewport(ele);
            console.clear();
            console.log("Fully in viewport: " + inVpFull);
            console.log("Partially in viewport: " + inVpPartial);
        }


        // var scrollLeft = (window.pageXOffset !== undefined) ? window.pageXOffset : (document.documentElement || document.body.parentNode || document.body).scrollLeft,
        // var scrollTop = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop;
    </script>
</head>

<body>
    <div style="display: block; width: 2000px; height: 10000px; background-color: green;">

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <div style="background-color: crimson; display: inline-block; width: 800px; height: 500px;" ></div>
        <div id="myele" onclick="fnIsVis(this);" style="display: inline-block; width: 100px; height: 100px; background-color: hotpink;">
        t
        </div>

        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />
        <br /><br /><br /><br /><br /><br />

        <input type="button" onclick="fnIsVis(document.getElementById('myele'));" value="det" />
    </div>

    <!--
    <script type="text/javascript">

        var element = document.getElementById("myele");
        var watcher = scrollMonitor.create(element);

        watcher.lock();

        watcher.stateChange(function() {
            console.log("state changed");
            // $(element).toggleClass('fixed', this.isAboveViewport)
        });
    </script>
    -->
</body>
</html>

2
isElementPartiallyInViewport同様に非常に便利です。良いですね。
RJ Cuthbertson

isElementInViewport(e)の場合:コードでも画像をロードしていません。これは正しいです:function isElementInViewport(e){var t = e.getBoundingClientRect(); t.top> = 0 && t.left> = 0 && t.bottom <=(window.innerHeight || document.documentElement.clientHeight)&& t.right <=(window.innerWidth || document.documentElement.clientWidth)を返す}
2018

1
@Arun chauhan:私のコードはどれも画像をロードしていないので、なぜそれが必要なのか、そして式は正しいです。
Stefan Steiger

1
@targumon:理由は古いブラウザのサポートです。
Stefan Steiger

1
MDNによると@StefanSteigerはIE9からサポートされているため、window.innerHeightを直接使用するだけで(少なくとも私の場合は)実質的に安全です。ありがとう!
ターグモン

28

getBoundingClientRectを使用するvergeのソースを参照してください。それは次のようなものです:

function inViewport (el) {

    var r, html;
    if ( !el || 1 !== el.nodeType ) { return false; }
    html = document.documentElement;
    r = el.getBoundingClientRect();

    return ( !!r
      && r.bottom >= 0
      && r.right >= 0
      && r.top <= html.clientHeight
      && r.left <= html.clientWidth
    );

}

要素のいずれかの部分がビューポートにあるtrue場合に返されます。


27

私の短くて速いバージョン:

function isElementOutViewport(el){
    var rect = el.getBoundingClientRect();
    return rect.bottom < 0 || rect.right < 0 || rect.left > window.innerWidth || rect.top > window.innerHeight;
}

そして必要に応じてjsFiddle:https ://jsfiddle.net/on1g619L/1/


4
私の解決策はより貪欲でより速く、要素がviewportにピクセルを持っている場合、falseを返します。
エリックチェン

1
私はそれが好きです。簡潔。最初の行で、関数名と括弧の間、および括弧とブレースの間のスペースを削除できます。それらのスペースが好きではなかった。たぶん、テキストエディターだけで色分けされているため、読みやすくなっています。function aaa(arg){statements}私はそれがより速く実行されないことを知っています、代わりに縮小化に該当します。
YK

21

利用可能な機能のjQuery中心のバージョンがなかったことが問題であることがわかりました。Danのソリューションに出会ったとき、jQuery OOスタイルでプログラミングしたい人々に何かを提供する機会を探しました。それは素晴らしくて機敏で、私にとっては魅力のように機能します。

バダビンバダブーム

$.fn.inView = function(){
    if(!this.length) 
        return false;
    var rect = this.get(0).getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );

};

// Additional examples for other use cases
// Is true false whether an array of elements are all in view
$.fn.allInView = function(){
    var all = [];
    this.forEach(function(){
        all.push( $(this).inView() );
    });
    return all.indexOf(false) === -1;
};

// Only the class elements in view
$('.some-class').filter(function(){
    return $(this).inView();
});

// Only the class elements not in view
$('.some-class').filter(function(){
    return !$(this).inView();
});

使用法

$(window).on('scroll',function(){

    if( $('footer').inView() ) {
        // Do cool stuff
    }
});

1
中括弧はifステートメントを閉じるのに十分でしょうか?
calasyr

1
同じクラスの複数の要素で機能させることはできませんでした。
オズのウィズ2017

1
@TheWhizofOz私が回答を更新して、他の考えられるユースケースの例を挙げました。幸運を祈ります。
r3wt 2017年

10

新しいIntersection Observer APIは、この問題に非常に直接対応しています。

Safari、Opera、およびInternet Explorerはまだこれをサポートしていないため、このソリューションにはポリフィルが必要です(ポリフィルはソリューションに含まれています)。

このソリューションでは、ターゲット(観測)であるビューの外にあるボックスがあります。表示されると、ヘッダー上部のボタンは非表示になります。ボックスがビューを離れると表示されます。

const buttonToHide = document.querySelector('button');

const hideWhenBoxInView = new IntersectionObserver((entries) => {
  if (entries[0].intersectionRatio <= 0) { // If not in view
    buttonToHide.style.display = "inherit";
  } else {
    buttonToHide.style.display = "none";
  }
});

hideWhenBoxInView.observe(document.getElementById('box'));
header {
  position: fixed;
  top: 0;
  width: 100vw;
  height: 30px;
  background-color: lightgreen;
}

.wrapper {
  position: relative;
  margin-top: 600px;
}

#box {
  position: relative;
  left: 175px;
  width: 150px;
  height: 135px;
  background-color: lightblue;
  border: 2px solid;
}
<script src="https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver"></script>
<header>
  <button>NAVIGATION BUTTON TO HIDE</button>
</header>
  <div class="wrapper">
    <div id="box">
    </div>
  </div>


3
適切な実装、およびこの回答のリンクによると、HTMLに追加することでサファリで動作するはずです<!DOCTYPE html>
Alon Eitan

IntersectionObserver(将来変更される可能性)実験的な機能です。
Karthik Chintala

2
@KarthikChintala-IEを除くすべてのブラウザでサポートされています-ポリフィルも利用できます。
ランディキャスバーン

のみを検出するので、OPの質問に対処していない変更をIntersectionObserverrootのみにターゲットの移動後にコールバックを発生させます。
Brandon Hill

呼び出しobserveイベントが発生するとすぐに、追跡されている要素の現在の交差状態が通知されます。だから、何らかの方法で-それは対処します。
Mesqalito

8

ここで遭遇したすべての回答は、要素が現在のビューポート内に配置されているかどうかを確認するだけです。しかし、それが見えることを意味するのではありません
指定された要素がコンテンツがオーバーフローしているdiv内にあり、ビューの外にスクロールされた場合はどうなりますか?

これを解決するには、要素がすべての親に含まれているかどうかを確認する必要があります。
私のソリューションはそれを正確に行います:

また、表示する要素の量を指定することもできます。

Element.prototype.isVisible = function(percentX, percentY){
    var tolerance = 0.01;   //needed because the rects returned by getBoundingClientRect provide the position up to 10 decimals
    if(percentX == null){
        percentX = 100;
    }
    if(percentY == null){
        percentY = 100;
    }

    var elementRect = this.getBoundingClientRect();
    var parentRects = [];
    var element = this;

    while(element.parentElement != null){
        parentRects.push(element.parentElement.getBoundingClientRect());
        element = element.parentElement;
    }

    var visibleInAllParents = parentRects.every(function(parentRect){
        var visiblePixelX = Math.min(elementRect.right, parentRect.right) - Math.max(elementRect.left, parentRect.left);
        var visiblePixelY = Math.min(elementRect.bottom, parentRect.bottom) - Math.max(elementRect.top, parentRect.top);
        var visiblePercentageX = visiblePixelX / elementRect.width * 100;
        var visiblePercentageY = visiblePixelY / elementRect.height * 100;
        return visiblePercentageX + tolerance > percentX && visiblePercentageY + tolerance > percentY;
    });
    return visibleInAllParents;
};

このソリューションは、次のような他の事実のために要素が表示されない可能性があるという事実を無視しました opacity: 0

このソリューションをChromeとInternet Explorer 11でテストしました。


8

ここで受け入れられた答えは、ほとんどのユースケースで過度に複雑であることがわかりました。このコードは(jQueryを使用して)うまく機能し、完全に表示される要素と部分的に表示される要素を区別します。

var element         = $("#element");
var topOfElement    = element.offset().top;
var bottomOfElement = element.offset().top + element.outerHeight(true);
var $window         = $(window);

$window.bind('scroll', function() {

    var scrollTopPosition   = $window.scrollTop()+$window.height();
    var windowScrollTop     = $window.scrollTop()

    if (windowScrollTop > topOfElement && windowScrollTop < bottomOfElement) {
        // Element is partially visible (above viewable area)
        console.log("Element is partially visible (above viewable area)");

    } else if (windowScrollTop > bottomOfElement && windowScrollTop > topOfElement) {
        // Element is hidden (above viewable area)
        console.log("Element is hidden (above viewable area)");

    } else if (scrollTopPosition < topOfElement && scrollTopPosition < bottomOfElement) {
        // Element is hidden (below viewable area)
        console.log("Element is hidden (below viewable area)");

    } else if (scrollTopPosition < bottomOfElement && scrollTopPosition > topOfElement) {
        // Element is partially visible (below viewable area)
        console.log("Element is partially visible (below viewable area)");

    } else {
        // Element is completely visible
        console.log("Element is completely visible");
    }
});

必ず$window = $(window)、スクロールハンドラの外部でキャッシュする必要があります。
sam

4

Element.getBoundingClientRect()のサポートとしての最も単純なソリューション完璧になりました

function isInView(el) {
    let box = el.getBoundingClientRect();
    return box.top < window.innerHeight && box.bottom >= 0;
}

これはモバイルブラウザでどのように動作しますか?それらのほとんどは彼らのヘッダがスクロールで上下に行くかで、ビューポートについてバギーであり、異なる動作時にキーボードショーアップ、それはなど、AndroidのまたはiOS場合によっては
ケヴ

@Kevこのメソッドをいつ呼び出すかによって、うまく機能するはずです。これを呼び出してウィンドウのサイズを変更すると、結果が明らかに正しくなくなる可能性があります。必要な機能のタイプに応じて、すべてのサイズ変更イベントで呼び出すことができます。特定のユースケースについて別の質問をして、ここにpingしてください。
レオニース

3

これは、より機能的な方法だと思います。 ダンの答えは、再帰的なコンテキストでは機能しません。

この関数は、HTMLタグまでのレベルを再帰的にテストして要素が他のスクロール可能なdiv内にある場合の問題を解決し、最初のfalseで停止します。

/**
 * fullVisible=true only returns true if the all object rect is visible
 */
function isReallyVisible(el, fullVisible) {
    if ( el.tagName == "HTML" )
            return true;
    var parentRect=el.parentNode.getBoundingClientRect();
    var rect = arguments[2] || el.getBoundingClientRect();
    return (
            ( fullVisible ? rect.top    >= parentRect.top    : rect.bottom > parentRect.top ) &&
            ( fullVisible ? rect.left   >= parentRect.left   : rect.right  > parentRect.left ) &&
            ( fullVisible ? rect.bottom <= parentRect.bottom : rect.top    < parentRect.bottom ) &&
            ( fullVisible ? rect.right  <= parentRect.right  : rect.left   < parentRect.right ) &&
            isReallyVisible(el.parentNode, fullVisible, rect)
    );
};

3

AndroidでGoogle Chromeをズームすると、最も受け入れられている回答が機能しません。Danの回答と組み合わせて、Android上のChromeを説明するには、visualViewportを使用する必要があります。次の例では、垂直方向のチェックのみを考慮し、ウィンドウの高さにjQueryを使用しています。

var Rect = YOUR_ELEMENT.getBoundingClientRect();
var ElTop = Rect.top, ElBottom = Rect.bottom;
var WindowHeight = $(window).height();
if(window.visualViewport) {
    ElTop -= window.visualViewport.offsetTop;
    ElBottom -= window.visualViewport.offsetTop;
    WindowHeight = window.visualViewport.height;
}
var WithinScreen = (ElTop >= 0 && ElBottom <= WindowHeight);

2

これが私の解決策です。要素がスクロール可能なコンテナ内に隠されている場合に機能します。

ここにデモがあります(ウィンドウのサイズを変更してみてください)

var visibleY = function(el){
    var top = el.getBoundingClientRect().top, rect, el = el.parentNode;
    do {
        rect = el.getBoundingClientRect();
        if (top <= rect.bottom === false)
            return false;
        el = el.parentNode;
    } while (el != document.body);
    // Check it's within the document viewport
    return top <= document.documentElement.clientHeight;
};

それがY軸に表示されているかどうかを確認するだけで済みました(スクロールするAjaxのその他のレコード機能)。


2

danの解決策に基づいて、同じページで複数回使用するのが簡単になるように、実装をクリーンアップしました。

$(function() {

  $(window).on('load resize scroll', function() {
    addClassToElementInViewport($('.bug-icon'), 'animate-bug-icon');
    addClassToElementInViewport($('.another-thing'), 'animate-thing');
    // 👏 repeat as needed ...
  });

  function addClassToElementInViewport(element, newClass) {
    if (inViewport(element)) {
      element.addClass(newClass);
    }
  }

  function inViewport(element) {
    if (typeof jQuery === "function" && element instanceof jQuery) {
      element = element[0];
    }
    var elementBounds = element.getBoundingClientRect();
    return (
      elementBounds.top >= 0 &&
      elementBounds.left >= 0 &&
      elementBounds.bottom <= $(window).height() &&
      elementBounds.right <= $(window).width()
    );
  }

});

要素をスクロールして表示するときに、CSSキーフレームアニメーションをトリガーするクラスを追加しています。これは非常に簡単で、ページ上で条件付きでアニメーション化するものが10以上ある場合に特に効果的です。


$window = $(window)スクロールハンドラーの外側は必ずキャッシュする必要があります
Adam Rehal

2

以前の回答のほとんどの使用法は、以下の時点で失敗しています:

-要素のピクセルが表示されているが「コーナー」は表示されていない場合、

-要素がビューポートより大きく中央にある場合、

-それらのほとんどは、ドキュメントまたはウィンドウ内の単一の要素のみをチェックしています

まあ、これらすべての問題について私は解決策を持っています、そしてプラス面は:

- visibleいずれかの側のピクセルのみが表示され、コーナーではない場合、戻ることができます。

- visible要素がビューポートよりも大きい場合でも、戻ることができます。

-あなたはあなたを選ぶことができます parent elementまたはあなたはそれを自動的に選択させることができます、

- 動的に追加された要素でも機能します。

以下のスニペットを確認するとoverflow-scroll、要素のコンテナでの使用の違いが問題を引き起こさないことがわかります。ピクセルがどの側から表示された場合でも、要素がビューポートより大きく、内部が表示されている場合でも、他の回答とは異なりここ他の回答とは異なります。それがまだ機能する要素のピクセル

使い方は簡単です:

// For checking element visibility from any sides
isVisible(element)

// For checking elements visibility in a parent you would like to check
var parent = document; // Assuming you check if 'element' inside 'document'
isVisible(element, parent)

// For checking elements visibility even if it's bigger than viewport
isVisible(element, null, true) // Without parent choice
isVisible(element, parent, true) // With parent choice

crossSearchAlgorithmこれがない場合のデモは、ビューポートチェックのelement3の内部ピクセルよりも大きい要素を表示するのに役立ちます。

ご覧のとおり、element3の内部にいるときは、要素が側面またはコーナーから見えるかどうかを確認しているだけなので、可視かどうかを判断できません。

そしてこれには、要素がビューポートよりも大きいときcrossSearchAlgorithmにも戻ることを可能にするものが含まれていますvisible

JSFiddleで遊ぶ:http ://jsfiddle.net/BerkerYuceer/grk5az2c/

このコードは、要素の一部がビューに表示されているかどうかにかかわらず、より正確な情報のために作成されます。パフォーマンスオプションまたは垂直スライドのみの場合は、これを使用しないでください。このコードは、描画ケースでより効果的です。


1

より良いソリューション:

function getViewportSize(w) {
    var w = w || window;
    if(w.innerWidth != null)
        return {w:w.innerWidth, h:w.innerHeight};
    var d = w.document;
    if (document.compatMode == "CSS1Compat") {
        return {
            w: d.documentElement.clientWidth,
            h: d.documentElement.clientHeight
        };
    }
    return { w: d.body.clientWidth, h: d.body.clientWidth };
}


function isViewportVisible(e) {
    var box = e.getBoundingClientRect();
    var height = box.height || (box.bottom - box.top);
    var width = box.width || (box.right - box.left);
    var viewport = getViewportSize();
    if(!height || !width)
        return false;
    if(box.top > viewport.h || box.bottom < 0)
        return false;
    if(box.right < 0 || box.left > viewport.w)
        return false;
    return true;
}

12
あなたは自分のバージョンがより良い理由を試して説明する必要があります。現状では、他のソリューションとほぼ同じように見えます。
アンディE

1
優れたソリューションはBOX / ScrollContainerを持ち、WINDOWを使用していません(指定されていない場合のみ)。より普遍的な解決策であるレートよりもコードを見てみましょう(多くを検索していました)
2015年

1

IMOはできる限り簡単です。

function isVisible(elem) {
  var coords = elem.getBoundingClientRect();
  return Math.abs(coords.top) <= coords.height;
}

0

要素が要素の現在のビューポートに表示されているかどうかを通知する関数は次のとおりです。

function inParentViewport(el, pa) {
    if (typeof jQuery === "function"){
        if (el instanceof jQuery)
            el = el[0];
        if (pa instanceof jQuery)
            pa = pa[0];
    }

    var e = el.getBoundingClientRect();
    var p = pa.getBoundingClientRect();

    return (
        e.bottom >= p.top &&
        e.right >= p.left &&
        e.top <= p.bottom &&
        e.left <= p.right
    );
}

0

これにより、要素が少なくとも部分的に表示されているかどうかが確認されます(垂直寸法)。

function inView(element) {
    var box = element.getBoundingClientRect();
    return inViewBox(box);
}

function inViewBox(box) {
    return ((box.bottom < 0) || (box.top > getWindowSize().h)) ? false : true;
}


function getWindowSize() {
    return { w: document.body.offsetWidth || document.documentElement.offsetWidth || window.innerWidth, h: document.body.offsetHeight || document.documentElement.offsetHeight || window.innerHeight}
}

0

同じ質問があり、getBoundingClientRect()を使用してそれを理解しました。

このコードは完全に「汎用的」であり、機能するために1回だけ記述する必要があります(ビューポートにあることを知りたい要素ごとに書き出す必要はありません)。

このコードは、ビューポートで垂直ではなく、垂直であるかどうかを確認するだけです。です。この場合、変数(配列)の 'elements'は、ビューポート内で垂直であることを確認するすべての要素を保持しているため、任意の場所にある要素を取得してそこに格納します。

「forループ」は、各要素をループし、ビューポート内で垂直かどうかを確認します。このコードは、ユーザーがスクロールするたびに実行さます。getBoudingClientRect()。topがビューポートの3/4より小さい場合(要素はビューポートの4分の1)、「ビューポート内」として登録されます。

コードは一般的なものなので、ビューポートにある「どの」要素を知りたいでしょう。これを見つけるには、カスタム属性、ノード名、ID、クラス名などで判断できます。

これが私のコードです(機能しない場合は教えてください。InternetExplorer 11、Firefox 40.0.3、Chromeバージョン45.0.2454.85 m、Opera 31.0.1889.174、およびWindows 10のEdgeでテストされています[まだSafariではありません] ])...

// Scrolling handlers...
window.onscroll = function(){
  var elements = document.getElementById('whatever').getElementsByClassName('whatever');
  for(var i = 0; i != elements.length; i++)
  {
   if(elements[i].getBoundingClientRect().top <= window.innerHeight*0.75 &&
      elements[i].getBoundingClientRect().top > 0)
   {
      console.log(elements[i].nodeName + ' ' +
                  elements[i].className + ' ' +
                  elements[i].id +
                  ' is in the viewport; proceed with whatever code you want to do here.');
   }
};

0

これは、私にとって効果的な簡単で小さなソリューションです。

:要素がオーバーフロースクロールを持つ親要素に表示されるかどうかを確認したいとします。

$(window).on('scroll', function () {

     var container = $('#sidebar');
     var containerHeight = container.height();
     var scrollPosition = $('#row1').offset().top - container.offset().top;

     if (containerHeight < scrollPosition) {
         console.log('not visible');
     } else {
         console.log('visible');
     }
})

0

ここでのすべての答えは、要素がビューポート内に完全に含まれているかどうかを判断することであり、何らかの方法で表示されるだけではありません。たとえば、ビューの下部に画像の半分しか表示されていない場合、ここでのソリューションはその「外側」を考慮して失敗します。

を使用して遅延読み込みを行うユースケースがありましIntersectionObserverたが、ポップイン中にアニメーションが発生するため、ページの読み込み時に既に交差している画像を観察したくありませんでした。そのために、次のコードを使用しました。

const bounding = el.getBoundingClientRect();
const isVisible = (0 < bounding.top && bounding.top < (window.innerHeight || document.documentElement.clientHeight)) ||
        (0 < bounding.bottom && bounding.bottom < (window.innerHeight || document.documentElement.clientHeight));

これは基本的に、上境界または下境界が独立してビューポートにあるかどうかを確認するためのチェックです。反対側の端は外側にある場合がありますが、一方の端が内側にある限り、少なくとも部分的には「可視」です。


-1

私はこの関数を使用します(ほとんどの場合xは必要ないため、yが画面内にあるかどうかをチェックするだけです)

function elementInViewport(el) {
    var elinfo = {
        "top":el.offsetTop,
        "height":el.offsetHeight,
    };

    if (elinfo.top + elinfo.height < window.pageYOffset || elinfo.top > window.pageYOffset + window.innerHeight) {
        return false;
    } else {
        return true;
    }

}

-3

同様の課題で、scrollIntoViewIfNeeded()のポリフィルを公開するこの要点を本当に楽しんだ。

答えるために必要なすべてのカンフーはこのブロック内にあります:

var parent = this.parentNode,
    parentComputedStyle = window.getComputedStyle(parent, null),
    parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
    parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
    overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
    overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
    overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
    overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
    alignWithTop = overTop && !overBottom;

thisたとえば、それがそうであるかどうかを知りたい要素を指します。overTopまたはoverBottom、ドリフトを取得するだけです...

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.