jQuery - Obter largura do elemento quando não visível (exibição: nenhum)

108

Parece que no jQuery, quando um elemento não está visível, width () retorna 0. Faz sentido, mas preciso obter a largura de uma tabela para definir a largura do pai antes de mostrar o pai.

Conforme observado abaixo, há texto no pai, que faz o pai distorcer e parecer desagradável. Quero que o pai seja apenas tão largo quanto a mesa e que o texto seja quebrado.

<div id="parent">
    Text here ... Can get very long and skew the parent
    <table> ... </table>
    Text here too ... which is why I want to shrink the parent based on the table
</div>

CSS:

#parent
{
    display: none;
}

Javascript:

var tableWidth = $('#parent').children('table').outerWidth();
if (tableWidth > $('#parent').width())
{
    $('#parent').width(tableWidth);
}

tableWidth sempre retorna 0, pois não é visível (é meu palpite, pois ele me dá um número quando visível). Existe uma maneira de obter a largura da mesa sem tornar o pai visível?

Martin
fonte

Respostas:

94

Aqui está um truque que usei. Envolve adicionar algumas propriedades CSS para fazer jQuery pensar que o elemento está visível, mas na verdade ele ainda está oculto.

var $table = $("#parent").children("table");
$table.css({ position: "absolute", visibility: "hidden", display: "block" });
var tableWidth = $table.outerWidth();
$table.css({ position: "", visibility: "", display: "" });

É uma espécie de hack, mas parece funcionar bem para mim.

ATUALIZAR

Desde então, escrevi uma postagem no blog que cobre esse tópico O método usado acima pode ser problemático, pois você está redefinindo as propriedades CSS para valores vazios. E se eles tivessem valores anteriormente? A solução atualizada usa o método swap () que foi encontrado no código-fonte do jQuery.

Código da postagem do blog referenciada:

//Optional parameter includeMargin is used when calculating outer dimensions  
(function ($) {
$.fn.getHiddenDimensions = function (includeMargin) {
    var $item = this,
    props = { position: 'absolute', visibility: 'hidden', display: 'block' },
    dim = { width: 0, height: 0, innerWidth: 0, innerHeight: 0, outerWidth: 0, outerHeight: 0 },
    $hiddenParents = $item.parents().andSelf().not(':visible'),
    includeMargin = (includeMargin == null) ? false : includeMargin;

    var oldProps = [];
    $hiddenParents.each(function () {
        var old = {};

        for (var name in props) {
            old[name] = this.style[name];
            this.style[name] = props[name];
        }

        oldProps.push(old);
    });

    dim.width = $item.width();
    dim.outerWidth = $item.outerWidth(includeMargin);
    dim.innerWidth = $item.innerWidth();
    dim.height = $item.height();
    dim.innerHeight = $item.innerHeight();
    dim.outerHeight = $item.outerHeight(includeMargin);

    $hiddenParents.each(function (i) {
        var old = oldProps[i];
        for (var name in props) {
            this.style[name] = old[name];
        }
    });

    return dim;
}
}(jQuery));
Tim Banks
fonte
1
Isso não fará com que o navegador redesenhe a página duas vezes? Nesse caso, esta é uma solução abaixo do ideal.
marcusklaas
@Tim Banks muito bom! Na verdade, escrevi uma extensão semelhante. O que tenho notado é um comportamento muito estranho no Firefox , width () retorna 0, tanto para o seu quanto para o meu plugin. E, além disso, nunca leva em consideração o preenchimento. jsfiddle.net/67cgB . Estou tendo dificuldade em descobrir o que mais posso fazer para consertar isso.
Mark Pieszak - Trilon.io
Como posso usar este hack dentro do acordeão jquery para obter dimensões ocultas do acordeão? Precisa da sua ajuda.
Deepak Ingole
1
@FercoCQ Eu adicionei o código da postagem do blog referenciada.
Tim Banks de
1
.andSelf () está obsoleto no jQuery 1.8+ e removido no jQuery 3+ . Se você estiver usando jQuery 1.8+, substitua .andSelf () por .addBack () .
Tim
41
function realWidth(obj){
    var clone = obj.clone();
    clone.css("visibility","hidden");
    $('body').append(clone);
    var width = clone.outerWidth();
    clone.remove();
    return width;
}
realWidth($("#parent").find("table:first"));
Fábio Paiva
fonte
bom truque sujo !!! Certifique-se de que o clone tenha as propriedades css corretas (por exemplo, se o tamanho da fonte do elemento original for 15, você deve usar clone.css ("visibility", "hidden"). Css ("font-size", "15px" );)
alex de
18
Apenas para apontar, isso não funcionará se o seu elemento herdou sua largura de qualquer pai. Ele apenas herdará a largura do corpo, que está incorreta.
Dean
3
I modificada clone.css("visibility","hidden");para clone.css({"visibility":"hidden", "display":"table"});que resolve o problema apontado por @Dean
isapir
Ótimo, obrigado! Funcionou com pequenas mudanças no meu caso: eu mudei para suportar um objeto a ser clonado e um seletor dentro dele para obter a largura real dele. Nem todas as vezes queremos medir o mesmo objeto raiz que está oculto.
falsarella
Eu costumava usar a mesma abordagem e recebi muitos votos negativos naquele dia. Estranho que você tenha tantos votos positivos: D Vou te dizer por que essa é uma solução ruim e o que foi apontado na minha resposta há alguns anos. Depois de copiar o elemento e colocá-lo diretamente no corpo, com certeza você está removendo todos os CSS relacionados a este elemento específico. Por exemplo. a partir deste: #some wrapper .hidden_element{/*Some CSS*/}Você está fazendo isso: body .hidden_element. O #some_wrapper .hidden_elementnão é mais usado! Como resultado, o tamanho calculado pode ser diferente do tamanho original. TLTR: Você está apenas perdendo estilos
vez disso
8

Com base na resposta de Roberts, aqui está minha função. Isso funciona para mim se o elemento ou seu pai foram apagados via jQuery, pode obter dimensões internas ou externas e também retorna os valores de deslocamento.

/ edit1: reescreve a função. agora é menor e pode ser chamado diretamente no objeto

/ edit2: a função agora irá inserir o clone logo após o elemento original em vez do corpo, tornando possível para o clone manter as dimensões herdadas.

$.fn.getRealDimensions = function (outer) {
    var $this = $(this);
    if ($this.length == 0) {
        return false;
    }
    var $clone = $this.clone()
        .show()
        .css('visibility','hidden')
        .insertAfter($this);        
    var result = {
        width:      (outer) ? $clone.outerWidth() : $clone.innerWidth(), 
        height:     (outer) ? $clone.outerHeight() : $clone.innerHeight(), 
        offsetTop:  $clone.offset().top, 
        offsetLeft: $clone.offset().left
    };
    $clone.remove();
    return result;
}

var dimensions = $('.hidden').getRealDimensions();
Marco Kerwitz
fonte
Versão YUI para quem precisa: gist.github.com/samueljseay/13c2e69508563b2f0458
Código Noviciado
O offsetTop está correto para o item, visto que é o clone e não o item 'real'? Você deve usar $ this.offset (). Top? Além disso, está curioso para o que você está usando a parte superior / esquerda? Estou usando apenas 'largura', mas me pergunto se devo me preocupar com esses deslocamentos por algum motivo.
Terry
3

Obrigado por postar a função realWidth acima, ela realmente me ajudou. Com base na função "realWidth" acima, escrevi uma redefinição de CSS (motivo descrito abaixo).

function getUnvisibleDimensions(obj) {
    if ($(obj).length == 0) {
        return false;
    }

    var clone = obj.clone();
    clone.css({
        visibility:'hidden',
        width : '',
        height: '',
        maxWidth : '',
        maxHeight: ''
    });
    $('body').append(clone);
    var width = clone.outerWidth(),
        height = clone.outerHeight();
    clone.remove();
    return {w:width, h:height};
}

"realWidth" obtém a largura de uma tag existente. Eu testei isso com algumas tags de imagem. O problema era que, quando a imagem fornecia dimensão CSS por largura (ou largura máxima), você nunca obteria a dimensão real dessa imagem. Talvez, o img tenha "max-width: 100%", a função "realWidth" cloná-lo e anexá-lo ao corpo. Se o tamanho original da imagem for maior do que o corpo, você obtém o tamanho do corpo e não o tamanho real dessa imagem.

Robert
fonte
3

Tento encontrar uma função funcional para elementos ocultos, mas percebo que CSS é muito mais complexo do que todos pensam. Existem várias novas técnicas de layout em CSS3 que podem não funcionar para todas as respostas anteriores, como caixa flexível, grade, coluna ou mesmo elemento dentro de um elemento pai complexo.

exemplo flexibox insira a descrição da imagem aqui

Acho 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 é mostrado ou escondido. No entanto, eu crio algumas funções com base na API DOM Attribute Modified 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 obter mais informações, visite meu projeto GitHub.

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

demonstração: http://jsbin.com/ETiGIre/7

Soul_Master
fonte
4
CSS é muito mais complexo do que todos pensam Isso é tão verdade ...
abaixo de
3

Se você precisa da largura de algo que está oculto e não pode desocultá-lo por qualquer motivo, você pode cloná-lo, alterar o CSS para que seja exibido fora da página, torná-lo invisível e medi-lo. O usuário não ficará sabendo se ele for oculto e excluído posteriormente.

Algumas das outras respostas aqui apenas tornam a visibilidade oculta, o que funciona, mas ocupará um espaço em branco em sua página por uma fração de segundo.

Exemplo:

$itemClone = $('.hidden-item').clone().css({
        'visibility': 'hidden',
        'position': 'absolute',
        'z-index': '-99999',
        'left': '99999999px',
        'top': '0px'
    }).appendTo('body');
var width = $itemClone.width();
$itemClone.remove();
Rannmann
fonte
2
Não funcionará se as dimensões do elemento forem influenciadas por um contêiner pai ou um seletor CSS mais complexo, com base na hierarquia de layout.
Sebastian Nowak
2

Antes de obter a largura, faça com que a tela principal seja exibida, depois pegue a largura e, por fim, faça com que a tela principal seja oculta. Apenas como seguir

$('#parent').show();
var tableWidth = $('#parent').children('table').outerWidth();
 $('#parent').hide();
if (tableWidth > $('#parent').width())
{
    $('#parent').width() = tableWidth;
}
Código fonte
fonte
2

Uma solução, embora não funcione em todas as situações, é ocultar o elemento configurando a opacidade para 0. Um elemento completamente transparente terá largura.

A desvantagem é que o elemento ainda ocupará espaço, mas isso não será um problema em todos os casos.

Por exemplo:

$(img).css("opacity", 0)  //element cannot be seen
width = $(img).width()    //but has width
Jake
fonte
1

O maior problema que passa despercebido pela maioria das soluções aqui é que a largura de um elemento é frequentemente alterada por CSS com base em onde seu escopo é definido em html.

Se eu fosse determinar o offsetWidth de um elemento anexando um clone dele ao corpo quando ele tem estilos que se aplicam apenas ao seu escopo atual, eu obteria a largura errada.

por exemplo:

// css

.module-container .my-elem {borda: 60px totalmente preto; }

agora, quando tento determinar a largura do my-elem no contexto do corpo, ele estará fora de 120px. Em vez disso, você poderia clonar o contêiner do módulo, mas seu JS não deveria saber esses detalhes.

Eu não testei, mas essencialmente a solução do Soul_Master parece ser a única que pode funcionar corretamente. Mas, infelizmente, olhando para a implementação, provavelmente será caro de usar e ruim para o desempenho (como a maioria das soluções apresentadas aqui também são).

Se possível, use visibilidade: oculto. Isso ainda renderizará o elemento, permitindo que você calcule a largura sem toda a confusão.

Código Noviciado
fonte
0

Além da resposta postada por Tim Banks , que seguiu para resolver meu problema, tive que editar uma coisa simples.

Outra pessoa pode ter o mesmo problema; Estou trabalhando com uma lista suspensa Bootstrap em uma lista suspensa. Mas o texto pode ser mais amplo que o conteúdo atual neste ponto (e não há muitas boas maneiras de resolver isso por meio do css).

Eu usei:

$table.css({ position: "absolute", visibility: "hidden", display: "table" });

em vez disso, o que define o contêiner para uma mesa que sempre escala em largura se o conteúdo for maior.

Mathijs Segers
fonte
0
jQuery(".megamenu li.level0 .dropdown-container .sub-column ul .level1").on("mouseover", function () {
    var sum = 0;
    jQuery(this).find('ul li.level2').each(function(){
    sum -= jQuery(this).height();
    jQuery(this).parent('ul').css('margin-top', sum / 1.8);
    console.log(sum);
    });
});

Ao pairar, podemos calcular a altura do item da lista.

rajashekar
fonte
0

Como foi dito antes, o método de clonar e anexar em outro lugar não garante os mesmos resultados, pois o estilo pode ser diferente.

Abaixo está minha abordagem. Ele viaja pelos pais em busca do pai responsável pela ocultação e, em seguida, mostra-o temporariamente para calcular a largura, altura necessária, etc.

    var width = parseInt($image.width(), 10);
    var height = parseInt($image.height(), 10);

    if (width === 0) {

        if ($image.css("display") === "none") {

            $image.css("display", "block");
            width = parseInt($image.width(), 10);
            height = parseInt($image.height(), 10);
            $image.css("display", "none");
        }
        else {

            $image.parents().each(function () {

                var $parent = $(this);
                if ($parent.css("display") === "none") {

                    $parent.css("display", "block");
                    width = parseInt($image.width(), 10);
                    height = parseInt($image.height(), 10);
                    $parent.css("display", "none");
                }
            });
        }
    }

Ibraheem
fonte