Как определить, виден ли элемент DOM в текущем окне просмотра?

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

(Вопрос касается Firefox)

33 ответа

Решение

Обновление: время идет, и наши браузеры тоже. Этот метод больше не рекомендуется, и вам следует использовать решение @Dan ниже ( /questions/9768043/kak-opredelit-viden-li-element-dom-v-tekuschem-okne-prosmotra/9768049#9768049), если вам не требуется поддержка IE<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, который стал лучшей практикой. Использование старого ответа очень медленное, не точное и содержит несколько ошибок.

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


Это решение было протестировано на 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 не применяется. Ваш код может работать локально с вашим CSS на жестком диске, но после установки на удаленный сервер это не удастся.

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

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

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

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

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

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

Обновить

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

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

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


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

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

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

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

Неудачный тест с использованием 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/

И результат:

Пройденный тест, используя isElementVisible

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

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

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

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

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

Теперь у нас есть собственный javascript Intersection Observer API, с помощью которого мы можем обнаруживать элементы, находятся ли они в области просмотра или нет.

Вот пример

Я попробовал ответ Дэна,но алгебра, используемая для определения границ, неверна. Ответ ryanve ближе, но тестируемый элемент должен находиться внутри области просмотра как минимум на 1 пиксель, поэтому попробуйте эту функцию:

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

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

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

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

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/

Новый API Intersection Observer решает этот вопрос очень напрямую.

Для этого решения потребуется полифилл, поскольку Safari, Opera и IE пока не поддерживают это. (полифилл включен в раствор).

В этом решении есть поле вне поля зрения, которое является целью (наблюдается). Когда это появляется, кнопка в верхней части заголовка скрыта. Это показано, как только окно покидает вид.

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>

Простейшее решение, как совместимость Element.getBoundingClientRect(), стало совершенным:

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

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

Бада Бинг Бада Бум

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

});

Существует плагин JQuery называется Inview, который делает работу

Все ответы, с которыми я столкнулся здесь, только проверяют, расположен ли элемент внутри текущего окна просмотра. Но это не значит, что это видно.
Что если данный элемент находится внутри элемента 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.

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

      /**
 * Returns Element placement information in Viewport
 * @link https://stackoverflow.com/a/70476497/2453148
 *
 * @typedef {object} ViewportInfo - Whether the element is…
 * @property {boolean} isInViewport - fully or partially in the viewport
 * @property {boolean} isPartiallyInViewport - partially in the viewport
 * @property {boolean} isInsideViewport - fully inside viewport
 * @property {boolean} isAroundViewport - completely covers the viewport
 * @property {boolean} isOnEdge - intersects the edge of viewport
 * @property {boolean} isOnTopEdge - intersects the top edge
 * @property {boolean} isOnRightEdge - intersects the right edge
 * @property {boolean} isOnBottomEdge - is intersects the bottom edge
 * @property {boolean} isOnLeftEdge - is intersects the left edge
 *
 * @param el Element
 * @return {Object} ViewportInfo
 */
function getElementViewportInfo(el) {

    let result = {};

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

    let insideX = rect.left >= 0 && rect.left + rect.width <= windowWidth;
    let insideY = rect.top >= 0 && rect.top + rect.height <= windowHeight;

    result.isInsideViewport = insideX && insideY;

    let aroundX = rect.left < 0 && rect.left + rect.width > windowWidth;
    let aroundY = rect.top < 0 && rect.top + rect.height > windowHeight;

    result.isAroundViewport = aroundX && aroundY;

    let onTop    = rect.top < 0 && rect.top + rect.height > 0;
    let onRight  = rect.left < windowWidth && rect.left + rect.width > windowWidth;
    let onLeft   = rect.left < 0 && rect.left + rect.width > 0;
    let onBottom = rect.top < windowHeight && rect.top + rect.height > windowHeight;

    let onY = insideY || aroundY || onTop || onBottom;
    let onX = insideX || aroundX || onLeft || onRight;

    result.isOnTopEdge    = onTop && onX;
    result.isOnRightEdge  = onRight && onY;
    result.isOnBottomEdge = onBottom && onX;
    result.isOnLeftEdge   = onLeft && onY;

    result.isOnEdge = result.isOnLeftEdge || result.isOnRightEdge ||
        result.isOnTopEdge || result.isOnBottomEdge;

    let isInX =
        insideX || aroundX || result.isOnLeftEdge || result.isOnRightEdge;
    let isInY =
        insideY || aroundY || result.isOnTopEdge || result.isOnBottomEdge;

    result.isInViewport = isInX && isInY;

    result.isPartiallyInViewport =
        result.isInViewport && result.isOnEdge;

    return result;
}

Я думаю, что это более функциональный способ сделать это. Ответ Дэна не работает в рекурсивном контексте.

Эта функция решает проблему, когда ваш элемент находится внутри других прокручиваемых div, проверяя все уровни, рекурсивно выше HTML-тега, и останавливается на первом значении 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)
    );
};

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

Вот демонстрация (попробуйте изменить размер окна до)

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 its within the document viewport
    return top <= document.documentElement.clientHeight;
};

Мне нужно было только проверить, виден ли он на оси Y (для прокрутки AJAX загрузить больше записей).

Наиболее распространенные ответы не работают при увеличении масштаба в Google Chrome на Android. В сочетании с ответом Дэна для учета Chrome на Android необходимо использовать 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);

Большинство использований в предыдущих ответах терпят неудачу в этих точках:

-Когда виден любой пиксель элемента, но не " угол ",

-Когда элемент больше области просмотра и центрирован,

-Большинство из них проверяют только один элемент внутри документа или окна.

Что ж, для всех этих проблем у меня есть решение и плюсы:

-Вы можете вернуться 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 что полезно для элементов размером больше, чем внутренние пиксели элемента viewport, чтобы увидеть:

function isVisible(element, parent, crossSearchAlgorithm) {
    var rect = element.getBoundingClientRect(),
            prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
        csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
        efp = function (x, y) { return document.elementFromPoint(x, y) };
    // Return false if it's not in the viewport
    if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
        return false;
    }
    var flag = false;
    // Return true if left to right any border pixel reached
    for (var x = rect.left; x < rect.right; x++) {
        if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
        flag = true;
        break;
      }
    }
    // Return true if top to bottom any border pixel reached
    if (flag == false) {
      for (var y = rect.top; y < rect.bottom; y++) {
        if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
          flag = true;
          break;
        }
      }
    }
    if(csa) {
      // Another algorithm to check if element is centered and bigger than viewport
      if (flag == false) {
        var x = rect.left;
        var y = rect.top;
        // From top left to bottom right
        while(x < rect.right || y < rect.bottom) {
          if (element.contains(efp(x,y))) {
            flag = true;
            break;
          }
          if(x < rect.right) { x++; }
          if(y < rect.bottom) { y++; }
        }
        if (flag == false) {
          x = rect.right;
          y = rect.top;
          // From top right to bottom left
          while(x > rect.left || y < rect.bottom) {
            if (element.contains(efp(x,y))) {
              flag = true;
              break;
            }
            if(x > rect.left) { x--; }
            if(y < rect.bottom) { y++; }
          }
        }
      }
    }
    return flag;
}

// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
    var elementList = document.getElementsByClassName("element");
  var console = document.getElementById('console');
    for (var i=0; i < elementList.length; i++) {
      // I did not define parent, so it will be element's parent
    if (isVisible(elementList[i])) {
          console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
      break;
    } else {
        console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
    }
  }
});

// Dynamically added elements
for(var i=4; i <= 6; i++) {
  var newElement = document.createElement("div");
  newElement.id = "element" + i;
  newElement.classList.add("element");
  document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
  width: 300px;
  height: 100px;
  background-color: lightblue;
  overflow-y: auto;
  padding-top: 150px;
  margin: 45px;
}
.element {
  margin: 400px;
  width: 400px;
  height: 320px;
  background-color: green;
}
#element3 {
  position: relative;
  margin: 40px;
  width: 720px;
  height: 520px;
  background-color: green;
}
#element3::before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  margin: 0px;
  width: 740px;
  height: 540px;
  border: 5px dotted green;
  background: transparent;
}
<div id="console"></div>
<div id="container">
    <div id="element1" class="element"></div>
    <div id="element2" class="element"></div>
    <div id="element3" class="element"></div>
</div>

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

И это включает crossSearchAlgorithm что позволяет еще вернуться visible когда элемент больше, чем область просмотра:

function isVisible(element, parent, crossSearchAlgorithm) {
    var rect = element.getBoundingClientRect(),
            prect = (parent != undefined) ? parent.getBoundingClientRect() : element.parentNode.getBoundingClientRect(),
        csa = (crossSearchAlgorithm != undefined) ? crossSearchAlgorithm : false,
        efp = function (x, y) { return document.elementFromPoint(x, y) };
    // Return false if it's not in the viewport
    if (rect.right < prect.left || rect.bottom < prect.top || rect.left > prect.right || rect.top > prect.bottom) {
        return false;
    }
    var flag = false;
    // Return true if left to right any border pixel reached
    for (var x = rect.left; x < rect.right; x++) {
        if (element.contains(efp(rect.top, x)) || element.contains(efp(rect.bottom, x))) {
        flag = true;
        break;
      }
    }
    // Return true if top to bottom any border pixel reached
    if (flag == false) {
      for (var y = rect.top; y < rect.bottom; y++) {
        if (element.contains(efp(rect.left, y)) || element.contains(efp(rect.right, y))) {
          flag = true;
          break;
        }
      }
    }
    if(csa) {
      // Another algorithm to check if element is centered and bigger than viewport
      if (flag == false) {
        var x = rect.left;
        var y = rect.top;
        // From top left to bottom right
        while(x < rect.right || y < rect.bottom) {
          if (element.contains(efp(x,y))) {
            flag = true;
            break;
          }
          if(x < rect.right) { x++; }
          if(y < rect.bottom) { y++; }
        }
        if (flag == false) {
          x = rect.right;
          y = rect.top;
          // From top right to bottom left
          while(x > rect.left || y < rect.bottom) {
            if (element.contains(efp(x,y))) {
              flag = true;
              break;
            }
            if(x > rect.left) { x--; }
            if(y < rect.bottom) { y++; }
          }
        }
      }
    }
    return flag;
}

// Check multiple elements visibility
document.getElementById('container').addEventListener("scroll", function() {
    var elementList = document.getElementsByClassName("element");
  var console = document.getElementById('console');
    for (var i=0; i < elementList.length; i++) {
      // I did not define parent so it will be element's parent
    // and it will do crossSearchAlgorithm
    if (isVisible(elementList[i],null,true)) {
          console.innerHTML = "Element with id[" + elementList[i].id + "] is visible!";
      break;
    } else {
        console.innerHTML = "Element with id[" + elementList[i].id + "] is hidden!";
    }
  }
});
// Dynamically added elements
for(var i=4; i <= 6; i++) {
  var newElement = document.createElement("div");
  newElement.id = "element" + i;
  newElement.classList.add("element");
  document.getElementById('container').appendChild(newElement);
}
#console { background-color: yellow; }
#container {
  width: 300px;
  height: 100px;
  background-color: lightblue;
  overflow-y: auto;
  padding-top: 150px;
  margin: 45px;
}
.element {
  margin: 400px;
  width: 400px;
  height: 320px;
  background-color: green;
}
#element3 {
  position: relative;
  margin: 40px;
  width: 720px;
  height: 520px;
  background-color: green;
}
#element3::before {
  content: "";
  position: absolute;
  top: -10px;
  left: -10px;
  margin: 0px;
  width: 740px;
  height: 540px;
  border: 5px dotted green;
  background: transparent;
}
<div id="console"></div>
<div id="container">
    <div id="element1" class="element"></div>
    <div id="element2" class="element"></div>
    <div id="element3" class="element"></div>
</div>

JSFiddle для игры: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Этот код предназначен для более точной информации, отображается ли какая-либо часть элемента в представлении или нет. Не используйте это для вариантов исполнения или только для вертикальных слайдов! Этот код более эффективен при рисовании.

Основываясь на приведенном выше решении @dan ( /questions/9768043/kak-opredelit-viden-li-element-dom-v-tekuschem-okne-prosmotra/9768049#9768049), я попытался очистить реализацию, чтобы несколько раз использовать ее на одной странице:

$(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 вещей, которые можно условно анимировать на странице.

Надеюсь, поможет!

Как можно проще получить IMO:

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

У меня был тот же вопрос, и я понял это с помощью getBoundingClientRect(). Этот код является полностью "общим" и должен быть написан только один раз, чтобы он работал (вам не нужно выписывать его для каждого элемента, который вы хотите знать, находится в области просмотра). Этот код только проверяет, находится ли он вертикально в окне просмотра, а не горизонтально. В этом случае переменная (массив) 'elements' содержит все элементы, которые вы проверяете, чтобы они были вертикально в окне просмотра, поэтому возьмите любые элементы, которые вы хотите где угодно и сохраните их там. 'For loop', проходит через каждый элемент и проверяет, находится ли он вертикально в области просмотра. Этот код выполняется каждый раз, когда пользователь прокручивает! Если getBoudingClientRect(). Top меньше 3/4 области просмотра (элемент составляет одну четверть в области просмотра), он регистрируется как "в области просмотра". Поскольку код является общим, вам нужно знать, какой элемент находится в области просмотра. Чтобы выяснить это, вы можете определить его по пользовательскому атрибуту, имени узла, идентификатору, имени класса и многим другим. Вот мой код (Скажите, если он не работает, он был протестирован в IE 11, FireFox 40.0.3, Chrome версии 45.0.2454.85 m, Opera 31.0.1889.174 и Edge с Windows 10, [пока не 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.');
   }
};

Надеюсь, это поможет кому-то:-)

Лучшее решение:

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

Вот функция, которая сообщает, виден ли элемент в текущем окне просмотра родительского элемента:

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

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

       const isHTMLElementInView = (element: HTMLElement) => {
  const rect = element?.getBoundingClientRect()

  if (!rect) return
  return rect.top <= window.innerHeight && rect.bottom >= 0
 }

Эта функция проверяет, находится ли элемент в окне просмотра на вертикальном уровне.

Вот фрагмент, чтобы проверить, полностью ли данный элемент виден в его родительском элементе:

      export const visibleInParentViewport = (el) => {
  const elementRect = el.getBoundingClientRect();
  const parentRect = el.parentNode.getBoundingClientRect();

  return (
    elementRect.top >= parentRect.top &&
    elementRect.right >= parentRect.left &&
    elementRect.top + elementRect.height <= parentRect.bottom &&
    elementRect.left + elementRect.width <= parentRect.right
  );
}

Проверяет, является ли элемент хотя бы частично видимым (вертикальный размер):

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

Все ответы здесь определяют, полностью ли элемент содержится в области просмотра, а не просто каким-то образом виден. Например, если внизу вида видна только половина изображения, решения здесь не сработают, учитывая, что это "снаружи".

У меня был вариант использования, когда я выполнял ленивую загрузку через 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));

Это в основном проверка, чтобы увидеть, находится ли верхняя или нижняя граница независимо в области просмотра. Противоположный конец может быть снаружи, но пока один конец находится внутри, он хотя бы частично "виден".

Простое и маленькое решение, которое сработало для меня.

Пример Вы хотите увидеть, видим ли элемент в родительском элементе с прокруткой переполнения.

$(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');
     }
})
Другие вопросы по тегам