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

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

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

Уточнение: Видно, как в пределах текущего отображаемого прямоугольника. Видимый, не скрытый или отображаемый: нет? Видно, а не за чем-то еще в Z-порядке?

Adam Wright 24.09.2008 01:31

как внутри текущего отображаемого прямоугольника. Спасибо. Перефразировал.

benzaita 24.09.2008 01:40

Не могли бы вы также обновить название?

EoghanM 03.12.2008 02:48

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

Andy E 04.03.2013 17:48

Я добавил мое собственное решение, который решает эту проблему

Andy E 04.03.2013 18:18

Учитывает ли какое-либо из этих решений z-индекс узла dom и как это может повлиять на видимость, в частности, путем скрытия элементов с более низким z-индексом?

Dexygen 08.05.2013 19:44

Ни один из ответов не работает с сгенерированными элементами с абсолютным позиционированием.

thednp 21.03.2015 15:38

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

roryf 24.09.2008 01:29

Ответ 2019 года: IntersectionObserver

Ted Fitzpatrick 08.10.2019 23:57

Я знаю, что это устарело, но это мое решение. Работает отлично! Демо Codesandbox

Enesy 09.01.2020 18:52

Есть миллион ответов, и большинство из них до смешного длинные. См. Здесь для двухстрочного

leonheess 10.01.2020 17:19
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1 042
11
567 776
26
Перейти к ответу Данный вопрос помечен как решенный

Ответы 26

Ответ принят как подходящий

Обновлять: Время идет, и наши браузеры тоже. Этот метод больше не рекомендуется, и вы должны использовать Решение Дэна, если вам не нужно поддерживать версию 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 24.09.2008 06:56

Также было бы разумно немного абстрагироваться от этого и создать некоторые служебные функции. Я использую тот, который называется getViewport, который возвращает верхнюю / левую / нижнюю / правую часть видимого окна, и тот, который называется getPosition, который находит верхний / левый угол элемента.

Prestaul 24.09.2008 07:01

Я пытаюсь получить эту функциональность в расширении Firefox, поэтому на случай, если кто-то еще окажется в такой же ситуации ... (веб-разработчики, вы, конечно, можете полностью игнорировать этот комментарий) Эта функция отлично работает в этом контексте, ЗА ИСКЛЮЧЕНИЕМ того, что window относится не к тому объекту, поэтому просто поместите var window = el.ownerDocument.defaultView в начало функции, и все готово.

MatrixFrog 23.09.2010 04:20

Что, если элемент находится в прокручиваемом div и прокручивается за пределы представления?

amartynov 06.03.2011 11:42

Пожалуйста, просмотрите более новую версию скрипта ниже

Dan 26.09.2011 19:29

В расширении Firefox el.ownerDocument.defaultView.innerWidth не является точным представлением области просмотра (хотя и window). Например, будет неправильно, если вы сделаете окно firefox уже, чем самая широкая установленная панель инструментов.

Brian 16.11.2011 01:50

Также интересно узнать о вопросе @amartynov. Кто-нибудь знает, как просто определить, скрыт ли элемент из-за переполнения элемента-предка? Бонус, если это можно обнаружить независимо от того, насколько глубоко вложен ребенок.

Eric Nguyen 08.11.2012 03:51

Мне интересно, почему вы рекомендуете ответ ниже, пока он чистый javascript и, похоже, не использует ничего неподдерживаемого

deadManN 05.07.2015 16:45

@deadManN, как известно, проходит через DOM медленно. Этого достаточно, но производители браузеров также создали getBoundingClientRect специально для определения координат элементов ... Почему бы нам не использовать его?

Prestaul 06.07.2015 18:25

Функция отлично работает, если нет блоков с полосами прокрутки. Предлагаю доработать блок while и окончательную проверку: while(elm.offsetParent) {elm = elm.offsetParent; top += elm.offsetTop - elm.scrollTop; left += elm.offsetLeft - elm.scrollLeft; } return (top < (window.innerHeight) && left < (window.innerWidth) && (top + height) > 0 && (left + width) > 0 );

evpozdniakov 24.11.2015 23:47

Я предлагаю добавить developer.mozilla.org/en-US/docs/Web/API/…, хотя он не поддерживает IE11, поэтому вам придется использовать резервный метод.

George 09.11.2017 22:13

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

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

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

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

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

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

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

Я использую это решение (однако остерегайтесь опечатки "дно"). Также есть кое-что, о чем следует знать, когда рассматриваемый нами элемент будет содержать изображения. Chrome (по крайней мере) должен дождаться загрузки изображения, чтобы получить точное значение boundingRectangle. Похоже, у Firefox нет этой "проблемы"

Claudio 20.10.2011 16:38

window.innerHeight и innerWidth не определены в IE. На howtocreate.co.uk/tutorials/javascript/browserwindow есть хорошая статья, показывающая различные способы получения высоты документа ... document.body.clientHeight кажется лучшим способом в целом. Я соответствующим образом отредактировал ваш ответ.

Joe Strommen 30.03.2012 18:47

вот мой вариант проверки, он просто проверяет, виден ли элемент в vp вообще: gist.github.com/2300296

Matas Petrikas 04.04.2012 14:41

Если вам не нужно поддерживать IE 5.5 и ниже (чего, вероятно, нет), вы можете использовать (window.innerWidth || document.documentElement.clientWidth) и его эквиваленты для кросс-браузерного решения.

Andy E 04.03.2013 17:42

@Claudio: проблема в том, что страница ПОВТОРЯЕТСЯ после загрузки каждого изображения. Итак, если вы вызовете мою функцию до того, как появится изображение, вы получите правильный результат, но он действителен в течение нескольких миллисекунд, пока не появится изображение и контейнер не будет расширен.

Dan 03.04.2013 22:38

@MatasPetrikas в вашей версии есть ошибки, но вы их исправили в комментариях

Dan 05.06.2013 13:47

Почему это место после документа. , в "rect.bottom <= (window.innerHeight || document. documentElement.clientHeight)" ??

Nobita 30.10.2013 20:58

Работает ли это, когда у вас включена прокрутка в контейнере внутри тела. Например, здесь это не работает - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). innerele находится внутри контейнера с включенной прокруткой.

agaase 08.12.2013 13:04

Нужно ли мне использовать все эти DOMContentLoaded load resize scroll? Любые проблемы с производительностью и т. д.!

Satya Prakash 28.12.2013 11:09

Я вижу, что он не работает с другим элементом jQuery(window).on, кроме окна, но я вижу, что обработчик вызывается МНОГО раз при каждой прокрутке. Есть ли способ привязать событие к конкретному элементу?

Satya Prakash 28.12.2013 15:44

В расчетах предполагается, что элемент меньше экрана. Если у вас высокие или широкие элементы, правильнее будет использовать return (rect.bottom >= 0 && rect.right >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight) && rect.left <= (window.innerWidth || document.documentElement.clientWidth));.

Roonaan 21.02.2014 11:29

Совет: тем, кто пытается реализовать это с помощью jQuery, просто напоминание о необходимости передать объект HTML DOM (например, isElementInViewport(document.getElementById('elem'))), а не объект jQuery (например, isElementInViewport($("#elem))). Эквивалент jQuery - добавить [0] следующим образом: isElementInViewport($("#elem)[0]).

jiminy 12.04.2014 11:44
stackoverflow.com/questions/995914/…
Dan 14.04.2014 15:20

Блестяще. Если не использовать jQuery, instanceof jQuery выдает ошибку.

RobG 11.09.2014 09:04

@ Dan - if (jQuery != undefined) ничего не исправляет, он все равно выдаст ошибку ссылки, если jQuery не был определен. Способ исправить это (то есть предотвратить ошибку, если jQuery не был определен или не является конструктором) - это if (typeof jQuery == 'function' && el.instanceof jQuery), что не является пуленепробиваемым (потому что экземпляр не особенно полезно), но должно быть достаточно хорошим.

RobG 24.09.2014 10:04

Вы может поймаете событие масштабирования / ущемления. Событие resize запускается, когда изменяется размер окна или масштабируется страница.

rvighne 16.11.2014 19:24

Вот моя версия, поддерживает элементы контейнера (например, если элемент находится внутри другого прокручиваемого элемента) и полную или частичную видимость (хотя только jQuery): gist.github.com/anonymous/f09b5cd487b5ad55c46e

Mahn 13.01.2015 22:52

@Mahn - Мне нравится ваше решение, похоже, именно то, что мне нужно. Можете привести пример использования? Я не понимаю, как ссылаться на этот тип функции.

Todd Prouty 21.05.2015 22:44

После объявления @ToddProuty вы можете выполнить $("#elementInside").isInViewport("#parentElement") (возвращает true, если #elementInside полностью виден в #parentElement) или $("#elementInside").isInViewport("#parentElement", true) (возвращает true, если #elementInside хотя бы частично виден в #parentElement) или $("#elementInside").isInViewport() (возвращает true, если виден в главном окне )

Mahn 22.05.2015 01:30

Этот подход не работает при переполнении элементов, которые в текущем окне просмотра невидимы.

Lewis 07.06.2015 11:44

По крайней мере, в инструментах разработчика в Firefox getBoundingClientRect() дает мне значения немного шире, чем область просмотра для элементов с width:100%, то есть область просмотра составляет 815 пикселей, а значение «справа» - 815.2000122070312. Я использовал Math.floor() в свойствах rect, чтобы обойти это.

RJ Cuthbertson 27.10.2015 22:20

Как мне использовать это, чтобы получить все видимые элементы в текущем окне просмотра? Для бонуса у меня будут координаты относительно области просмотра.

SuperUberDuper 10.12.2015 15:52

Я хотел бы проверить наличие нескольких элементов jQuery, которые могут находиться в (или вне) области просмотра. Кажется, этот сценарий проверяет только один элемент. Когда в текущем окне просмотра находится несколько элементов, я хочу получить последний элемент, находящийся в текущем окне просмотра. Как это можно было сделать с помощью этого скрипта? Или какая-то модификация?

Vernon 13.04.2016 13:17

@SuperUberDuper Я создал Gist, который возвращает самый нижний элемент в вашем текущем окне просмотра. Если вы просто вернете массив visibleElements в этой функции, вы получите именно то, что вам нужно. См .: gist.github.com/vernondegoede/5b5638706aec5a80611010c88609d1‌ 70

Vernon 13.04.2016 15:04

Что делать, если у вас есть коллекция элементов?

Donato 27.08.2016 02:33

Алгебра этого требует пояснения. Это больше похоже на проверку того, является ли элемент полностью вне области просмотра. Он возвращает «невидимый» для элементов, которые только частично находятся за пределами области просмотра. См. Ответ @Walf для более интуитивной версии. (из isElementInViewport). Прослушивание событий отлично работает.

iPherian 22.09.2016 02:31

el is not defined

M_Willett 18.10.2016 13:47

Эта дополнительная функция полезна для определения, когда прокручивать, когда она не видна: function alignToTop(el) { let rect = el.getBoundingClientRect(); return rect.top < 0 || rect.bottom <= (window.innerHeight || document.documentElement.clientHeight); }

Corey Alix 12.01.2017 22:35

Я обнаружил, что это работает почти идеально, за исключением чрезвычайно быстрой прокрутки, например. нажатие cmd вниз или cmd вверх иногда может быть неточным. Я нашел альтернативу - использовать github.com/stutrek/scrollMonitor, который всегда на 100% точен для меня и очень эффективен.

svnm 03.02.2017 04:53

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

matteo 26.11.2017 00:44

const onVisibilityChange = (el, обратный вызов) => () => isElementInViewport (el) && обратный вызов ()

Miguel Palau 06.04.2018 00:54

Этот ответ проверяет, полностью ли отображается элемент в области просмотра. Если часть его отключена, это будет рассматривать его как не отображаемое. Если вы хотите знать, видна ли какая-либо часть элемента, используйте фрагмент из комментария @Roonaan.

Jezzamon 26.08.2018 09:01

Эй, думаю, на всякий случай стоит добавить if (!el) { return; }, чтобы избежать сбоев.

Robert Molina 25.12.2018 23:52

Этот код работает только с одним элементом, вы не можете прикрепить это событие, например, к. весь класс css. Я знаю, что можно выполнить цикл по всем элементам класса и присоединить к ним событие, но как насчет динамически добавляемых элементов?

prk_001 29.11.2019 11:17

Следует отметить, что getBoundingClientRect может быть ОЧЕНЬ медленным, на сложных страницах это может занять несколько сотен миллисекунд (!), Поэтому, если вы точно знаете, как расположена ваша страница и как вложены элементы, может быть лучше использовать clientLeft / clientTop и т. д.

Seven Systems 06.01.2020 21:31

мне не повезло .. он возвращает true, даже элемент не находится в области просмотра

kumaresan_sd 24.01.2020 07:27

Этот подход отлично подходит для меня. Мне нужна всего одна модификация. Я хочу определить, находится ли 50% элемента во вьюпорте или нет! Прямо сейчас он немедленно сообщает мне, даже если только верхняя часть элемента была прокручена во вьюпорте.

mikasa 30.05.2020 23:48

Uncaught ReferenceError: el is not defined

Oscar Chambers 10.11.2020 08:12

@Roonaan, ваш комментарий решил мою проблему. Раньше пропустил этот момент и получал, что getBoundingClientRect не является функцией.

Md. Samsull Arefeen 30.12.2020 17:24

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

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

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

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

Andy E 04.03.2013 17:46

Отличное решение, у него есть BOX / ScrollContainer и не используется WINDOW (только если он не указан). Взгляните на код, чем оцените, это более универсальное решение (много искал)

teter 13.05.2015 12:10

Обновлять

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

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

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

Passing test:http://jsfiddle.net/AndyE/cAY8c/

И результат:

Passed test, using isElementVisible

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

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

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

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

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

Вы хотели использовать doc.documentElement.clientWidth? Должен ли это быть «document.documentElement»? С другой стороны, это единственный метод, который также работает для таких случаев использования, как скрытие содержимого элемента для обеспечения доступности с помощью свойства CSS 'clip': snook.ca/archives/html_and_css/hiding-content-for-accessibil ity

Kevin Lamping 19.03.2013 23:34

@klamping: хороший улов, спасибо. Я скопировал его прямо из своего кода, где я использовал doc в качестве псевдонима для document. Да, мне нравится думать об этом как о достойном решении для крайних случаев.

Andy E 20.03.2013 00:14

У вас есть идеи по устранению проблемы с z-индексом?

Dan 25.06.2013 19:06

@Christoph: хотя он на самом деле не прояснил это, я предполагаю, что Дэн говорит о том факте, что элемент может быть перед тем, который вы проверяете, но не скрываться (например, прозрачный слой). Для этого сценария, очевидно, довольно сложно определить, видим ли элемент или нет, потому что мое решение дало бы ложноотрицательные результаты. Его можно изменить так, чтобы он возвращал не-логический флаг, т.е. «скрытый», «видимый» или «неопределенный».

Andy E 10.09.2013 16:13

Я не считаю, что это работает с hide / slideup. Когда элемент был сдвинут / спрятан, функция не вернет true.

FooBar 26.11.2013 20:20

Есть ошибка. Я разглагольствую на скрипке. Результаты тестов Test 1: false и Test 2: true. Если я заставлю алгоритм использовать compareDocumentPosition, отредактировав строку, в которой установлен contains, так, чтобы она была установлена ​​на compareDocumentPosition, то результаты будут Test 1: false, Test 2: false. Это потому, что 0x10 должен быть побитовым образом дополнен результатом compareDocumentPosition. Или 0x14 может использоваться для равенства, поскольку спецификации говорит, что содержащийся узел всегда следует.

Louis 26.11.2013 20:22

Для меня это не работает. Но inViewport () в предыдущем ответе работает в FF.

Satya Prakash 28.12.2013 11:05

Также может быть полезно проверить, что центр элемента виден, если у вас есть закругленные углы или применено преобразование, поскольку ограничивающие углы могут не возвращать ожидаемый элемент: element.contains(efp(rect.right - (rect.width / 2), rect.bottom - (rect.height / 2)))

Jared 10.03.2015 11:15

Объявление efp было бы лучше efp = document.elementFromPoint.bind(document).

Schism 03.06.2015 21:39

@schism, только если вас не волнует поддержка IE8 (и тот факт, что связанные функции плохо оптимизированы в некоторых браузерах) :-)

Andy E 03.06.2015 21:57

@AndyE Думаю, мне просто повезло, что мне не нужно об этом заботиться! :-) Кстати, похоже, это работает в 4.4 Webview, но не в Chrome ...

Schism 04.06.2015 18:01

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

Lewis 07.06.2015 11:46

У меня странная ситуация, но я хочу ее отметить для будущих читателей. Мой проект встроен в другой через iframe. В iframe есть полоса прокрутки, потому что выделенное ему пространство больше, чем становится видимым. Ни у Дэна, ни у Энди нет подходов, подходящих для моей ситуации. Элементы в нижней части iframe, которые вам нужно будет прокрутить, считаются видимыми с помощью обоих подходов. Ответы rainyjune, ryanve и ally также дают ложное срабатывание. Я еще не нашел способа определить видимый размер iframe, только выделенный размер.

Eric 15.07.2015 22:08

Не работал у меня на входах (хром канарейка 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 18.02.2016 17:05

@rayjax это потому, что разработчики Chrome решили изменить поведение window.innerHeight / Width и сломать сеть, см. code.google.com/p/chromium/issues/detail?id=571297 и не стесняйтесь добавлять свою жалобу к остальным!

Andy E 18.02.2016 20:36

@AndyE, если я прав, innerHeight / Width повлияет только на первую часть функции (находится внутри окна), а не на вторую (отображается)?

Rayjax 19.02.2016 18:20

@AndyE Это не всегда работает для элементов, которые шире / выше, чем область просмотра, потому что все углы могут быть за пределами экрана, даже если он виден

Andy 19.07.2016 22:02

@AndyE, чтобы решить, что вы можете проверить углы пересечения ограничивающего прямоугольника клиента элемента и прямоугольника области просмотра

Andy 19.07.2016 22:04

Также elementFromPoint может возвращать элементы с opacity: 0

Andy 19.07.2016 22:10

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

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

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

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

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

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 load-more-records).

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

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/

Мое решение более жадное и быстрое, когда элемент имеет какой-либо пиксель в области просмотра, он вернет false.

Eric Chen 20.01.2015 02:05

Мне это нравится. Лаконично. Вы можете удалить пробелы между именем функции и скобками, а также между скобками и скобками в первой строке. Никогда не любил те места. Возможно, это просто мой текстовый редактор, который все это кодирует цветом, чтобы его было легко читать. function aaa (arg) {statement} Я знаю, что это не ускоряет выполнение, а вместо этого подпадает под минимизацию.

Y.K. 06.09.2018 00:09

В качестве государственной службы:
Ответ Дэна с правильными вычислениями (элемент может быть> окном, особенно на экранах мобильных телефонов) и правильным тестированием jQuery, а также добавлением isElementPartialInViewport:

Кстати, разница между 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.info("Fully in viewport: " + inVpFull);
    console.info("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.info("Fully in viewport: " + inVpFull);
            console.info("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.info("state changed");
            // $(element).toggleClass('fixed', this.isAboveViewport)
        });
    </script>
    -->
</body>
</html>

isElementPartiallyInViewport тоже очень полезен. Хороший.

RJ Cuthbertson 28.10.2015 16:02

Для 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) }

Arun chauhan 13.08.2018 11:00

@Arun chauhan: Ни один из моих кодов не загружает изображения, так зачем это нужно, и формула верна.

Stefan Steiger 13.08.2018 11:53

@StefanSteiger Многие ответы здесь содержат window.innerHeight || document.documentElement.clientHeight как часть проверки. Поскольку вы первый, кто обратил внимание на разницу между ними, может быть, вы также сможете объяснить, зачем вообще нужен этот запасной вариант? AFAIK window.innerHeight всегда будет иметь значение (не может быть null или undefined), поэтому единственное ложное значение, которое он может удерживать, - 0. Это похоже на реальный крайний случай, в котором я даже не уверен, как document.documentElement.clientWidth может внезапно оказаться полезным.

targumon 22.01.2019 13:29

@targumon: Причина в поддержке старых браузеров.

Stefan Steiger 22.01.2019 19:38

@StefanSteiger согласно MDN поддерживается начиная с IE9, поэтому практически безопасно (по крайней мере, в моем случае) просто использовать window.innerHeight напрямую. Спасибо!

targumon 24.01.2019 02:31

@targumon: Да, но это значит, что не в IE8.

Stefan Steiger 25.01.2019 15:09

@StefanSteiger Надеюсь, вам больше не нужно поддерживать IE8. Microsoft больше не поддерживает его с января 2016 года!

jelhan 28.10.2019 15:35

isElementPartiallyInViewport мне очень помог. благодарю вас!

good_afternoon 02.03.2021 19:05

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

$(function() {

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

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

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

});

Я использую его так: когда элемент прокручивается в поле зрения, я добавляю класс, который запускает анимацию ключевого кадра CSS. Это довольно просто и особенно хорошо работает, когда у вас есть около 10+ объектов для условной анимации на странице.

Вы обязательно должны кэшировать $window = $(window) вне обработчика прокрутки

Adam Rehal 01.11.2017 11:13

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

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

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

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

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

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

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

Вам обязательно нужно кэшировать $window = $(window) вне обработчика прокрутки.

sam 01.07.2017 22:36

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

Эта функция решает проблему, когда ваш элемент находится внутри других прокручиваемых div, путем рекурсивного тестирования любых уровней до тега HTML и останавливается при первом ложном значении.

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

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

Bada bing bada бум

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

Будет ли фигурной скобки достаточно, чтобы закрыть оператор if?

calasyr 25.05.2016 02:58

Я не мог заставить его работать с несколькими элементами одного и того же класса.

The Whiz of Oz 20.06.2017 10:38

@TheWhizofOz Я обновил свой ответ, чтобы привести примеры других возможных вариантов использования, которые вы подняли. удачи.

r3wt 20.06.2017 23:07

У меня был тот же вопрос, и я решил это с помощью 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.info(elements[i].nodeName + ' ' +
                  elements[i].className + ' ' +
                  elements[i].id +
                  ' is in the viewport; proceed with whatever code you want to do here.');
   }
};

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

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

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

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

}

Это простое и компактное решение, которое мне помогло.

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

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

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

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

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

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

Новый 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>

Хорошая реализация, и согласно ссылке в этот ответ это должен работает на сафари, добавляя <!DOCTYPE html> в HTML

Alon Eitan 15.03.2019 15:06

Обратите внимание, что IntersectionObserver - экспериментальная функция (которая может измениться в будущем).

Karthik Chintala 26.06.2019 11:58

@KarthikChintala - он поддерживается во всех браузерах, кроме IE, а также доступен полифилл.

Randy Casburn 26.06.2019 16:50

Не отвечает на вопрос OP, поскольку обнаруживает только изменения: IntersectionObserver запускает обратный вызов только после перемещения цели относительно корня.

Brandon Hill 28.08.2019 19:37

При вызове observe немедленно запускается событие, сообщающее вам текущее состояние пересечения отслеживаемого элемента. Так что в некотором роде - адреса.

Mesqalito 24.01.2020 15:38

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

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

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

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

Как это работает в мобильных браузерах? Большинство из них содержат ошибки в отношении области просмотра, их заголовок поднимается или опускается при прокрутке, а также разное поведение при отображении клавиатуры, в зависимости от того, Android это или iOS и т. д.

Kev 23.01.2020 19:27

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

leonheess 26.01.2020 13:30

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

-When any pixel of an element is visible, but not "a corner",

-When an element is bigger than viewport and centered,

-Most of them are checking only for a singular element inside a document or window.

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

-You can return visible when only a pixel from any sides shows up and is not a corner,

-You can still return visible while element bigger than viewport,

-You can choose your parent element or you can automatically let it choose,

-Works on dynamically added elements too.

Если вы проверите приведенные ниже фрагменты, вы увидите разницу в использовании 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, чтобы увидеть:

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/

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

Наиболее распространенные ответы не работают при увеличении масштаба в 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);

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

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

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

Другие вопросы по теме