Як я можу визначити, чи бачиться елемент DOM у поточному вікні перегляду?


949

Чи є ефективний спосіб визначити, чи в даний момент елемент DOM (у документі HTML) видно (відображається у вікні перегляду )?

(Питання стосується Firefox.)


Залежить від того, що ви маєте на увазі під видимим. Якщо ви маєте на увазі, чи відображається він зараз на сторінці, враховуючи положення прокрутки, ви можете його обчислити, виходячи з елементів y зміщення та поточної позиції прокрутки.
roryf

18
Пояснення: Видиме, як і в поточному прямокутнику, що відображається. Видиме, як у не прихованому або на дисплеї: немає? Видно, як не позаду чогось іншого в порядку Z?
Адам Райт

6
як у межах прямокутника, що відображається в даний час. Дякую. Перефразоване.
benzaita

2
Зауважте, що всі відповіді тут дадуть помилковий позитив для предметів, які знаходяться за межами видимої області їхнього батьківського елемента (наприклад, із прихованим переповненням), але всередині області перегляду.
Енді Е

1
Є мільйон відповідей, і більшість смішно довго. Ознайомтеся з двома лайнерами
leonheess

Відповіді:


346

Оновлення: Час марширується і так є у наших браузерів. Ця методика більше не рекомендується, і ви повинні використовувати рішення Дана, якщо вам не потрібно підтримувати версію 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
  );
}

1
Оригінальна розміщена функція мала помилку. Потрібно зберегти ширину / висоту перед переназначенням El ...
Prestaul

15
Що робити, якщо елемент живе в ділі, що прокручується, і прокручується з виду ??
амартинов

2
Будь ласка , ознайомтеся з більш новою версією сценарію нижче
Дан

1
Також цікаво питання @ amartynov. Хтось знає, як просто сказати, чи прихований елемент через переповнення елемента пращура? Бонус, якщо це можна виявити незалежно від того, наскільки глибоко вкладена дитина.
Ерік Нгуен

1
@deadManN повтор через DOM є, як відомо, повільним. Це є достатньою причиною, але постачальники браузерів також створили саме getBoundingClientRectдля того, щоб знайти координати елементів ... Чому б ми не використали його?
Prestaul

1402

Зараз більшість браузерів підтримують метод 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);

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

TODO: цей метод не вирішує дві ситуації:

  • перекриття з використанням z-index
  • використання overflow-scrollв контейнері елемента
  • спробуйте щось нове - пояснив API перехрестя

6
Я використовую це рішення (остерігайтеся помилки "botom"). Також слід пам’ятати про те, коли елемент, який ми розглядаємо, мав би в ньому зображення. Chrome (принаймні) повинен дочекатися завантаження зображення, щоб мати точне значення для limitingRectangle. Здається, що у Firefox немає цієї "проблеми"
Клаудіо

4
Чи працює це, коли у вас активована прокрутка в контейнері всередині корпусу. Наприклад, тут він не працює - agaase.github.io/webpages/demo/isonscreen2.html isElementInViewport (document.getElementById ("innerele")). innerele присутній всередині контейнера, який увімкнено прокрутку.
агааза

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

11
el is not defined
M_Willett

175

Оновлення

У сучасних браузерах ви можете перевірити API Intersection Observer, який забезпечує такі переваги:

  • Краща продуктивність, ніж прослуховування подій прокрутки
  • Працює в міждоменних iframe
  • Може визначити, чи елемент перешкоджає / перетинає інший

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
Кевін Ламп

@klamping: хороший улов, дякую. Я скопіював це прямо зі свого коду, де я використовував docпсевдонім document. Так, мені подобається думати про це як про гідне рішення крайових справ.
Енді Е

2
Для мене це не працює. Але inViewport () у попередній відповіді працює у FF.
Сатя Пракаш

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

2
Не працював на входах для мене (chrome canary 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() */;
}

33

Як державна служба: відповідь
Дена з правильними розрахунками (елементом може бути> вікно, особливо на екранах мобільних телефонів), правильним тестуванням jQuery, а також додаванням isElementParicallyInViewport:

До речі, різниця між 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 Cuthbertson

Для isElementInViewport (e): Ваш код навіть не завантажує зображення. Цей правильний: функція isElementInViewport (e) {var t = e.getBoundingClientRect (); повернути 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якщо будь-яка частина елемента знаходиться у вікні перегляду.


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
Моє рішення більш жадібне і швидше, коли елемент має будь-який піксель у вікні перегляду, він повернеться хибним.
Ерік Чен

1
Мені це подобається. Стислі. Ви можете видалити пробіли між назвою функції та круглими дужками, а також між круглими дужками та дужкою у першому рядку. Ніколи не любив тих просторів. Можливо, це лише мій редактор тексту, який кодує все те, що все ще робить його легким для читання. function aaa (arg) {твердження} Я знаю, що це не примушує його виконувати швидше, підпадає під мінімізацію.
YK

21

Мені стало непокоїти те, що не було доступної в jQuery версії доступних функціональних можливостей. Коли я натрапив на рішення Дана, я підглянув можливість запропонувати щось для людей, які люблять програмувати в стилі jQuery OO. Це приємно і спритно і працює як шарм для мене.

Bada bing bada boom

$.fn.inView = function(){
    if(!this.length) 
        return false;
    var rect = this.get(0).getBoundingClientRect();

    return (
        rect.top >= 0 &&
        rect.left >= 0 &&
        rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
        rect.right <= (window.innerWidth || document.documentElement.clientWidth)
    );

};

// Additional examples for other use cases
// 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
    }
});

1
Чи буде фігурної дужки достатньо для закриття заяви if?
каласир

1
Я не міг змусити його працювати з декількома елементами ідентичного класу.
Віст Оз

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, - також є поліфіл.
Ренді Касберн

Не стосується питання ОП, оскільки виявляє лише зміни : 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.


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

Як це поводиться в мобільних браузерах? Більшість з них баггі в області перегляду, їх заголовок піднімається вгору або вниз, і відрізняється поведінкою, коли клавіатура відображається, залежно від того, чи це android або ios тощо.
Kev

@Kev Має працювати добре, залежно від того, коли ви називаєте цей метод. Якщо ви зателефонуєте йому та зміните розмір вікна, результат, очевидно, більше не буде правильним. Ви можете викликати його на кожному заході розміру, залежно від типу функціональності, який ви хочете. Не соромтеся задати окреме запитання щодо конкретного випадку використання та пінг мені тут.
leonheess

3

Я думаю, що це більш функціональний спосіб зробити це. Відповідь Дена не працює в рекурсивному контексті.

Ця функція вирішує проблему, коли ваш елемент знаходиться в інших дисках, що прокручуються, тестуючи будь-які рівні рекурсивно до 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)
    );
};

3

Найбільш прийняті відповіді не працюють під час збільшення масштабування Google Chrome на Android. У поєднанні з відповіддю Дана для обліку Chrome на Android потрібно використовувати VisVportport . Наступний приклад враховує лише вертикальну перевірку і використовує 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);

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


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+ речей.


Ви обов'язково повинні кешувати $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якої корисно для елементів, більших за перегляд контрольних елементів3 внутрішніх пікселів, щоб побачити:

Розумієте, коли ви знаходитесь всередині елемента3, він не вказує, чи він видимий чи ні, тому що ми перевіряємо, чи елемент видно з боків або кутів .

І цей включає, crossSearchAlgorithmщо дозволяє все одно повертатися, visibleколи елемент більший за область перегляду:

JSFiddle грати з: http://jsfiddle.net/BerkerYuceer/grk5az2c/

Цей код зроблений для більш точної інформації, якщо якась частина елемента відображається у вікні перегляду чи ні. Для параметрів продуктивності або лише вертикальних слайдів не використовуйте це! Цей код є більш ефективним у малюнках.


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

12
Вам слід спробувати пояснити, чому ваша версія краще. На сьогоднішній день вона виглядає більш-менш такою ж, як і інші рішення.
Енді Е

1
Прекрасне рішення, що він має BOX / ScrollContainer і не використовує WINDOW (тільки якщо його не вказано). Подивіться на код, ніж оцінюйте, це більш універсальне рішення (
Шукали

1

Як просто, IMO:

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

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

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

0

У мене було те саме питання і я вирішив це за допомогою getBoundingClientRect ().

Цей код є повністю "загальним" і його потрібно написати лише один раз, щоб він працював (не потрібно виписувати його для кожного елемента, який ви хочете знати, у вікні перегляду).

Цей код перевіряє, чи знаходиться він вертикально у вікні перегляду, а не по горизонталі . У цьому випадку змінна (масив) "елементи" містить усі елементи, які ви перевіряєте, вертикально знаходяться у вікні перегляду, тому захопіть будь-які потрібні вам елементи будь-де та збережіть їх там.

Значення "for loop" проходить через кожен елемент і перевіряє, чи знаходиться він вертикально у вікні перегляду. Цей код виконується щоразу, коли користувач прокручує! Якщо topBoudingClientRect (). Top менше 3/4 в області перегляду (елемент - одна чверть у вікні перегляду), він реєструється як "у вікні перегляду".

Оскільки код є загальним, ви хочете знати, який елемент знаходиться у вікні перегляду. Щоб дізнатися це, ви можете визначити його за спеціальним атрибутом, ім'ям вузла, ідентифікатором, назвою класу тощо.

Ось мій код (скажіть, чи він не працює; він протестований у Internet Explorer 11, Firefox 40.0.3, Chrome версії 45.0.2454.85 м, 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.');
   }
};

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

Я використовую цю функцію (вона перевіряє лише, чи y екран, оскільки більшість часу х не потрібен)

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

Для подібного виклику мені дуже сподобалася ця суть, яка демонструє поліфункцію для 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- вам просто слід отримати дрейф ...

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.