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

950

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

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

benzaita
источник
Зависит от того, что вы подразумеваете под видимым. Если вы имеете в виду, отображается ли он в данный момент на странице, учитывая позицию прокрутки, вы можете рассчитать ее на основе смещения элементов y и текущей позиции прокрутки.
Рориф
18
Пояснение: видимое, как внутри текущего отображаемого прямоугольника. Видимый, как в не скрытом или отображаемом виде: нет? Видно, как не за чем-то еще в Z-порядке?
Адам Райт
6
как внутри текущего отображаемого прямоугольника. Спасибо. Перефразировать.
benzaita
2
Обратите внимание, что все ответы здесь дадут вам ложное срабатывание для элементов, которые находятся за пределами видимой области их родительского элемента (например, со скрытым переполнением), но внутри области области просмотра.
Энди Э
1
Есть миллион ответов, и большинство из них смехотворно длинные. Смотрите здесь для
двухслойной

Ответы:

347

Обновление: время идет, и наши браузеры тоже. Этот метод больше не рекомендуется, и вам следует использовать решение Дэна, если вам не требуется поддерживать версию Internet Explorer до 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
  );
}
Prestaul
источник
1
В оригинальной опубликованной функции произошла ошибка. Необходимо сохранить ширину / высоту перед переназначением эл ...
Prestaul
15
Что если элемент живет в прокручиваемом элементе div и прокручивается вне поля зрения?
Амартынов
2
Пожалуйста, ознакомьтесь с более новой версией скрипта ниже
Dan
1
Также любопытно на вопрос @ amartynov. Кто-нибудь знает, как просто сказать, если элемент скрыт из-за переполнения элемента предка? Бонус, если это можно обнаружить независимо от того, насколько глубоко вложен ребенок.
Эрик Нгуен
1
@deadManN повторяется через DOM, как известно, медленно. Этого достаточно, но производители браузеров также создали его getBoundingClientRectспециально для поиска координат элементов ... Почему бы нам не использовать его?
Престол
1403

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

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

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

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

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

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

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

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

  • перекрытие с помощью z-index
  • использование overflow-scrollв контейнере элемента
  • попробуйте что-то новое - объяснил API Intersection Observer
Dan
источник
6
Я использую это решение (остерегайтесь опечатки "botom", хотя). Есть также кое-что, о чем нужно знать, когда рассматриваемый нами элемент будет содержать изображения. Chrome (как минимум) должен ждать загрузки изображения, чтобы иметь точное значение boundingRectangle. Похоже, у Firefox нет этой «проблемы»
Клаудио
4
Работает ли, когда у вас включена прокрутка в контейнере внутри тела. Например, здесь это не работает - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). innerele присутствует внутри контейнера, для которого включена прокрутка.
agaase
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, просто дружеское напоминание о передаче объекта HTML DOM (например, isElementInViewport(document.getElementById('elem'))), а не объект jQuery (например, isElementInViewport($("#elem))). JQuery эквивалент добавить [0]следующим образом: isElementInViewport($("#elem)[0]).
Jiminy
12
el is not defined
M_Willett
176

Обновить

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

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

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


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

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

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

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

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

Оба 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»: snook.ca/archives/html_and_css/hiding-content-for-accessibility
Kevin Лампинг
@klamping: хороший улов, спасибо. Я скопировал это прямо из своего кода, где я использовал docв качестве псевдонима для document. Да, мне нравится думать об этом как о приличном решении для крайних случаев.
Энди Э,
2
Для меня это не работает. Но inViewport () в предыдущем ответе работает в FF.
Сатья Пракаш
9
Также может быть полезно проверить, что центр элемента виден, если у вас закругленные углы или применено преобразование, поскольку ограничивающие углы могут не возвращать ожидаемый элемент:element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))
Джаред
2
Не работал на входах для меня (хром канарейка 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легко получить ложные отрицания. Если вы хотите определить, находится ли элемент в окне просмотра, ответ 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() */;
}
WALF
источник
33

В качестве общедоступной услуги:
ответ Дэна с правильными вычислениями (элемент может быть> окном, особенно на экранах мобильных телефонов) и корректным тестированием 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>
Стефан Штайгер
источник
2
isElementPartiallyInViewportэто тоже очень полезно. Хороший.
RJ Катбертсон
Для isElementInViewport (e): Ваш код даже не загружает изображения. Это правильно: function isElementInViewport (e) {var t = e.getBoundingClientRect (); return t.top> = 0 && t.left> = 0 && t.bottom <= (window.innerHeight || document.documentElement.clientHeight) && t.right <= (window.innerWidth || document.documentElement.clientWidth) }
Арун Чаухан
1
@Arun chauhan: Ни один из моего кода не загружает изображения, так зачем это нужно, и формула верна.
Стефан Штайгер
1
@targumon: причина в поддержке старых браузеров.
Стефан Штайгер
1
@StefanSteiger согласно MDN поддерживается с IE9, поэтому практически безопасно (по крайней мере, в моем случае) просто использовать window.innerHeight напрямую. Спасибо!
Targumon
28

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

ryanve
источник
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
Мое решение более жадное и более быстрое, когда элемент имеет какой-либо пиксель в области просмотра, он возвращает false.
Эрик Чен
1
Мне это нравится. Сжатый. Вы можете удалить пробелы между именем функции и круглыми скобками, а также между круглыми скобками и фигурными скобками в первой строке. Никогда не любил эти места. Может быть, это всего лишь мой текстовый редактор, который все это кодирует цветом, который все еще облегчает чтение. функция aaa (arg) {операторы} Я знаю, что это не заставляет это выполняться быстрее, вместо этого подпадает под минимизацию.
Ю.К.
21

Меня беспокоило, что не было jQuery- ориентированной версии функциональности. Когда я наткнулся на решение Дэна, я увидел возможность предоставить что-то для людей, которые любят программировать в стиле 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
    }
});
r3wt
источник
1
Будет ли фигурная скобка достаточной для закрытия оператора if?
calasyr
1
Я не мог заставить его работать с несколькими элементами одного и того же класса.
The Whiz of Oz
1
@TheWhizofOz Я обновил свой ответ, чтобы привести примеры других возможных вариантов использования, которые вы затронули. удачи.
r3wt
10

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

Для этого решения потребуется полифилл, так как 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
Хорошая реализация, и по ссылке в этом ответе она должна работать на сафари, добавив <!DOCTYPE html>в HTML
Алон Эйтан
Обратите внимание, что IntersectionObserverэто экспериментальная функция (которая может измениться в будущем).
Картик Чинтала,
2
@KarthikChintala - он поддерживается во всех браузерах, кроме IE - и есть также доступный полифилл.
Рэнди Кэсберн
Не отвечает на вопрос OP, так как обнаруживает только изменения : IntersectionObserverтолько вызывает обратный вызов после перемещения цели относительно корня.
Брэндон Хилл
Когда вызывается observeсобытие, немедленно сообщается текущее состояние пересечения отслеживаемого элемента. Так что, в некотором роде - это адреса.
Мескалито
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.

Domysee
источник
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)вне обработчика прокрутки.
Сэм
4

Самое простое решение , как поддержка Element.getBoundingClientRect () уже стал совершенным :

function isInView(el) {
    let box = el.getBoundingClientRect();
    return box.top < window.innerHeight && box.bottom >= 0;
}
leonheess
источник
Как это ведет себя в мобильных браузерах? Большинство из них содержат ошибки в области просмотра, заголовок идет вверх или вниз при прокрутке, и различное поведение при отображении клавиатуры, в зависимости от того, Android это или ios и т. Д.
Kev
@Kev Должно работать нормально, в зависимости от того, когда вы вызываете этот метод. Если вы вызываете его, а затем изменяете размер окна, результат, возможно, больше не будет правильным. Вы можете вызывать это на каждом событии изменения размера в зависимости от типа функциональности, которую вы хотите. Не стесняйтесь задавать отдельный вопрос о вашем конкретном случае использования и пинговать меня здесь.
Леоне
3

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

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

Большинство принятых ответов не работают при масштабировании в 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);
Dakusan
источник
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

Основываясь на решении Дана , я попытался очистить реализацию, чтобы несколько раз использовать ее на одной странице:

$(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 вещей для условной анимации на странице.

Pirijan
источник
Вы должны определенно кэшировать $window = $(window)вне обработчика прокрутки
Адам Рехал
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/

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

Berker Yüceer
источник
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;
}
rainyjune
источник
12
Вы должны попытаться объяснить, почему ваша версия лучше. В нынешнем виде он выглядит примерно так же, как и другие решения.
Энди Э
1
Отличное решение, оно имеет BOX / ScrollContainer и не использует WINDOW (только если оно не указано). Взгляните на код, чем оцените, это более универсальное решение (много его искал)
тет
1

Как можно проще, ИМО:

function isVisible(elem) {
  var coords = elem.getBoundingClientRect();
  return Math.abs(coords.top) <= coords.height;
}
JuanM.
источник
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
    );
}
ssten
источник
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}
}
Lumic
источник
0

У меня был тот же вопрос, и я понял это с помощью getBoundingClientRect ().

Этот код является полностью «универсальным» и должен быть написан только один раз, чтобы он работал (вам не нужно выписывать его для каждого элемента, который вы хотите знать, находится в области просмотра).

Этот код только проверяет, находится ли он вертикально в области просмотра, а не горизонтально . В этом случае переменная (массив) 'elements' содержит все элементы, которые вы проверяете, чтобы они были вертикально в окне просмотра, поэтому возьмите любые элементы, которые вы хотите где угодно и сохраните их там.

Цикл for проходит по каждому элементу и проверяет, находится ли он вертикально в области просмотра. Этот код выполняется каждый раз, когда пользователь прокручивает! Если getBoudingClientRect (). Top меньше 3/4 области просмотра (элемент составляет одну четверть в области просмотра), он регистрируется как «в области просмотра».

Так как код является общим, вы захотите узнать, какой элемент находится в области просмотра. Чтобы выяснить это, вы можете определить его по пользовательскому атрибуту, имени узла, идентификатору, имени класса и многим другим.

Вот мой код (скажите, если он не работает; он был протестирован в Internet Explorer 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.');
   }
};
www139
источник
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

Я использую эту функцию (она только проверяет, является ли у экранным, поскольку в большинстве случаев х не нужен)

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

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

Philzen
источник