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


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

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

22 772

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

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


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


function isElementInViewport (el) {

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

    var rect = el.getBoundingClientRect();

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

обновление

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

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

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


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

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

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

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

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 и более поздних версиях и большинство настольные браузеры в течение длительного времени (хотя и не идеально). Смотрите Quirksmode на этих функциях для получения дополнительной информации.

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

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

кстати, the разница между окно.внутренний мир и документ.функцию 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 что делает работу

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

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

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

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

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

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

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

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

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

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

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

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

}

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