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

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

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

22 ответов


обновление: время идет дальше, и поэтому наши браузеры. эта техника больше не рекомендуется и вы должны использовать решение @Dan ниже (https://stackoverflow.com/a/7557433/5628) Если вам не нужно поддерживать IE

оригинальное решение (теперь устаревший):

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

function elementInViewport(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top >= window.pageYOffset &&
    left >= window.pageXOffset &&
    (top + height) <= (window.pageYOffset + window.innerHeight) &&
    (left + width) <= (window.pageXOffset + window.innerWidth)
  );
}

вы можете изменить это просто, чтобы определить если какая-либо часть элемента отображается в окне просмотра:

function elementInViewport2(el) {
  var top = el.offsetTop;
  var left = el.offsetLeft;
  var width = el.offsetWidth;
  var height = el.offsetHeight;

  while(el.offsetParent) {
    el = el.offsetParent;
    top += el.offsetTop;
    left += el.offsetLeft;
  }

  return (
    top < (window.pageYOffset + window.innerHeight) &&
    left < (window.pageXOffset + window.innerWidth) &&
    (top + height) > window.pageYOffset &&
    (left + width) > window.pageXOffset
  );
}

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

решение, выбранное как правильное, является почти никогда точно. Ты можешь!--25-->подробнее о его ошибках.


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


function isElementInViewport (el) {

    //special bonus for those using jQuery
    if (typeof jQuery === "function" && el instanceof jQuery) {
        el = el[0];
    }

    var rect = el.getBoundingClientRect();

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

как использовать:

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

поместите следующий код в нижней части <body> tag:

function onVisibilityChange(el, callback) {
    var old_visible;
    return function () {
        var visible = isElementInViewport(el);
        if (visible != old_visible) {
            old_visible = visible;
            if (typeof callback == 'function') {
                callback();
            }
        }
    }
}

var handler = onVisibilityChange(el, function() {
    /* your code go here */
});


//jQuery
$(window).on('DOMContentLoaded load resize scroll', handler); 

/* //non-jQuery
if (window.addEventListener) {
    addEventListener('DOMContentLoaded', handler, false); 
    addEventListener('load', handler, false); 
    addEventListener('scroll', handler, false); 
    addEventListener('resize', handler, false); 
} else if (window.attachEvent)  {
    attachEvent('onDOMContentLoaded', handler); // IE9+ :(
    attachEvent('onload', handler);
    attachEvent('onscroll', handler);
    attachEvent('onresize', handler);
}
*/

если вы делаете любые изменения DOM, то они могут изменить видимость вашей стихии, конечно.

руководящие принципы и общие подводные камни:

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

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

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

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

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

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

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

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

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


обновление

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

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

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


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

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

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

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

Failed test, using isElementInViewport

решение: isElementVisible()

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

function isElementVisible(el) {
    var rect     = el.getBoundingClientRect(),
        vWidth   = window.innerWidth || doc.documentElement.clientWidth,
        vHeight  = window.innerHeight || doc.documentElement.clientHeight,
        efp      = function (x, y) { return document.elementFromPoint(x, y) };     

    // Return false if it's not in the viewport
    if (rect.right < 0 || rect.bottom < 0 
            || rect.left > vWidth || rect.top > vHeight)
        return false;

    // Return true if any of its four corners are visible
    return (
          el.contains(efp(rect.left,  rect.top))
      ||  el.contains(efp(rect.right, rect.top))
      ||  el.contains(efp(rect.right, rect.bottom))
      ||  el.contains(efp(rect.left,  rect.bottom))
    );
}

проходящий тест: http://jsfiddle.net/AndyE/cAY8c/

и в итоге:

Passed test, using isElementVisible

дополнительная информация

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

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

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

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


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

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

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

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

кстати,разница между окно.внутренняя ширина и документ.функцию documentelement.clientWidth заключается в том, что clientWidth/clientHeight не включает полосу прокрутки, а окно.расстояние между/высоты делает.

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

есть плагин jQuery под названием inview что делает работу


см. источник краю, который использует 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 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/


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

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

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

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

};

//additional examples for other use cases
//true false whether an array of elements are all in view
$.fn.allInView = function(){
    var all = [];
    this.forEach(function(){
        all.push( $(this).inView() );
    });
    return all.indexOf(false) === -1;
};

//only the class elements in view
$('.some-class').filter(function(){
    return $(this).inView();
});

//only the class elements not in view
$('.some-class').filter(function(){
    return !$(this).inView();
});

использование

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

    if( $('footer').inView() ) {
        // do cool stuff
    }

});

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

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

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

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

/**
 * fullVisible=true only returns true if the all object rect is visible
 */
function isReallyVisible(el, fullVisible) {
    if ( el.tagName == "HTML" )
            return true;
    var parentRect=el.parentNode.getBoundingClientRect();
    var rect = arguments[2] || el.getBoundingClientRect();
    return (
            ( fullVisible ? rect.top    >= parentRect.top    : rect.bottom > parentRect.top ) &&
            ( fullVisible ? rect.left   >= parentRect.left   : rect.right  > parentRect.left ) &&
            ( fullVisible ? rect.bottom <= parentRect.bottom : rect.top    < parentRect.bottom ) &&
            ( fullVisible ? rect.right  <= parentRect.right  : rect.left   < parentRect.right ) &&
            isReallyVisible(el.parentNode, fullVisible, rect)
    );
};

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


на основе решения @dan выше (https://stackoverflow.com/a/7557433/5628), я пошел на очистку реализации, чтобы использовать ее несколько раз на одной странице проще:

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

Надежда это помогает!


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


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

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

var visibleY = function(el){
    var top = el.getBoundingClientRect().top, rect, el = el.parentNode;
    do {
        rect = el.getBoundingClientRect();
        if (top <= rect.bottom === false)
            return false;
        el = el.parentNode;
    } while (el != document.body);
    // Check its within the document viewport
    return top <= document.documentElement.clientHeight;
};

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


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

function getViewportSize(w) {
    var w = w || window;
    if(w.innerWidth != null) return {w:w.innerWidth, h:w.innerHeight};
    var d = w.document;
    if (document.compatMode == "CSS1Compat") {
        return {
            w: d.documentElement.clientWidth,
            h: d.documentElement.clientHeight
        };
    }
    return { w: d.body.clientWidth, h: d.body.clientWidth };
}
function isViewportVisible(e) {
    var box = e.getBoundingClientRect();
    var height = box.height || (box.bottom - box.top);
    var width = box.width || (box.right - box.left);
    var viewport = getViewportSize();
    if(!height || !width) return false;
    if(box.top > viewport.h || box.bottom < 0) return false;
    if(box.right < 0 || box.left > viewport.w) return false;
    return true;    
}

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

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

У меня был тот же вопрос и понял это, используя getBoundingClientRect(). Этот код полностью "общий" и должен быть написан только один раз, чтобы он работал (вам не нужно писать его для каждого элемента, который вы хотите знать, находится в окне просмотра). Этот код проверяет только, находится ли он вертикально в окне просмотра не горизонтально. В этом случае переменная (массив) "элементы" содержит все элементы, которые вы проверяете, чтобы быть вертикально в окне просмотра, поэтому возьмите любой элементы вы хотите в любом месте и хранить их там. "Для цикла", петли через каждый элемент и проверяет, является ли он вертикально в окне просмотра. Этот код выполняет каждый раз пользователь прокручивает! Если getBoudingClientRect ().top меньше 3/4 видового экрана (элемент составляет одну четверть в видовом окне), он регистрируется как "в видовом окне". Поскольку код является общим, вы захотите узнать, какой элемент находится в окне просмотра. Чтобы узнать это, вы можете определить его по custom атрибут, имя узла, id, имя класса и многое другое. Вот мой код (скажите мне, если он не работает, он был протестирован в IE 11, FireFox 40.0.3, Chrome версии 45.0.2454.85 m, Opera 31.0.1889.174 и Edge с Windows 10, [еще не Safari])...

//scrolling handlers...
window.onscroll = function(){
  var elements = document.getElementById('whatever').getElementsByClassName('whatever');
  for(var i = 0; i != elements.length; i++)
  {
   if(elements[i].getBoundingClientRect().top <= window.innerHeight*0.75 && elements[i].getBoundingClientRect().top > 0)
   {
      console.log(elements[i].nodeName + ' ' + elements[i].className + ' ' + elements[i].id + ' is in the viewport; proceed with whatever code you want to do here.');
   }
};

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


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

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

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

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

function inParentViewport(el, pa) {
    if (typeof jQuery === "function"){
        if (el instanceof jQuery)
            el = el[0];
        if (pa instanceof jQuery)
            pa = pa[0];
    }

    var e = el.getBoundingClientRect();
    var p = pa.getBoundingClientRect();

    return (
        e.bottom >= p.top &&
        e.right >= p.left &&
        e.top <= p.bottom &&
        e.left <= p.right
    );
}

Я использую эту функцию (она проверяет только, является ли y inscreen, так как большую часть времени 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;
    }

}

для аналогичного вызова мне очень понравилось в этом суть который выставляет polyfill для scrollIntoViewIfNeeded ().

все необходимое Кунг-Фу, необходимое для ответа, находится в этом блоке:

var parent = this.parentNode,
    parentComputedStyle = window.getComputedStyle(parent, null),
    parentBorderTopWidth = parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
    parentBorderLeftWidth = parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
    overTop = this.offsetTop - parent.offsetTop < parent.scrollTop,
    overBottom = (this.offsetTop - parent.offsetTop + this.clientHeight - parentBorderTopWidth) > (parent.scrollTop + parent.clientHeight),
    overLeft = this.offsetLeft - parent.offsetLeft < parent.scrollLeft,
    overRight = (this.offsetLeft - parent.offsetLeft + this.clientWidth - parentBorderLeftWidth) > (parent.scrollLeft + parent.clientWidth),
    alignWithTop = overTop && !overBottom;

this относится к элементу, который вы хотите знать, если это т. е. overTop или overBottom - просто должен получить дрейф...