Как определить, виден ли элемент DOM в текущем окне просмотра? - PullRequest
837 голосов
/ 24 сентября 2008

Существует ли эффективный способ определить, является ли элемент DOM (в документе HTML) видимым в данный момент (отображается в области просмотра )?

(вопрос относится к Firefox)

Ответы [ 23 ]

1287 голосов
/ 26 сентября 2011

Сейчас большинство браузеров поддерживают getBoundingClientRect метод, который стал лучшей практикой. Использование старого ответа очень медленно , не точно и имеет несколько ошибок .

Правильное решение выбрано почти никогда не точное . Вы можете узнать больше о его ошибках.


Это решение было протестировано на IE7 +, iOS5 + Safari, Android2 +, Blackberry, Opera Mobile и IE Mobile 10 .


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); // IE9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

Если вы делаете какие-либо модификации DOM, они, конечно, могут изменить видимость вашего элемента.

Указания и общие подводные камни:

Может быть, вам нужно отслеживать масштабирование страницы / сжатие мобильного устройства? jQuery должен обрабатывать масштабирование / масштабирование кросс-браузер, иначе первый или второй ссылка должна вам помочь.

Если вы измените DOM , это может повлиять на видимость элемента. Вы должны взять это под контроль и позвонить handler() вручную. К сожалению, у нас нет кросс-браузерного события onrepaint. С другой стороны, это позволяет нам проводить оптимизацию и перепроверять только те модификации DOM, которые могут изменить видимость элемента.

Никогда и никогда используйте его внутри jQuery $ (document) .ready () только потому, что гарантия не применяется в данный момент. Ваш код может работать локально с вашим CSS на жестком диске, но после установки на удаленный сервер он потерпит неудачу.

После запуска DOMContentLoaded применяются стили , но изображения еще не загружены . Итак, мы должны добавить window.onload слушатель событий.

Мы пока не можем поймать событие увеличения / увеличения.

В крайнем случае может быть следующий код:

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

Вы можете использовать потрясающую функцию pageVisibiliy HTML5 API, если вам важно, если вкладка с вашей веб-страницей активна и видима.

TODO: этот метод не обрабатывает две ситуации:

318 голосов
/ 24 сентября 2008

Обновление: Время идет, как и наши браузеры. Этот метод больше не рекомендуется , и вам следует использовать решение @ Dan ниже (https://stackoverflow.com/a/7557433/5628), если вам не требуется поддержка IE <7. </p>

Исходное решение (сейчас устарело):

Это проверит, является ли элемент полностью видимым в текущем окне просмотра:

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
  );
}
149 голосов
/ 04 марта 2013

Обновление

В современных браузерах вы можете попробовать API-интерфейс Intersection Observer , который обеспечивает следующие преимущества:

  • Лучшая производительность, чем прослушивание событий прокрутки
  • Работает в кросс-доменных фреймах
  • Может сказать, если элемент препятствует / пересекает другой

Intersection Observer находится на пути к тому, чтобы стать полноценным стандартом и уже поддерживается в Chrome 51+, Edge 15+ и Firefox 55+ и находится в стадии разработки для Safari. Также имеется полифилл .


Предыдущий ответ

Есть некоторые проблемы с ответом , предоставленным Дэном , которые могут сделать его неподходящим для некоторых ситуаций. Некоторые из этих проблем указаны в его ответе внизу, что его код даст ложные срабатывания для элементов, которые:

  • Скрыт другим элементом перед проверяемым
  • За пределами видимой области родительского или предкового элемента
  • Элемент или его дочерние элементы скрыты с помощью свойства CSS clip

Эти ограничения продемонстрированы в следующих результатах простого теста :

Failed test, using isElementInViewport

Решение: isElementVisible()

Вот решение этих проблем, с приведенным ниже результатом теста и объяснением некоторых частей кода.

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || doc.documentElement.clientWidth,
        vHeight  = window.innerHeight || doc.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/

И результат:

Passed test, using isElementVisible

Дополнительные примечания

Однако этот метод не лишен собственных ограничений. Например, тестируемый элемент с более низким z-индексом, чем другой элемент в том же месте, будет идентифицирован как скрытый, даже если элемент перед собой на самом деле не скрывает какую-либо его часть. Тем не менее, этот метод имеет свои применения в некоторых случаях, которые решение Дана не охватывает.

И element.getBoundingClientRect(), и document.elementFromPoint() являются частью спецификации рабочего проекта CSSOM и поддерживаются как минимум в IE 6 и более поздних версиях и в большинстве настольных браузеров в течение длительного времени (хотя и не идеально). См. Quirksmode для этих функций для получения дополнительной информации.

contains() используется, чтобы увидеть, является ли элемент, возвращаемый document.elementFromPoint(), дочерним узлом элемента, который мы проверяем на видимость. Он также возвращает true, если возвращаемый элемент является тем же элементом. Это только делает проверку более надежной. Он поддерживается во всех основных браузерах, Firefox 9.0 является последним из них, добавившим его. Для более старой поддержки Firefox, проверьте историю этого ответа.

Если вы хотите проверить больше точек вокруг элемента на предмет видимости, т. Е. Чтобы убедиться, что элемент не покрыт более чем, скажем, на 50%, то для корректировки последней части ответа не потребуется много , Однако имейте в виду, что, вероятно, будет очень медленно, если вы проверите каждый пиксель, чтобы убедиться, что он виден на 100%.

57 голосов
/ 29 апреля 2013

Я попытался ответ Дэна однако алгебра, используемая для определения границ, означает, что элемент должен быть как ≤ размера области просмотра, так и полностью внутри области просмотра, чтобы легко получить 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() */;
}
30 голосов
/ 25 сентября 2014

В качестве государственной службы:
Ответ Дэна с правильными вычислениями (элемент может быть> окном, особенно на экранах мобильных телефонов) и корректным тестированием 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>
25 голосов
/ 03 мая 2010

Существует плагин jQuery с именем inview , который выполняет работу

24 голосов
/ 14 сентября 2012

См. Источник verge , который использует getBoundingClientRect . Это как:

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, если любая часть элемента находится в области просмотра.

23 голосов
/ 23 апреля 2014

моя более короткая и быстрая версия.

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/

19 голосов
/ 02 августа 2015

Меня беспокоит, что не существует jQuery центрированной версии функциональности. Когда я наткнулся на решение Дэна , я увидел возможность предоставить что-то для людей, которые любят программировать в стиле jQuery OO Обязательно прокрутите вверх и оставьте отзыв на код Дэна. Это красиво и быстро и работает как шарм для меня.

Bada Bing Bada Boom

$.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
//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
    }

});
8 голосов
/ 30 января 2015

Я считаю, что принятый ответ здесь слишком сложен для большинства случаев использования. Этот код хорошо выполняет свою работу (используя 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");
    }
});
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...