jQuery - Uzyskaj szerokość elementu, gdy jest niewidoczny (wyświetlanie: brak)

109

Wygląda na to, że w jQuery, gdy element nie jest widoczny, width () zwraca 0. Ma to sens, ale muszę uzyskać szerokość tabeli, aby ustawić szerokość elementu nadrzędnego, zanim pokażę go.

Jak zauważono poniżej, w rodzicu znajduje się tekst, który sprawia, że ​​rodzic wypacza się i wygląda paskudnie. Chcę, aby rodzic był tak szeroki jak stół i zawijany tekst.

<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 zawsze zwraca 0, ponieważ nie jest widoczne (jest to moje przypuszczenie, ponieważ daje mi liczbę, gdy jest widoczna). Czy istnieje sposób, aby uzyskać szerokość stołu bez pokazywania rodzica?

Jaskółka oknówka
źródło

Odpowiedzi:

95

Oto sztuczka, której użyłem. Wymaga to dodania pewnych właściwości CSS, aby jQuery pomyślał, że element jest widoczny, ale w rzeczywistości jest nadal ukryty.

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

To trochę hack, ale wydaje mi się, że działa dobrze.

AKTUALIZACJA

Od tego czasu napisałem post na blogu, który obejmuje ten temat. Metoda zastosowana powyżej może być problematyczna, ponieważ resetujesz właściwości CSS do pustych wartości. Co by było, gdyby wcześniej mieli wartości? Zaktualizowane rozwiązanie korzysta z metody swap (), która została znaleziona w kodzie źródłowym jQuery.

Kod z przywoływanego posta na blogu:

//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
źródło
1
Czy nie spowoduje to dwukrotnego odświeżenia strony przez przeglądarkę? Jeśli tak, jest to nieoptymalne rozwiązanie.
marcusklaas
@Tim Banks bardzo miło! Właściwie napisałem podobne rozszerzenie. To, co zauważyłem, to bardzo dziwne zachowanie w przeglądarce Firefox , width () zwraca 0, zarówno dla twojej, jak i dla mojej wtyczki. A ponadto nigdy nie bierze pod uwagę wypełnienia. jsfiddle.net/67cgB . Najtrudniej jest mi dowiedzieć się, co jeszcze mogę zrobić, aby to naprawić.
Mark Pieszak - Trilon.io
Jak mogę użyć tego hacka wewnątrz akordeonu jQuery, aby uzyskać ukryte wymiary akordeonu?
Deepak Ingole
1
@FercoCQ Dodałem kod z podanego posta na blogu.
Tim Banks
1
.andSelf () jest przestarzała w jQuery 1.8+ i usunięta w jQuery 3+ . Jeśli używasz jQuery 1.8+, zamień .andSelf () na .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
źródło
brudna fajna sztuczka !!! Upewnij się, że klon ma odpowiednie właściwości css (np. Jeśli rozmiar czcionki oryginalnego elementu to 15, użyj clone.css ("widoczność", "ukryty"). Css ("rozmiar czcionki", "15 pikseli" );)
alex
18
Zwróć uwagę, że to nie zadziała, jeśli Twój element odziedziczył szerokość po jakimkolwiek rodzicu. Po prostu odziedziczy niepoprawną szerokość ciała.
Dziekan
3
I zmodyfikowane clone.css("visibility","hidden");do clone.css({"visibility":"hidden", "display":"table"});którego rozwiązuje problem wskazał @Dean
isapir
Wielkie dzięki! Działało z niewielkimi zmianami w moim przypadku: zmieniłem go, aby obsługiwał obiekt do klonowania i selektor wewnątrz niego, aby uzyskać rzeczywistą szerokość. Nie zawsze chcemy mierzyć ten sam obiekt główny, który jest ukryty.
falsarella
Kiedyś stosowałem to samo podejście i tego dnia otrzymałem kilka głosów przeciw. Dziwne, że masz tyle pozytywnych głosów: D Powiem Ci, dlaczego to złe rozwiązanie i na co wskazałem pod moją odpowiedzią kilka lat temu. Po skopiowaniu elementu i umieszczeniu go bezpośrednio w treści, z całą pewnością usuwasz wszystkie CSS związane z tym konkretnym elementem. Na przykład. z tego: #some wrapper .hidden_element{/*Some CSS*/}Robisz tak: body .hidden_element. Nie #some_wrapper .hidden_elementjest już używany! W rezultacie obliczony rozmiar może różnić się od rozmiaru oryginalnego. TLTR: Po prostu tracisz styl
zamiast tego
8

Opierając się na odpowiedzi Robertsa, oto moja funkcja. Działa to dla mnie, jeśli element lub jego rodzic został wyblakły za pośrednictwem jQuery, może uzyskać wymiary wewnętrzne lub zewnętrzne, a także zwraca wartości przesunięcia.

/ edit1: przepisano funkcję. jest teraz mniejszy i można go wywołać bezpośrednio na obiekcie

/ edit2: funkcja wstawi teraz klon zaraz po oryginalnym elemencie zamiast body, umożliwiając klonowi zachowanie dziedziczonych wymiarów.

$.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
źródło
Wersja YUI dla tych, którzy jej potrzebują: gist.github.com/samueljseay/13c2e69508563b2f0458
Code
Czy offsetTop jest poprawny dla elementu, biorąc pod uwagę, że jest to klon, a nie „prawdziwy” element? Czy powinieneś użyć $ this.offset (). Top? Ciekawi Cię też, do czego używasz górnej / lewej strony? Używam tylko „szerokości”, ale zastanawiam się, czy z jakiegoś powodu nie powinienem martwić się tymi przesunięciami.
Terry,
3

Dziękuję za zamieszczenie powyższej funkcji realWidth, naprawdę mi pomogła. W oparciu o powyższą funkcję "realWidth" napisałem reset CSS (powód opisany poniżej).

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” pobiera szerokość istniejącego znacznika. Przetestowałem to z niektórymi tagami graficznymi. Problem polegał na tym, że gdy obraz ma wymiar CSS na szerokość (lub maksymalną szerokość), nigdy nie uzyskasz rzeczywistego wymiaru tego obrazu. Być może img ma „max-width: 100%”, funkcja „realWidth” klonuje go i dołącza do treści. Jeśli oryginalny rozmiar obrazu jest większy niż ciało, otrzymasz rozmiar ciała, a nie rzeczywisty rozmiar tego obrazu.

Robert
źródło
3

Próbuję znaleźć działającą funkcję dla ukrytego elementu, ale zdaję sobie sprawę, że CSS jest o wiele bardziej złożony niż wszyscy myślą. Istnieje wiele nowych technik układu w CSS3, które mogą nie działać dla wszystkich poprzednich odpowiedzi, takich jak elastyczne pudełko, siatka, kolumna lub nawet element wewnątrz złożonego elementu nadrzędnego.

przykład Flexibox wprowadź opis obrazu tutaj

Myślę, że jedynym zrównoważonym i prostym rozwiązaniem jest renderowanie w czasie rzeczywistym . W tym momencie przeglądarka powinna podać prawidłowy rozmiar elementu .

Niestety, JavaScript nie zapewnia żadnego bezpośredniego zdarzenia powiadamiającego o pokazaniu lub ukryciu elementu. Jednak tworzę funkcję opartą na DOM Attribute Modified API, która będzie wykonywała funkcję callback, gdy widoczność elementu ulegnie zmianie.

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

    // render or adjust something
});

Aby uzyskać więcej informacji, odwiedź mój projekt GitHub.

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

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

Władca dusz
źródło
4
CSS jest o wiele bardziej skomplikowany, niż wszyscy myślą. To prawda…
dgellow
3

Jeśli potrzebujesz szerokości czegoś, co jest ukryte i nie możesz tego ukryć z jakiegokolwiek powodu, możesz to sklonować, zmienić CSS tak, aby był wyświetlany poza stroną, uczynić go niewidocznym, a następnie zmierzyć. Użytkownik nie będzie mądrzejszy, jeśli zostanie później ukryty i usunięty.

Niektóre z innych odpowiedzi powodują, że widoczność jest ukryta, co działa, ale zajmuje to puste miejsce na stronie na ułamek sekundy.

Przykład:

$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
źródło
2
Nie zadziała, jeśli na wymiary elementu ma wpływ kontener nadrzędny lub bardziej złożony selektor CSS, oparty na hierarchii układu.
Sebastian Nowak,
2

Zanim weźmiesz szerokość, pokaż ekran nadrzędny, następnie wybierz szerokość i na koniec ukryj wyświetlacz nadrzędny. Tak jak śledzenie

$('#parent').show();
var tableWidth = $('#parent').children('table').outerWidth();
 $('#parent').hide();
if (tableWidth > $('#parent').width())
{
    $('#parent').width() = tableWidth;
}
kod źródłowy
źródło
2

Jednym z rozwiązań, choć nie zadziała we wszystkich sytuacjach, jest ukrycie elementu poprzez ustawienie krycia na 0. Całkowicie przezroczysty element będzie miał szerokość.

Wadą jest to, że element nadal będzie zajmował miejsce, ale nie będzie to problemem we wszystkich przypadkach.

Na przykład:

$(img).css("opacity", 0)  //element cannot be seen
width = $(img).width()    //but has width
Jake
źródło
1

Największym problemem, który jest pomijany w większości rozwiązań tutaj, jest to, że szerokość elementu jest często zmieniana przez CSS na podstawie zakresu w html.

Gdybym miał określić offsetWidth elementu, dołączając jego klon do body, gdy ma style, które mają zastosowanie tylko w jego bieżącym zakresie, uzyskałbym niewłaściwą szerokość.

na przykład:

// css

.module-container .my-elem {border: 60px solid black; }

teraz, gdy spróbuję określić szerokość mojego-elem w kontekście ciała, będzie ona zmniejszona o 120px. Możesz zamiast tego sklonować kontener modułu, ale Twój JS nie powinien znać tych szczegółów.

Nie testowałem tego, ale w zasadzie rozwiązanie Soul_Master wydaje się być jedynym, które może działać poprawnie. Ale niestety patrząc na implementację, prawdopodobnie będzie ona kosztowna w użyciu i niekorzystna dla wydajności (podobnie jak większość zaprezentowanych tutaj rozwiązań).

Jeśli to możliwe, użyj zamiast tego widoczności: ukryty. Spowoduje to nadal renderowanie elementu, umożliwiając obliczenie szerokości bez całego zamieszania.

Code Novitiate
źródło
0

Oprócz odpowiedzi wysłanej przez Tima Banksa , po której rozwiązałem mój problem, musiałem zmienić jedną prostą rzecz.

Ktoś inny może mieć ten sam problem; Pracuję z rozwijanym menu Bootstrap w menu rozwijanym. Ale tekst może być szerszy niż obecna treść w tym momencie (i nie ma wielu dobrych sposobów rozwiązania tego problemu za pomocą css).

Użyłem:

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

zamiast tego ustawia kontener na tabelę, która zawsze skaluje się na szerokość, jeśli zawartość jest szersza.

Mathijs Segers
źródło
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);
    });
});

Po najechaniu kursorem możemy obliczyć wysokość elementu listy.

rajashekar
źródło
0

Jak już powiedziano, metoda clone and attach w innym miejscu nie gwarantuje takich samych rezultatów, jak stylizacja może być inna.

Poniżej przedstawiam moje podejście. Wędruje w górę rodziców, szukając rodzica odpowiedzialnego za ukrycie, a następnie tymczasowo ją odsłania, aby obliczyć wymaganą szerokość, wysokość itp.

    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
źródło