jQuery - получить ширину элемента, когда он не виден (отображение: нет)


109

Похоже, что в jQuery, когда элемент не виден, width () возвращает 0. Имеет смысл, но мне нужно получить ширину таблицы, чтобы установить ширину родительского элемента, прежде чем я покажу его.

Как указано ниже, в родительском элементе есть текст, из-за которого он искажается и выглядит неприятно. Я хочу, чтобы родительский элемент был такой же ширины, как таблица, и имел текстовый перенос.

<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 всегда возвращает 0, поскольку он не виден (я предполагаю, поскольку он дает мне номер, когда он виден). Есть ли способ получить ширину таблицы, не делая видимого родителя?

Ответы:


95

Вот трюк, который я использовал. Он включает добавление некоторых свойств CSS, чтобы jQuery считал элемент видимым, но на самом деле он все еще скрыт.

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

Это своего рода взлом, но, похоже, он мне подходит.

ОБНОВИТЬ

С тех пор я написал сообщение в блоге , посвященное этой теме. Использованный выше метод может вызвать проблемы, поскольку вы сбрасываете свойства CSS на пустые значения. Что, если бы у них были ценности раньше? В обновленном решении используется метод swap (), который был найден в исходном коде jQuery.

Код из указанного сообщения в блоге:

//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));

1
Не заставит ли браузер дважды перерисовывать страницу? Если так, то это неоптимальное решение.
marcusklaas

@ Тим Бэнкс очень мило! Я действительно написал подобное расширение. Я заметил очень странное поведение в Firefox , width () возвращает 0 как для вашего, так и для моего плагина. И, кроме того, он никогда не влияет на заполнение. jsfiddle.net/67cgB . Мне очень трудно понять, что еще можно сделать, чтобы это исправить.
Марк Пешак - Trilon.io

Как я могу использовать этот хак внутри jquery accordion, чтобы получить скрытые размеры аккордеона? Нужна ваша помощь.
Deepak Ingole

1
@FercoCQ Я добавил код из упомянутого сообщения в блоге.
Тим Бэнкс

1
.andSelf () устарел в jQuery 1.8+ и удален в jQuery 3+ . Если вы используете jQuery 1.8+, замените .andSelf () на .addBack () .
Тим

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"));

грязный трюк !!! Убедитесь, что клон имеет правильные свойства css (например, если размер шрифта исходного элемента равен 15, вы должны использовать clone.css ("visibility", "hidden"). Css ("font-size", "15px" );)
алекс

18
Отметим, что это не сработает, если ваш элемент унаследовал ширину от любого родителя. Он просто унаследует неверную ширину тела.
Дин

3
Я изменил clone.css("visibility","hidden");в clone.css({"visibility":"hidden", "display":"table"});которой решает вопрос указывал @Dean
isapir

Большое спасибо! В моем случае это сработало с небольшими изменениями: я изменил его, чтобы поддерживать объект для клонирования и селектор внутри него, чтобы получить его реальную ширину. Не всегда мы хотим измерить один и тот же скрытый корневой объект.
falsarella

Раньше я использовал тот же подход, и в тот день я получил множество отрицательных голосов. Странно, что у Вас так много голосов: D Я скажу Вам, почему это плохое решение и что было указано в моем ответе несколько лет назад. Как только вы скопируете элемент и поместите его прямо в тело, вы наверняка удалите весь CSS, связанный с этим конкретным элементом. Например. от этого: #some wrapper .hidden_element{/*Some CSS*/}Вы делаете это: body .hidden_element. Больше #some_wrapper .hidden_elementне используется! В результате рассчитанный вами размер может отличаться от исходного. TLTR: Вы просто теряете стили
вместо этого

8

Основываясь на ответе Робертса, вот моя функция. Это работает для меня, если элемент или его родительский элемент были затемнены с помощью jQuery, могут получать внутренние или внешние размеры, а также возвращают значения смещения.

/ edit1: переписал функцию. теперь он меньше и может вызываться непосредственно на объекте

/ edit2: теперь функция будет вставлять клон сразу после исходного элемента, а не тела, что позволяет клону сохранить унаследованные размеры.

$.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();

Версия YUI для тех, кому она нужна: gist.github.com/samueljseay/13c2e69508563b2f0458
Code

Правильно ли offsetTop для элемента, учитывая, что это клон, а не «настоящий» элемент? Стоит ли использовать $ this.offset (). Top? Кроме того, интересно, для чего вы используете верхнее / левое? Я использую только «ширину», но мне интересно, стоит ли мне по какой-то причине беспокоиться об этих смещениях.
Terry

3

Спасибо, что разместили выше функцию realWidth, она мне очень помогла. На основе приведенной выше функции "realWidth" я написал сброс CSS (причина описана ниже).

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 получает ширину существующего тега. Я тестировал это с помощью некоторых тегов изображений. Проблема заключалась в том, что когда изображение имеет размер CSS на ширину (или максимальную ширину), вы никогда не получите реальный размер этого изображения. Возможно, у img есть "max-width: 100%", функция "realWidth" клонирует его и добавляет в тело. Если исходный размер изображения больше, чем тело, то вы получаете размер тела, а не реальный размер этого изображения.


3

Я пытаюсь найти рабочую функцию для скрытого элемента, но понимаю, что CSS намного сложнее, чем все думают. В CSS3 есть много новых методов компоновки, которые могут не работать для всех предыдущих ответов, таких как гибкое поле, сетка, столбец или даже элемент внутри сложного родительского элемента.

пример флексибокса введите описание изображения здесь

Я думаю, что единственное устойчивое и простое решение - это рендеринг в реальном времени . В это время браузер должен предоставить вам правильный размер элемента .

К сожалению, JavaScript не предоставляет никаких прямых событий для уведомления, когда элемент отображается или скрыт. Однако я создаю некоторую функцию на основе API измененного атрибута DOM, которая будет выполнять функцию обратного вызова при изменении видимости элемента.

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

    // render or adjust something
});

Для получения дополнительной информации посетите мой проект GitHub.

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

демо: http://jsbin.com/ETiGIre/7


4
CSS намного сложнее, чем все думают. Это правда…
dgellow

3

Если вам нужна ширина чего-то, что скрыто, и вы не можете отменить это по какой-либо причине, вы можете клонировать это, изменить CSS, чтобы он отображался за пределами страницы, сделать его невидимым, а затем измерить. Пользователь не станет мудрее, если он будет скрыт и впоследствии удален.

Некоторые из других ответов здесь просто скрывают видимость, что работает, но на долю секунды он займет пустое место на вашей странице.

Пример:

$itemClone = $('.hidden-item').clone().css({
        'visibility': 'hidden',
        'position': 'absolute',
        'z-index': '-99999',
        'left': '99999999px',
        'top': '0px'
    }).appendTo('body');
var width = $itemClone.width();
$itemClone.remove();

2
Это не сработает, если на размеры элемента влияет родительский контейнер или более сложный селектор CSS, основанный на иерархии макета.
Себастьян Новак,

2

Прежде чем брать ширину, сделайте родительский дисплей показанным, затем возьмите ширину и, наконец, скройте родительский дисплей. Так же, как после

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

2

Одно из решений, хотя оно не будет работать во всех ситуациях, - это скрыть элемент, установив непрозрачность на 0. Полностью прозрачный элемент будет иметь ширину.

Недостатком является то, что элемент по-прежнему будет занимать место, но это не будет проблемой во всех случаях.

Например:

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

1

Самая большая проблема, которую упускают из виду большинство решений, заключается в том, что ширина элемента часто изменяется с помощью CSS в зависимости от того, где он находится в области html.

Если бы я должен был определить offsetWidth элемента, добавив его клон в body, когда у него есть стили, которые применяются только в его текущей области, я бы получил неправильную ширину.

например:

// css

.module-container .my-elem {граница: 60 ​​пикселей сплошной черный; }

теперь, когда я пытаюсь определить ширину my-elem в контексте тела, она будет на 120 пикселей. Вместо этого вы можете клонировать контейнер модуля, но ваш JS не должен знать эти детали.

Я не тестировал его, но, по сути, решение Soul_Master оказалось единственным, которое могло работать должным образом. Но, к сожалению, если посмотреть на реализацию, он, вероятно, будет дорогостоящим в использовании и плохим для производительности (как и большинство представленных здесь решений).

Если возможно, используйте вместо этого видимость: скрытый. Это все равно отрендерит элемент, что позволит вам вычислить ширину без лишних хлопот.


0

В дополнение к ответу Тима Бэнкса , который последовал за решением моей проблемы, мне пришлось отредактировать одну простую вещь.

У кого-то может быть такая же проблема; Я работаю с раскрывающимся списком Bootstrap в раскрывающемся списке. Но текст может быть шире, чем текущий контент на этом этапе (и не так много хороших способов решить это с помощью css).

Я использовал:

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

вместо этого, который устанавливает контейнер в таблицу, которая всегда масштабируется по ширине, если содержимое шире.


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);
    });
});

При наведении курсора мы можем вычислить высоту элемента списка.


0

Как было сказано ранее, метод клонирования и прикрепления в другом месте не гарантирует одинаковых результатов, поскольку стили могут отличаться.

Ниже мой подход. Он перемещается вверх по родителям в поисках родителя, ответственного за укрытие, затем временно показывает его, чтобы вычислить требуемую ширину, высоту и т. Д.

    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");
                }
            });
        }
    }

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.