[javascript] 현재 뷰포트에서 DOM 요소가 보이는지 어떻게 알 수 있습니까?

HTML 문서에서 DOM 요소가 현재 표시되는지 ( 뷰포트 에 표시) 알 수있는 효율적인 방법이 있습니까?

(질문은 Firefox에 관한 것입니다.)



답변

업데이트 : 시간이 흐르고 브라우저도 있습니다. 이 기술은 더 이상 권장하지 않습니다 그리고 당신은 사용해야 단의 솔루션을 사용하면 7 전에 인터넷 익스플로러의 지원 버전이 필요하지 않은 경우.

독창적 인 솔루션 (현재 구식) :

현재 뷰포트에서 요소가 완전히 보이는지 확인합니다.

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


답변

이제 대부분의 브라우저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는 줌 / 핀치 크로스 브라우저를 처리해야 합니다. 그렇지 않으면 첫 번째 또는 두 번째 링크가 도움이됩니다.

DOM수정 하면 요소의 가시성에 영향을 줄 수 있습니다. 이를 제어하고 handler()수동으로 호출 해야합니다. 불행히도, 우리는 크로스 브라우저 onrepaint이벤트 가 없습니다 . 반면에 요소의 가시성을 변경할 수있는 DOM 수정에 대해서만 최적화하고 재확인을 수행 할 수 있습니다.

이 시점에서 CSS가 적용되었다는 보장이 없으므로 jQuery $ (document) .ready () 에서만 사용 하지 마십시오 . 코드는 하드 드라이브에서 CSS와 로컬로 작동 할 수 있지만 일단 원격 서버에 넣으면 실패합니다.

후에 DOMContentLoaded스타일이 적용 되지만 이미지는 아직로드되지 않습니다 . 따라서 window.onload이벤트 리스너를 추가해야합니다 .

아직 줌 / 핀치 이벤트를 잡을 수 없습니다.

마지막 수단은 다음 코드 일 수 있습니다.

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

웹 페이지가있는 탭이 활성화되어 있고 가시적인지 신경 쓰면 HTML5 API 의 멋진 기능 pageVisibiliy 를 사용할 수 있습니다.

TODO :이 방법은 두 가지 상황을 처리하지 않습니다.

  • 사용하여 겹침 z-index
  • overflow-scroll요소의 컨테이너에서 사용
  • 새로운 것을 시도하십시오- 교차 관찰자 API 설명

답변

최신 정보

최신 브라우저에서는 다음과 같은 이점을 제공하는 Intersection Observer API 를 확인할 수 있습니다 .

  • 스크롤 이벤트를 듣는 것보다 나은 성능
  • 교차 도메인 iframe에서 작동
  • 요소가 다른 요소를 방해하거나 교차하는지 알 수 있습니다.

Intersection Observer는 본격적인 표준으로 나아가고 있으며 Chrome 51 이상, Edge 15 이상 및 Firefox 55 이상에서 이미 지원되며 Safari 용으로 개발 중입니다. 도있다 polyfill 이 없습니다.


이전 답변

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- 색인으로 테스트중인 요소는 앞에있는 요소가 실제로 일부를 숨기지 않더라도 숨겨진 것으로 식별됩니다. 여전히이 방법은 Dan의 솔루션에서 다루지 않는 경우에 사용됩니다.

모두 element.getBoundingClientRect()document.elementFromPoint()CSSOM 작업 초안 사양의 일부이며 이후 적어도 IE 6에서 지원하고, 가장 오랫동안 데스크탑 브라우저 (하지 완벽하게이기는하지만). 자세한 내용 은이 기능에 대한 Quirksmode 를 참조하십시오.

contains()에서 반환 한 document.elementFromPoint()요소가 가시성 테스트중인 요소의 하위 노드 인지 확인하는 데 사용됩니다 . 리턴 된 요소가 동일한 요소 인 경우에도 true를 리턴합니다. 이것은 단지 수표를 더욱 강력하게 만듭니다. 모든 주요 브라우저에서 지원되며 Firefox 9.0이 마지막으로 추가됩니다. 이전 Firefox 지원에 대해서는이 답변의 기록을 확인하십시오.

가시성을 위해 요소 주위에 더 많은 점을 테스트하려는 경우 (예 : 요소가 50 % 이상으로 덮여 있지 않은지 확인하려면) 응답의 마지막 부분을 조정하는 데 많은 시간이 걸리지 않습니다. 그러나 모든 픽셀을 100 % 볼 수 있는지 확인하면 속도가 매우 느려질 수 있습니다.


답변

나는 Dan의 대답을 시도했다 . 그러나 경계를 결정하는 데 사용되는 대수는 요소가 뷰포트 크기보다 작고 뷰포트 내부에 완전히 있어야해서 true쉽게 잘못된 결과 를 가져옵니다 . 요소가 뷰포트에 있는지 여부를 결정하려면 ryanve의 답변 이 가깝지만 테스트중인 요소가 뷰포트와 겹치므로 다음을 시도하십시오.

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


답변

공공 서비스 :
Dan은 올바른 계산 (특히 휴대 전화 화면에서> 창일 수 있음), 올바른 jQuery 테스트 및 isElementPartiallyInViewport 추가와 함께 대답합니다.

그런데 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>


답변

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경우 어떤 요소의 일부가 뷰포트에 있습니다.


답변

더 짧고 빠른 버전 :

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/