jQuery - Obtenir la largeur de l'élément lorsqu'il n'est pas visible (affichage: aucun)

109

Il semble que dans jQuery lorsqu'un élément n'est pas visible, width () retourne 0. Cela a du sens, mais j'ai besoin d'obtenir la largeur d'un tableau afin de définir la largeur du parent avant d'afficher le parent.

Comme indiqué ci-dessous, il y a du texte dans le parent, ce qui le rend biaisé et semble méchant. Je veux que le parent soit aussi large que la table et que le texte soit enveloppé.

<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 renvoie toujours 0 car il n'est pas visible (c'est mon estimation car il me donne un nombre lorsqu'il est visible). Existe-t-il un moyen d'obtenir la largeur du tableau sans rendre le parent visible?

Martin
la source

Réponses:

95

Voici une astuce que j'ai utilisée. Cela implique d'ajouter des propriétés CSS pour faire croire à jQuery que l'élément est visible, mais en fait il est toujours caché.

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

C'est une sorte de hack, mais cela semble bien fonctionner pour moi.

METTRE À JOUR

J'ai depuis écrit un article de blog qui couvre ce sujet. La méthode utilisée ci-dessus peut être problématique car vous réinitialisez les propriétés CSS sur des valeurs vides. Et s'ils avaient des valeurs auparavant? La solution mise à jour utilise la méthode swap () qui a été trouvée dans le code source jQuery.

Code d'un article de blog référencé:

//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
la source
1
Le navigateur ne redessine-t-il pas la page deux fois? Si tel est le cas, il s'agit d'une solution sous-optimale.
marcusklaas
@Tim Banks très gentil! J'ai en fait écrit une extension similaire. Ce que j'ai remarqué, c'est un comportement très étrange dans Firefox , width () renvoie 0, pour votre et mon plugin à la fois. Et en plus de cela, il ne prend jamais en compte le rembourrage. jsfiddle.net/67cgB . J'ai du mal à trouver ce que je peux faire d'autre pour y remédier.
Mark Pieszak - Trilon.io
Comment puis-je utiliser ce hack dans l'accordéon jquery pour obtenir des dimensions d'accordéon cachées? Besoin de votre aide.
Deepak Ingole
1
@FercoCQ J'ai ajouté le code du billet de blog référencé.
Tim Banks
1
.andSelf () est obsolète dans jQuery 1.8+ et supprimé dans jQuery 3+ . Si vous utilisez jQuery 1.8+, remplacez .andSelf () par .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
la source
sale bon truc !!! Assurez-vous que le clone a les bonnes propriétés css (par exemple, si la taille de la police de l'élément d'origine est de 15, vous devez utiliser clone.css ("visibilité", "hidden"). Css ("font-size", "15px") );)
alex
18
Juste pour souligner, cela ne fonctionnera pas si votre élément a hérité de sa largeur d'un parent. Il héritera simplement de la largeur du corps qui est incorrecte.
Dean
3
J'ai modifié clone.css("visibility","hidden");à clone.css({"visibility":"hidden", "display":"table"});qui résout le problème signalé par @Dean
isapir
Grand merci! Cela a fonctionné avec de petits changements pour mon cas: je l'ai changé pour prendre en charge un objet à cloner et un sélecteur à l'intérieur pour en obtenir la largeur réelle. Nous ne souhaitons pas toujours mesurer le même objet racine caché.
falsarella
J'avais l'habitude d'utiliser la même approche et j'ai eu beaucoup de votes négatifs ce jour-là. Bizarre que vous ayez autant de votes positifs: D Je vais vous dire pourquoi c'est une mauvaise solution et ce qui a été signalé sous ma réponse il y a quelques années. Une fois que vous avez copié l'élément et l'avez placé directement dans le corps, vous supprimez certainement tous les CSS liés à cet élément spécifique. Par exemple. de ceci: #some wrapper .hidden_element{/*Some CSS*/}Vous faites ceci: body .hidden_element. Le #some_wrapper .hidden_elementn'est plus utilisé! Par conséquent, votre taille calculée peut différer de sa taille d'origine. TLTR: Vous perdez juste des styles
place du
8

Basé sur la réponse de Roberts, voici ma fonction. Cela fonctionne pour moi si l'élément ou son parent a été effacé via jQuery, peut obtenir des dimensions internes ou externes et renvoie également les valeurs de décalage.

/ edit1: réécrit la fonction. il est maintenant plus petit et peut être appelé directement sur l'objet

/ edit2: la fonction insère désormais le clone juste après l'élément d'origine au lieu du corps, ce qui permet au clone de conserver les dimensions héritées.

$.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
la source
Version YUI pour ceux qui en ont besoin: gist.github.com/samueljseay/13c2e69508563b2f0458
Code Noviciat
OffsetTop est-il correct pour l'élément étant donné qu'il s'agit du clone et non de l'élément «réel»? Devriez-vous utiliser $ this.offset (). Top? Aussi, curieux de savoir pourquoi vous utilisez le haut / gauche? J'utilise uniquement «width» mais je me demande si je devrais m'inquiéter de ces décalages pour une raison quelconque.
Terry
3

Merci d'avoir publié la fonction realWidth ci-dessus, cela m'a vraiment aidé. Sur la base de la fonction "realWidth" ci-dessus, j'ai écrit, une réinitialisation CSS, (raison décrite ci-dessous).

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" obtient la largeur d'une balise existante. J'ai testé cela avec quelques balises d'image. Le problème était que lorsque l'image a donné une dimension CSS par largeur (ou largeur maximale), vous n'obtiendrez jamais la dimension réelle de cette image. Peut-être que l'img a "max-width: 100%", la fonction "realWidth" le clone et l'ajoute au corps. Si la taille d'origine de l'image est plus grande que le corps, alors vous obtenez la taille du corps et non la taille réelle de cette image.

Robert
la source
3

J'essaie de trouver une fonction fonctionnelle pour l'élément caché mais je me rends compte que CSS est beaucoup plus complexe que tout le monde ne le pense. Il existe de nombreuses nouvelles techniques de mise en page dans CSS3 qui pourraient ne pas fonctionner pour toutes les réponses précédentes telles que la boîte flexible, la grille, la colonne ou même l'élément à l'intérieur d'un élément parent complexe.

exemple de flexibox entrez la description de l'image ici

Je pense que la seule solution durable et simple est le rendu en temps réel . À ce moment-là, le navigateur devrait vous donner la taille d'élément correcte .

Malheureusement, JavaScript ne fournit aucun événement direct pour notifier lorsqu'un élément est affiché ou masqué. Cependant, je crée une fonction basée sur l'API DOM Attribute Modified qui exécutera la fonction de rappel lorsque la visibilité de l'élément est modifiée.

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

    // render or adjust something
});

Pour plus d'informations, veuillez visiter mon projet GitHub.

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

démo: http://jsbin.com/ETiGIre/7

Maître de l'âme
la source
4
CSS est beaucoup plus complexe que tout le monde ne le pense. C'est tellement vrai…
dgellow
3

Si vous avez besoin de la largeur de quelque chose qui est caché et que vous ne pouvez pas le masquer pour une raison quelconque, vous pouvez le cloner, modifier le CSS pour qu'il s'affiche hors de la page, le rendre invisible, puis le mesurer. L'utilisateur ne sera pas plus sage s'il est masqué et supprimé par la suite.

Certaines des autres réponses ici ne font que masquer la visibilité, ce qui fonctionne, mais cela prendra un espace vide sur votre page pendant une fraction de seconde.

Exemple:

$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
la source
2
Cela ne fonctionnera pas si les dimensions de l'élément sont influencées par un conteneur parent ou un sélecteur CSS plus complexe, basé sur la hiérarchie de disposition.
Sebastian Nowak
2

Avant de prendre la largeur, faites apparaître l'affichage parent, puis prenez la largeur et enfin faites masquer l'affichage parent. Tout comme suivre

$('#parent').show();
var tableWidth = $('#parent').children('table').outerWidth();
 $('#parent').hide();
if (tableWidth > $('#parent').width())
{
    $('#parent').width() = tableWidth;
}
code source
la source
2

Une solution, bien que cela ne fonctionne pas dans toutes les situations, est de masquer l'élément en définissant l'opacité sur 0. Un élément complètement transparent aura une largeur.

L'inconvénient est que l'élément prendra toujours de la place, mais ce ne sera pas un problème dans tous les cas.

Par exemple:

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

Le plus gros problème qui échappe à la plupart des solutions ici est que la largeur d'un élément est souvent modifiée par CSS en fonction de sa portée en html.

Si je devais déterminer offsetWidth d'un élément en ajoutant un clone de celui-ci au corps quand il a des styles qui ne s'appliquent que dans sa portée actuelle, j'obtiendrais la mauvaise largeur.

par exemple:

// css

.module-container .my-elem {bordure: 60px noir uni; }

maintenant, quand j'essaie de déterminer la largeur de mon-elem dans le contexte du corps, il sera de 120px. Vous pouvez plutôt cloner le conteneur du module, mais votre JS ne devrait pas avoir à connaître ces détails.

Je ne l'ai pas testé mais essentiellement la solution de Soul_Master semble être la seule qui puisse fonctionner correctement. Mais malheureusement, en regardant l'implémentation, il sera probablement coûteux à utiliser et mauvais pour les performances (comme la plupart des solutions présentées ici le sont également.)

Si cela est possible, utilisez la visibilité: masqué à la place. Cela rendra toujours l'élément, vous permettant de calculer la largeur sans trop de bruit.

Code Noviciat
la source
0

En plus de la réponse publiée par Tim Banks , qui a suivi pour résoudre mon problème, j'ai dû modifier une chose simple.

Quelqu'un d'autre pourrait avoir le même problème; Je travaille avec une liste déroulante Bootstrap dans une liste déroulante. Mais le texte peut être plus large que le contenu actuel à ce stade (et il n'y a pas beaucoup de bonnes façons de résoudre cela via css).

J'ai utilisé:

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

à la place, ce qui définit le conteneur sur une table dont la largeur est toujours mise à l'échelle si le contenu est plus large.

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

En survolant, nous pouvons calculer la hauteur de l'élément de liste.

Rajashekar
la source
0

Comme cela a été dit précédemment, la méthode cloner et attacher ailleurs ne garantit pas les mêmes résultats car le style peut être différent.

Voici mon approche. Il fait remonter les parents à la recherche du parent responsable de la dissimulation, puis l'affiche temporairement pour calculer la largeur, la hauteur, 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
la source