Obtenha a altura visível de um div com jQuery

86

Preciso recuperar a altura visível de um div em uma área de rolagem. Eu me considero bastante decente com o jQuery, mas isso está me deixando completamente louco.

Digamos que eu tenha um div vermelho dentro de um invólucro preto:

No gráfico acima, a função jQuery retornaria 248, a parte visível do div.

Assim que o usuário rolar para além do topo do div, como no gráfico acima, ele reportará 296.

Agora, depois que o usuário rolar para além do div, ele relatará novamente 248.

Obviamente, meus números não serão tão consistentes e claros como são nesta demonstração, ou eu apenas codificaria para esses números.

Eu tenho um pouco de teoria:

  • Obtenha a altura da janela
  • Obtenha a altura do div
  • Obtenha o deslocamento inicial do div do topo da janela
  • Obtenha o deslocamento conforme o usuário rola.
    • Se o deslocamento for positivo, significa que o topo da div ainda está visível.
    • se for negativo, a parte superior do div foi eclipsada pela janela. Neste ponto, o div pode estar ocupando toda a altura da janela ou a parte inferior do div pode estar sendo exibida
    • Se a parte inferior da div estiver aparecendo, descubra a lacuna entre ela e a parte inferior da janela.

Parece muito simples, mas simplesmente não consigo entender isso. Vou tentar outra vez amanhã de manhã; Achei que alguns de vocês, gênios, poderiam ajudar.

Obrigado!

ATUALIZAÇÃO: descobri isso sozinho, mas parece que uma das respostas abaixo é mais elegante, então usarei isso. Para os curiosos, aqui está o que eu criei:

$(document).ready(function() {
    var windowHeight = $(window).height();
    var overviewHeight = $("#overview").height();
    var overviewStaticTop = $("#overview").offset().top;
    var overviewScrollTop = overviewStaticTop - $(window).scrollTop();
    var overviewStaticBottom = overviewStaticTop + $("#overview").height();
    var overviewScrollBottom = windowHeight - (overviewStaticBottom - $(window).scrollTop());
    var visibleArea;
    if ((overviewHeight + overviewScrollTop) < windowHeight) {
        // alert("bottom is showing!");
        visibleArea = windowHeight - overviewScrollBottom;
        // alert(visibleArea);
    } else {
        if (overviewScrollTop < 0) {
            // alert("is full height");
            visibleArea = windowHeight;
            // alert(visibleArea);
        } else {
            // alert("top is showing");
            visibleArea = windowHeight - overviewScrollTop;
            // alert(visibleArea);
        }
    }
});
JacobTheDev
fonte
Gostaria de olhar para unidades vh. 1vh = 1/100 da altura da janela de visualização. Você provavelmente encontrará uma solução com isso. Encontre a altura da janela de visualização, a altura do elemento, a posição dos elementos e a posição de rolagem e calcule de acordo.
davidcondrey
Supondo que haja margem no DIV interno, diga "10px" ao redor. Eu detectaria a altura da rolagem para ver se passou de "10", então eu apenas pegaria a altura do elemento pai, subtrairia com base na altura da rolagem.
Se tudo mais falhar, acabei de encontrar este script que parece servir para o propósito que você precisa: larsjung.de/fracs
davidcondrey

Respostas:

57

Aqui está um conceito rápido e sujo. Basicamente, compara o offset().topdo elemento com a parte superior da janela e offset().top + height()com a parte inferior da janela:

function getVisible() {    
    var $el = $('#foo'),
        scrollTop = $(this).scrollTop(),
        scrollBot = scrollTop + $(this).height(),
        elTop = $el.offset().top,
        elBottom = elTop + $el.outerHeight(),
        visibleTop = elTop < scrollTop ? scrollTop : elTop,
        visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
    $('#notification').text(visibleBottom - visibleTop);
}

$(window).on('scroll resize', getVisible);

Exemplo de violino

editar - pequena atualização para também realizar a lógica quando a janela está sendo redimensionada.

Rory McCrossan
fonte
Estou tendo problemas para definir isso para vários divs sem apenas repetir o código. Existe uma maneira de fazer isso?
JacobTheDev
2
@Rev, aqui está: jsfiddle.net/b5DGj/1 . Separei cada elemento em sua própria chamada de função. Você poderia até mesmo ir além e definir um plugin para fazer isso por você.
Rory McCrossan de
Eu reescrevi ligeiramente a abordagem de @RoryMcCrossan para funcionar como um plugin jQuery e a postei como uma resposta abaixo - pode ter uma aplicabilidade mais geral nesse formato.
Michael.Lumley
Às vezes, um elemento pode ser parcialmente oculto por causa de estouro ou elementos pais, independentemente da "janela"
Nadav B
55

Calcule a quantidade de px de um elemento (altura) na janela de visualização

Demonstração de violino

Esta função minúscula retornará a quantidade de pxum elemento que está visível na janela de visualização (vertical) :

function inViewport($el) {
    var elH = $el.outerHeight(),
        H   = $(window).height(),
        r   = $el[0].getBoundingClientRect(), t=r.top, b=r.bottom;
    return Math.max(0, t>0? Math.min(elH, H-t) : Math.min(b, H));
}

Use como:

$(window).on("scroll resize", function(){
  console.log( inViewport($('#elementID')) ); // n px in viewport
});

é isso aí.


.inViewport()Plugin jQuery

demonstração jsFiddle

acima, você pode extrair a lógica e criar um plugin como este:

/**
 * inViewport jQuery plugin by Roko C.B.
 * http://stackoverflow.com/a/26831113/383904
 * Returns a callback function with an argument holding
 * the current amount of px an element is visible in viewport
 * (The min returned value is 0 (element outside of viewport)
 */
;(function($, win) {
  $.fn.inViewport = function(cb) {
     return this.each(function(i,el) {
       function visPx(){
         var elH = $(el).outerHeight(),
             H = $(win).height(),
             r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
         return cb.call(el, Math.max(0, t>0? Math.min(elH, H-t) : Math.min(b, H)));  
       }
       visPx();
       $(win).on("resize scroll", visPx);
     });
  };
}(jQuery, window));

Use como:

$("selector").inViewport(function(px) {
  console.log( px ); // `px` represents the amount of visible height
  if(px > 0) {
    // do this if element enters the viewport // px > 0
  }else{
    // do that if element exits  the viewport // px = 0
  }
}); // Here you can chain other jQuery methods to your selector

seus seletores ouvirão dinamicamente a janela scrolle resizetambém retornarão o valor inicial no DOM pronto por meio do primeiro argumento da função de retorno de chamada px.

Roko C. Buljan
fonte
Para trabalhar no Android Chrome, você pode precisar adicionar <meta name="viewport" content="width=your_size">a seção head html.
userlond
@userlond obrigado por sua contribuição, mas não tenho certeza se isso se content="width=1259"adequa à maioria. Você já tentou content="width=device-width, initial-scale=1"?
Roko C. Buljan
O site tem template legado com largura fixa e não responde ... Acho que sua sugestão vai dar certo na maioria. Então, para esclarecer: se a solução de Roko C. Buljan não funcionar, tente adicionar uma <meta name="viewport" content="your_content">declaração :)
userlond
Isso é tão incrível e tão próximo do que eu tenho procurado. Como posso calcular a distância da parte inferior dos elementos à parte inferior da janela? Assim, posso carregar mais conteúdo antes que você veja o final do conteúdo. Por exemplo, se elBottom <200 de windowBot, dispare AJAX.
Thom
1
@Thom bem, o método acima não retorna nenhum outro valor específico (ele pode ser expandido para retornar mais coisas do que apenas visible height px, como um objeto. Veja este violino para essa ideia: jsfiddle.net/RokoCB/6xjq5gyy (console de desenvolvedor aberto para ver os registros)
Roko C. Buljan
11

Aqui está uma versão da abordagem de Rory acima, exceto escrita para funcionar como um plugin jQuery. Pode ter aplicabilidade mais geral nesse formato. Ótima resposta, Rory - obrigado!

$.fn.visibleHeight = function() {
    var elBottom, elTop, scrollBot, scrollTop, visibleBottom, visibleTop;
    scrollTop = $(window).scrollTop();
    scrollBot = scrollTop + $(window).height();
    elTop = this.offset().top;
    elBottom = elTop + this.outerHeight();
    visibleTop = elTop < scrollTop ? scrollTop : elTop;
    visibleBottom = elBottom > scrollBot ? scrollBot : elBottom;
    return visibleBottom - visibleTop
}

Pode ser chamado com o seguinte:

$("#myDiv").visibleHeight();

jsFiddle

Michael.Lumley
fonte
2
Não trabalhe no caso de a janela ficar maior e o bloco caber. É por isso que precisamos redefinir a altura do bloco: $ (this) .css ('height', ''); jsfiddle.net/b5DGj/144
Max Koshel