jQuery: obtém a altura do elemento oculto no jQuery

249

Eu preciso obter a altura de um elemento que está dentro de uma div que está oculta. Agora eu mostro a div, obtenho a altura e oculto a div principal. Isso parece um pouco bobo. Existe uma maneira melhor?

Estou usando o jQuery 1.4.2:

$select.show();
optionHeight = $firstOption.height(); //we can only get height if its visible
$select.hide();
mkoryak
fonte
42
+1 Seu exemplo de solução é realmente melhor do que qualquer uma das respostas, lol.
Tim Santeford
9
Eu discordo de Tim. Com esta solução, há uma chance de a exibição piscar porque você está realmente mostrando o item e depois ocultando-o. Embora a solução Nicks seja mais complicada, ela não tem chance de tremer a tela, pois a div pai nunca é mostrada.
Humphrey
5
Harry, na verdade o que você descobriu é que seu código não funciona no IE, não nesta solução. Go depurar seu código :)
Gavin

Respostas:

181

Você pode fazer algo assim, embora um pouco hacky, esqueça positionse já é absoluto:

var previousCss  = $("#myDiv").attr("style");

$("#myDiv").css({
    position:   'absolute', // Optional if #myDiv is already absolute
    visibility: 'hidden',
    display:    'block'
});

optionHeight = $("#myDiv").height();

$("#myDiv").attr("style", previousCss ? previousCss : "");
Nick Craver
fonte
17
Consolidei o código acima em um plugin jQ jsbin.com/ihakid/2 . Eu também uso uma classe e verifico se os pais também estão ocultos.
hitautodestruct
3
+1 Isso funciona muito bem. Lembre-se de que os pais do elemento também devem ser exibidos. Isso me deixou confuso por alguns minutos. Além disso, se o elemento for position:relative, é claro que você deseja definir isso depois em vez de static.
Michael Mior
6
Quando você usa esse método, o elemento de destino geralmente pode mudar de forma. Portanto, obter a altura e / ou largura da div quando está absolutamente posicionada pode não ser muito útil.
21412 Jeremy Ricketts
2
@ Gavin, isso evita um cálculo de refluxo e qualquer oscilação para o usuário, por isso é mais barato e menos invasivo.
Nick Craver
2
@hitautodestruct, modifiquei seu plug-in de consulta para verificar se apenas o elemento está realmente oculto. jsbin.com/ihakid/58
Prasad
96

Corri para o mesmo problema ao obter a largura do elemento oculto, então escrevi esse plugin chamado jQuery Actual para corrigi-lo. Ao invés de usar

$('#some-element').height();

usar

$('#some-element').actual('height');

fornecerá o valor certo para o elemento oculto ou o elemento tem um pai oculto.

Documentação completa, consulte aqui . Há também uma demonstração incluída na página.

Espero que esta ajuda :)

ben
fonte
3
Sim, isso ajuda! 2015 e ainda ajuda. Você diz "versões mais antigas do jquery" no site Real - no entanto - ainda é necessário na v2.1.3, pelo menos na minha situação.
LpLrich
Obrigado! Eu usei esse plugin para ler e definir a altura de elementos (isto é feito de forma dinâmica) escondido inicialmente dentro acordeões
AIDS
Impressionante plugin! Eu tentei de tudo para obter a largura de uma div dentro de um modal quando definido como 100% e nada funcionou. configure isso em 5 segundos e funcionou perfeitamente!
Craig Howell
@ ben O link está quebrado.
Sachin Prasad
39

Você está confundindo dois estilos CSS, o estilo de exibição e o estilo de visibilidade .

Se o elemento estiver oculto configurando o estilo css de visibilidade, você poderá obter a altura independentemente de o elemento estar ou não visível ou não, pois o elemento ainda ocupa espaço na página .

Se o elemento estiver oculto alterando o estilo css de exibição para "none", o elemento não ocupará espaço na página e você deverá fornecer um estilo de exibição que fará com que o elemento seja renderizado em algum espaço, em Nesse ponto, você pode obter a altura.

casperOne
fonte
obrigado por isso. meu problema envolvia uma célula da tabela e definir visibilitypara hiddennão resolver o meu problema, mas me deu a ideia de definir position: fixed; top: 100%e funciona como um encanto!
precisa
Agora entendo por que a altura não é mostrada na tela: nenhum elemento. Muito obrigado pela explicação maravilhosa.
FrenkyB
30

Na verdade, eu recorri a um pouco de truque para lidar com isso às vezes. Eu desenvolvi um widget de barra de rolagem jQuery, onde encontrei o problema que não sei com antecedência se o conteúdo rolável faz parte de uma parte oculta da marcação ou não. Aqui está o que eu fiz:

// try to grab the height of the elem
if (this.element.height() > 0) {
    var scroller_height = this.element.height();
    var scroller_width = this.element.width();

// if height is zero, then we're dealing with a hidden element
} else {
    var copied_elem = this.element.clone()
                      .attr("id", false)
                      .css({visibility:"hidden", display:"block", 
                               position:"absolute"});
    $("body").append(copied_elem);
    var scroller_height = copied_elem.height();
    var scroller_width = copied_elem.width();
    copied_elem.remove();
}

Isso funciona na maior parte do tempo, mas há um problema óbvio que pode surgir potencialmente. Se o conteúdo que você está clonando tiver um estilo com CSS que inclua referências à marcação pai em suas regras, o conteúdo clonado não conterá o estilo apropriado e provavelmente terá medidas ligeiramente diferentes. Para contornar isso, verifique se a marcação que você está clonando tem regras CSS aplicadas a ela que não incluem referências à marcação pai.

Além disso, isso não veio para mim com o meu widget de rolagem, mas para obter a altura apropriada do elemento clonado, você precisará definir a largura para a mesma largura do elemento pai. No meu caso, uma largura CSS sempre foi aplicada ao elemento real, então não precisei me preocupar com isso, no entanto, se o elemento não tiver uma largura aplicada, talvez seja necessário fazer algum tipo de recursividade. passagem da ancestralidade DOM do elemento para encontrar a largura do elemento pai apropriado.

Elliotlarson
fonte
Isso tem um problema interessante. Se o elemento já for um elemento de bloco, o clone terá toda a largura do corpo (exceto o preenchimento ou a margem do corpo), que é diferente do tamanho dentro de um contêiner.
Meligy
16

Desenvolvendo ainda mais a resposta do usuário Nick e o plug-in do usuário hitautodestruct no JSBin, criei um plug-in jQuery semelhante que recupera largura e altura e retorna um objeto que contém esses valores.

Pode ser encontrado aqui: http://jsbin.com/ikogez/3/

Atualizar

Eu reprojetei completamente esse minúsculo plugin, pois a versão anterior (mencionada acima) não era realmente utilizável em ambientes da vida real, onde muita manipulação de DOM estava acontecendo.

Isso está funcionando perfeitamente:

/**
 * getSize plugin
 * This plugin can be used to get the width and height from hidden elements in the DOM.
 * It can be used on a jQuery element and will retun an object containing the width
 * and height of that element.
 *
 * Discussed at StackOverflow:
 * http://stackoverflow.com/a/8839261/1146033
 *
 * @author Robin van Baalen <[email protected]>
 * @version 1.1
 * 
 * CHANGELOG
 *  1.0 - Initial release
 *  1.1 - Completely revamped internal logic to be compatible with javascript-intense environments
 *
 * @return {object} The returned object is a native javascript object
 *                  (not jQuery, and therefore not chainable!!) that
 *                  contains the width and height of the given element.
 */
$.fn.getSize = function() {    
    var $wrap = $("<div />").appendTo($("body"));
    $wrap.css({
        "position":   "absolute !important",
        "visibility": "hidden !important",
        "display":    "block !important"
    });

    $clone = $(this).clone().appendTo($wrap);

    sizes = {
        "width": $clone.width(),
        "height": $clone.height()
    };

    $wrap.remove();

    return sizes;
};
Robin van Baalen
fonte
seu código não está correto. Você precisa sizes = {"width": $wrap.width(), "height": $wrap.height()};do thisrefere-se ao item original, que não é visível.
jackJoe
@JackJoe Estou usando esse código há algum tempo sem problemas. Na melhor das hipóteses, eu diria que preciso de $ clone.width () em vez de 'this'. Não $ wrap desde que eu quero o tamanho do elemento dentro do wrap. O invólucro pode ter 10000x10000px, enquanto o elemento que eu quero medir ainda é 30x40px, por exemplo.
Robin van Baalen
Mesmo direcionando o $wrapé ok (pelo menos nos meus testes). Eu tentei com o thise, obviamente, ele não pegaria o tamanho, porque thisainda se refere ao elemento oculto.
jackJoe
1
Obrigado @jackJoe Agora atualizei o exemplo de código.
Robin van Baalen
12

Desenvolvendo ainda mais a resposta de Nick:

$("#myDiv").css({'position':'absolute','visibility':'hidden', 'display':'block'});
optionHeight = $("#myDiv").height();
$("#myDiv").css({'position':'static','visibility':'visible', 'display':'none'});

Eu achei que é melhor fazer isso:

$("#myDiv").css({'position':'absolute','visibility':'hidden', 'display':'block'});
optionHeight = $("#myDiv").height();
$("#myDiv").removeAttr('style');

A configuração de atributos CSS os inserirá em linha, o que substituirá quaisquer outros atributos que você tiver no seu arquivo CSS. Ao remover o atributo style no elemento HTML, tudo volta ao normal e continua oculto, pois estava oculto em primeiro lugar.

Gregory Bolkenstijn
fonte
1
$("#myDiv").css({'position':'absolute','visibility':'hidden', 'display':'block'}); optionHeight = $("#myDiv").height(); $("#myDiv").css({'position':'','visibility':'', 'display':''});
Aaron
9
... a menos que você já tenha estilos embutidos no elemento. Qual seria o caso se você ocultasse o elemento com jQuery.
Muhd
4

Você também pode posicionar o div oculto na tela com uma margem negativa em vez de usar a tela: nenhuma, muito parecida com a técnica de substituição da imagem de recuo do texto.

por exemplo.

position:absolute;
left:  -2000px;
top: 0;

Dessa forma, a altura () ainda está disponível.

vaughanos
fonte
3

Eu tento encontrar a função de trabalho para o elemento oculto, mas percebo que o CSS é muito complexo do que todos pensam. Existem muitas técnicas de layout novas no CSS3 que podem não funcionar para todas as respostas anteriores, como caixa flexível, grade, coluna ou mesmo elemento dentro de elemento pai complexo.

exemplo flexibox insira a descrição da imagem aqui

Penso que a única solução sustentável e simples é a renderização em tempo real . Nesse momento, o navegador deve fornecer o tamanho correto do elemento .

Infelizmente, o JavaScript não fornece nenhum evento direto para notificar quando o elemento é exibido ou oculto. No entanto, eu crio alguma função baseada na API de modificação de atributo do DOM que executará a função de retorno de chamada quando a visibilidade do elemento for alterada.

$('[selector]').onVisibleChanged(function(e, isVisible)
{
    var realWidth = $('[selector]').width();
    var realHeight = $('[selector]').height();

    // render or adjust something
});

Para mais informações, visite o meu projeto no GitHub.

https://github.com/Soul-Master/visible.event.js

demo: http://jsbin.com/ETiGIre/7

Soul_Master
fonte
Obrigado pela sua resposta. Também me inspirou a usar o MutationObservers. Mas observe que sua biblioteca verifica apenas as alterações de visibilidade causadas por stylealterações de atributo (consulte a linha 59 aqui ), enquanto as alterações de visibilidade também podem ser causadas por alterações no classatributo.
Ashraf Sabry
Você está certo. A instrução correta deve ser e.attributeName! == 'style' && e.attributeName! == 'className'
Soul_Master
2

Seguindo a solução de Nick Craver, definir a visibilidade do elemento permite obter dimensões precisas. Eu usei essa solução com muita frequência. No entanto, tendo que redefinir os estilos manualmente, cheguei a achar isso complicado, já que ao modificar o posicionamento / exibição inicial do elemento no meu css durante o desenvolvimento, muitas vezes esqueço de atualizar o código javascript relacionado. O código a seguir não redefine os estilos, por exemplo, mas remove os estilos embutidos adicionados pelo javascript:

$("#myDiv")
.css({
    position:   'absolute',
    visibility: 'hidden',
    display:    'block'
});

optionHeight = $("#myDiv").height();
optionWidth = $("#myDiv").width();

$("#myDiv").attr('style', '');

A única suposição aqui é que não pode haver outros estilos embutidos ou eles serão removidos também. O benefício aqui, no entanto, é que os estilos do elemento são retornados ao que eram na folha de estilo css. Como conseqüência, você pode escrever isso como uma função na qual um elemento é passado e uma altura ou largura é retornada.

Outro problema que encontrei ao definir os estilos em linha via js é que, ao lidar com transições por meio do css3, você é forçado a adaptar os pesos de suas regras de estilo para serem mais fortes do que um estilo em linha, o que às vezes pode ser frustrante.

Prusprus
fonte
1

Por definição, um elemento só tem altura se estiver visível.

Apenas curioso: por que você precisa da altura de um elemento oculto?

Uma alternativa é ocultar efetivamente um elemento, colocando-o para trás (usando o z-index) de algum tipo de sobreposição).

cleto
fonte
1
Eu preciso da altura de um elemento, que fica oculto durante o tempo que eu quero a altura dele. Estou construindo um plugin e eu preciso para reunir alguns dados como i o init-lo
mkoryak
2
outro motivo é para centralizar as coisas com javascript que ainda não estão visíveis
Simon_Weaver
@cletus Se você apenas está curioso para escrever comentários, isso geralmente ocorre na codificação de front-end para obter o tamanho de um elemento oculto.
Rantiev 8/04
1

Na minha circunstância, eu também tinha um elemento oculto que me impedia de obter o valor da altura, mas não era o elemento em si, mas um dos pais ... então, verifiquei um dos meus plugins para ver se é oculto, encontre o elemento oculto mais próximo. Aqui está um exemplo:

var $content = $('.content'),
    contentHeight = $content.height(),
    contentWidth = $content.width(),
    $closestHidden,
    styleAttrValue,
    limit = 20; //failsafe

if (!contentHeight) {
    $closestHidden = $content;
    //if the main element itself isn't hidden then roll through the parents
    if ($closestHidden.css('display') !== 'none') { 
        while ($closestHidden.css('display') !== 'none' && $closestHidden.size() && limit) {
            $closestHidden = $closestHidden.parent().closest(':hidden');
            limit--;
        }
    }
    styleAttrValue = $closestHidden.attr('style');
    $closestHidden.css({
        position:   'absolute',
        visibility: 'hidden',
        display:    'block'
    });
    contentHeight = $content.height();
    contentWidth = $content.width();

    if (styleAttrValue) {
        $closestHidden.attr('style',styleAttrValue);
    } else {
        $closestHidden.removeAttr('style');
    }
}

Na verdade, essa é uma amálgama das respostas de Nick, Gregory e Eyelidlessness para fornecer o uso do método aprimorado de Gregory, mas utiliza os dois métodos, caso haja algo no atributo de estilo que você queira colocar de volta e procure por um elemento pai.

Minha única reclamação com a minha solução é que o laço entre os pais não é totalmente eficiente.

marksyzm
fonte
1

Uma solução alternativa é criar uma div principal fora do elemento do qual você deseja obter a altura, aplicar uma altura de '0' e ocultar qualquer estouro. Em seguida, pegue a altura do elemento filho e remova a propriedade overflow do pai.

var height = $("#child").height();
// Do something here
$("#parent").append(height).removeClass("overflow-y-hidden");
.overflow-y-hidden {
  height: 0px;
  overflow-y: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="parent" class="overflow-y-hidden">
  <div id="child">
    This is some content I would like to get the height of!
  </div>
</div>

Matt
fonte
0

Aqui está um script que escrevi para lidar com todos os métodos de dimensão do jQuery para elementos ocultos, mesmo descendentes de pais ocultos. Observe que, é claro, há um impacto no desempenho usando isso.

// Correctly calculate dimensions of hidden elements
(function($) {
    var originals = {},
        keys = [
            'width',
            'height',
            'innerWidth',
            'innerHeight',
            'outerWidth',
            'outerHeight',
            'offset',
            'scrollTop',
            'scrollLeft'
        ],
        isVisible = function(el) {
            el = $(el);
            el.data('hidden', []);

            var visible = true,
                parents = el.parents(),
                hiddenData = el.data('hidden');

            if(!el.is(':visible')) {
                visible = false;
                hiddenData[hiddenData.length] = el;
            }

            parents.each(function(i, parent) {
                parent = $(parent);
                if(!parent.is(':visible')) {
                    visible = false;
                    hiddenData[hiddenData.length] = parent;
                }
            });
            return visible;
        };

    $.each(keys, function(i, dimension) {
        originals[dimension] = $.fn[dimension];

        $.fn[dimension] = function(size) {
            var el = $(this[0]);

            if(
                (
                    size !== undefined &&
                    !(
                        (dimension == 'outerHeight' || 
                            dimension == 'outerWidth') &&
                        (size === true || size === false)
                    )
                ) ||
                isVisible(el)
            ) {
                return originals[dimension].call(this, size);
            }

            var hiddenData = el.data('hidden'),
                topHidden = hiddenData[hiddenData.length - 1],
                topHiddenClone = topHidden.clone(true),
                topHiddenDescendants = topHidden.find('*').andSelf(),
                topHiddenCloneDescendants = topHiddenClone.find('*').andSelf(),
                elIndex = topHiddenDescendants.index(el[0]),
                clone = topHiddenCloneDescendants[elIndex],
                ret;

            $.each(hiddenData, function(i, hidden) {
                var index = topHiddenDescendants.index(hidden);
                $(topHiddenCloneDescendants[index]).show();
            });
            topHidden.before(topHiddenClone);

            if(dimension == 'outerHeight' || dimension == 'outerWidth') {
                ret = $(clone)[dimension](size ? true : false);
            } else {
                ret = $(clone)[dimension]();
            }

            topHiddenClone.remove();
            return ret;
        };
    });
})(jQuery);
ausência de pálpebra
fonte
A resposta por eyelidlessness parece perfeita para o que eu preciso, mas não funciona com o jQuery que estou executando: 1.3.2 Faz com que o jQuery execute um this.clone (ou algo muito próximo) não é uma função. Alguém tem alguma idéia de como corrigi-lo?
0

Se você já exibiu o elemento na página anteriormente, pode simplesmente pegar a altura diretamente do elemento DOM (alcançável no jQuery com .get (0)), pois ele é definido mesmo quando o elemento está oculto:

$('.hidden-element').get(0).height;

o mesmo para a largura:

$('.hidden-element').get(0).width;

(graças a Skeets O'Reilly pela correção)

Luca C.
fonte
Retornaundefined
Jake Wilson
1
Certamente isso só funcionará se você já tiver exibido o elemento na página anteriormente. Para elementos que sempre foram display='none', isso não funcionará.
Skeets