jQuery - Obtener el ancho del elemento cuando no está visible (Pantalla: Ninguno)

108

Parece que en jQuery cuando un elemento no es visible width () devuelve 0. Tiene sentido, pero necesito obtener el ancho de una tabla para establecer el ancho del padre antes de mostrar el padre.

Como se indica a continuación, hay texto en el padre que hace que el padre se desvíe y se vea desagradable. Quiero que el padre sea tan ancho como la mesa y que el texto se ajuste.

<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 siempre devuelve 0 ya que no es visible (supongo que ya que me da un número cuando está visible). ¿Hay alguna forma de obtener el ancho de la tabla sin que el padre sea visible?

Martín
fuente

Respuestas:

94

Aquí hay un truco que he usado. Implica agregar algunas propiedades CSS para hacer que jQuery piense que el elemento es visible, pero de hecho aún está oculto.

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

Es una especie de truco, pero parece funcionar bien para mí.

ACTUALIZAR

Desde entonces, escribí una publicación de blog que cubre este tema. El método utilizado anteriormente tiene el potencial de ser problemático ya que está restableciendo las propiedades de CSS a valores vacíos. ¿Y si tuvieran valores previamente? La solución actualizada utiliza el método swap () que se encontró en el código fuente de jQuery.

Código de la publicación de 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
fuente
1
¿No hará que el navegador vuelva a dibujar la página dos veces? Si es así, esta es una solución subóptima.
marcusklaas
@Tim Banks muy agradable! De hecho, escribí una extensión similar. Lo que he notado es un comportamiento muy extraño en Firefox , width () devuelve 0, para usted y para mi complemento. Y además de eso, nunca tiene en cuenta el acolchado. jsfiddle.net/67cgB . Me está costando mucho saber qué más puedo hacer para solucionarlo.
Mark Pieszak - Trilon.io
¿Cómo puedo usar este truco dentro del acordeón jquery para obtener dimensiones ocultas del acordeón? Necesito tu ayuda.
Deepak Ingole
1
@FercoCQ Agregué el código de la publicación de blog a la que se hace referencia.
Tim Banks
1
.andSelf () está obsoleto en jQuery 1.8+ y eliminado en jQuery 3+ . Si está utilizando jQuery 1.8+, reemplace .andSelf () con .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
fuente
buen truco sucio !!! Asegúrese de que el clon tenga las propiedades CSS correctas (por ejemplo, si el tamaño de fuente del elemento original es 15, debe usar clone.css ("visibilidad", "oculto"). Css ("tamaño de fuente", "15px" );)
alex
18
Solo para señalar, esto no funcionará si su elemento ha heredado su ancho de cualquier padre. Simplemente heredará el ancho del cuerpo que es incorrecto.
Dean
3
He modificado clone.css("visibility","hidden");a clone.css({"visibility":"hidden", "display":"table"});la que se resuelve el problema señalado por @Dean
isapir
¡Muchas gracias! Funcionó con pequeños cambios para mi caso: lo cambié para admitir un objeto para clonar y un selector dentro de él para obtener el ancho real del mismo. No todas las veces queremos medir el mismo objeto raíz que está oculto.
falsarella
Solía ​​usar el mismo enfoque y obtuve muchos votos negativos ese día. Es extraño que tengas tantos votos a favor: D Te diré por qué esta es una mala solución y qué se señaló en mi respuesta hace unos años. Una vez que copie el elemento y lo coloque directamente en el cuerpo, seguramente eliminará todo el CSS relacionado con este elemento específico. Por ejemplo. de esto: #some wrapper .hidden_element{/*Some CSS*/}Usted está haciendo esto: body .hidden_element. ¡Ya #some_wrapper .hidden_elementno se usa! Como resultado, su tamaño calculado puede diferir de su tamaño original. TLTR: Estás perdiendo estilos
cambio,
8

Según la respuesta de Robert, aquí está mi función. Esto funciona para mí si el elemento o su padre se han desvanecido a través de jQuery, pueden obtener dimensiones internas o externas y también devuelven los valores de compensación.

/ edit1: reescribió la función. ahora es más pequeño y se puede llamar directamente en el objeto

/ edit2: la función ahora insertará el clon justo después del elemento original en lugar del cuerpo, haciendo posible que el clon mantenga las dimensiones heredadas.

$.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
fuente
Versión YUI para aquellos que lo necesitan: gist.github.com/samueljseay/13c2e69508563b2f0458
Code
¿Es offsetTop correcto para el artículo dado que es el clon y no el artículo "real"? ¿Debería usar $ this.offset (). Top? Además, ¿tienes curiosidad por saber para qué estás usando la parte superior / izquierda? Solo estoy usando 'ancho', pero me pregunto si debería preocuparme por esas compensaciones por alguna razón.
Terry
3

Gracias por publicar la función realWidth anterior, realmente me ayudó. Sobre la base de la función "realWidth" anterior, escribí, un restablecimiento de CSS, (la razón se describe a continuación).

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" obtiene el ancho de una etiqueta existente. Probé esto con algunas etiquetas de imagen. El problema era que, cuando la imagen tenía una dimensión CSS por ancho (o ancho máximo), nunca obtendría la dimensión real de esa imagen. Quizás, el img tiene "max-width: 100%", la función "realWidth" lo clona y lo agrega al cuerpo. Si el tamaño original de la imagen es mayor que el cuerpo, obtienes el tamaño del cuerpo y no el tamaño real de esa imagen.

Robert
fuente
3

Intento encontrar una función que funcione para el elemento oculto, pero me doy cuenta de que CSS es mucho más complejo de lo que todos piensan. Hay muchas nuevas técnicas de diseño en CSS3 que podrían no funcionar para todas las respuestas anteriores, como caja flexible, cuadrícula, columna o incluso elemento dentro del elemento principal complejo.

ejemplo de caja flexible ingrese la descripción de la imagen aquí

Creo que la única solución sostenible y sencilla es el renderizado en tiempo real . En ese momento, el navegador debería proporcionarle ese tamaño de elemento correcto .

Lamentablemente, JavaScript no proporciona ningún evento directo para notificar cuando se muestra u oculta un elemento. Sin embargo, creo una función basada en la API modificada de atributo DOM que ejecutará la función de devolución de llamada cuando se cambie la visibilidad del elemento.

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

    // render or adjust something
});

Para obtener más información, visite mi proyecto GitHub.

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

demostración: http://jsbin.com/ETiGIre/7

Soul_Master
fuente
4
CSS es mucho más complejo de lo que todos piensan. Esto es tan cierto…
dgellow
3

Si necesita el ancho de algo que está oculto y no puede mostrarlo por alguna razón, puede clonarlo, cambiar el CSS para que se muestre fuera de la página, hacerlo invisible y luego medirlo. El usuario no se dará cuenta si se oculta y elimina posteriormente.

Algunas de las otras respuestas aquí solo ocultan la visibilidad, lo que funciona, pero ocupará un espacio en blanco en su página durante una fracción de segundo.

Ejemplo:

$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
fuente
2
No funcionará si las dimensiones del elemento están influenciadas por un contenedor principal o un selector CSS más complejo, según la jerarquía de diseño.
Sebastian Nowak
2

Antes de tomar el ancho, haga que se muestre la pantalla principal, luego tome el ancho y finalmente oculte la pantalla principal. Como seguir

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

Una solución, aunque no funcionará en todas las situaciones, es ocultar el elemento estableciendo la opacidad en 0. Un elemento completamente transparente tendrá ancho.

El inconveniente es que el elemento seguirá ocupando espacio, pero eso no será un problema en todos los casos.

Por ejemplo:

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

El mayor problema que la mayoría de las soluciones pasan por alto aquí es que el ancho de un elemento a menudo es cambiado por CSS en función de su alcance en html.

Si tuviera que determinar offsetWidth de un elemento agregando un clon del mismo al cuerpo cuando tiene estilos que solo se aplican en su alcance actual, obtendría el ancho incorrecto.

por ejemplo:

// css

.module-container .my-elem {borde: 60px negro sólido; }

ahora, cuando trato de determinar el ancho de my-elem en el contexto del cuerpo, saldrá en 120px. En su lugar, podría clonar el contenedor del módulo, pero su JS no debería tener que conocer estos detalles.

No lo he probado, pero esencialmente la solución de Soul_Master parece ser la única que podría funcionar correctamente. Pero, desafortunadamente, al observar la implementación, es probable que sea costoso de usar y malo para el rendimiento (como también lo son la mayoría de las soluciones presentadas aquí).

Si es posible, utilice la visibilidad: oculta en su lugar. Esto seguirá representando el elemento, lo que le permitirá calcular el ancho sin tanto alboroto.

Código Noviciado
fuente
0

Además de la respuesta publicada por Tim Banks , que siguió a la solución de mi problema, tuve que editar una cosa simple.

Alguien más podría tener el mismo problema; Estoy trabajando con un menú desplegable Bootstrap en un menú desplegable. Pero el texto puede ser más amplio que el contenido actual en este punto (y no hay muchas buenas formas de resolverlo a través de CSS).

Solía:

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

en su lugar, establece el contenedor en una tabla que siempre se escala en ancho si el contenido es más ancho.

Mathijs Segers
fuente
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);
    });
});

Al pasar el mouse, podemos calcular la altura del elemento de la lista.

rajashekar
fuente
0

Como se ha dicho antes, el método de clonar y adjuntar en otro lugar no garantiza los mismos resultados, ya que el estilo puede ser diferente.

A continuación se muestra mi enfoque. Viaja por los padres buscando al padre responsable del escondite, luego lo muestra temporalmente para calcular el ancho, alto, 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
fuente