jQuery: самый быстрый способ определить, виден ли хотя бы один дочерний элемент в контейнере

Как быстрее всего определить, виден ли хотя бы один дочерний элемент div в контейнере div?

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

if ($this.children('div:visible').length) {...

но он медленный, потому что проверяет всех детей (которых очень много).

Я предполагаю, что самый быстрый метод прекратит поиск, как только найдет первый видимый элемент, но выигрывает тот, кто быстрее :)

КСТАТИ:

Моей реальной ситуацией является основной контейнер, который содержит около 100 второстепенных контейнеров, каждый из которых содержит до 100 элементов. Я хочу определить, какие второстепенные контейнеры имеют хотя бы один видимый элемент. Элементы внизу скрываются и отображаются различными классами.

Спасибо.


person Nick    schedule 10.07.2012    source источник


Ответы (4)


JSPerf всех методов на этой странице

Отредактировано: Причина того, что мой первоначальный метод работает быстрее, заключается в том, что он использовал .find('>' + selector), где нужно использовать .children() (который перебирает все дочерние элементы и проверяет, соответствует ли элемент селектору).

Поскольку div — это нативно поддерживаемый селектор, а тестовый пример не содержит глубоко вложенных элементов, мое решение оказалось быстрым. Но после его нормализации он выглядит почти таким же, как решение qwertymks. JSPerf этих двух решений покажет, что его решение немного быстрее, потому что оно имеет на один вызов функции меньше.

Решения на этой странице являются общими: приведенный ниже код можно оптимизировать для конкретных случаев (например, тот факт, что селектор — это просто тег): http://jsfiddle.net/kFZJs/


Чтобы ускорить процесс, разделите селектор, потому что :visible не является родным селектором CSS.

Предпочтительное решение должно использовать как можно меньше jQuery, потому что желаемое решение должно быть производительным. Для этого изучите логику :visible.

Исходная функция содержит jQuery.support.reliableHiddenOffsets. Это можно безопасно убрать в пользу производительности, когда ваши дочерние элементы не являются ячейками таблицы (что используется только в IE8-).

Теперь напишите плагин jQuery (это не дорого):

 (function($) {
     $.fn.hasAtLeastOneVisibleChild = function(selector) {
         var $col = this.children(selector), i, elem;
         for (i=0; i<$col.length; i++) {
             var elem = $col[i];
             if (elem.offsetWidth !== 0 || elem.offsetHeight !== 0) {
                 return true;
             }
         }
         return false;
     };
 })(jQuery);
 // Usage:
 $this.hasAtLeastOneVisibleChild('div'); // True or false
person Rob W    schedule 10.07.2012
comment
$elem вводит в заблуждение, должно быть elem, но все равно получает +1 - person qwertymk; 10.07.2012
comment
Спасибо за это. Я пытаюсь реализовать ваше решение, но я попал в затруднительное положение. Вот код для проверки фактической проблемы. $('#clf').children('ul').each(function (i) {var $that = $(this);$that.children().each(function (){if ($(this).hasAtLeastOneVisibleChild('li')) {console.log("Found one for " + $that.attr('id'));} else {console.log("Found none for " + $that.attr('id'));}});}); Однако я получаю несколько console.log для каждого ul (13, 17...). Вы видите, почему? - person Nick; 10.07.2012
comment
@Nick Как только вы найдете совпадение, вам нужно выйти из цикла, используя return false;. См. также документы: api.jquery.com/each. - person Rob W; 10.07.2012
comment
@qwertymk Спасибо за место, я отредактировал эту часть, а также включил тест JSPerf. - person Rob W; 10.07.2012
comment
Это уладило дело. Спасибо еще раз. JSPerf довольно убедителен :) - person Nick; 10.07.2012
comment
Кстати, мой тестовый код 4 комментария назад имеет дополнительный цикл .each, который все портит. - person Nick; 10.07.2012
comment
@Nick Я просмотрел и отредактировал свой ответ. Теперь выводы совсем другие: в случае, когда большинство/все элементы видны, мое решение является лучшим. В случае, если все элементы скрыты, ваш исходный метод немного лучше. RE: Ваш код: он содержит $().children('ul').each() В каждом цикле вы используете $that.children().each и .hasAtLeastOneVisibleChild('li'). $that.children() уже является коллекцией <li>, у них не будет <li> дочерних элементов. - person Rob W; 10.07.2012
comment
Да, Роб, я заметил лишний $that.children().each. Я также упорно работал с вашими оригинальными проверками === elem, прежде чем вы изменили его на !==, но я понял, почему он не работает, а затем заметил, что вы его изменили! Еще раз спасибо за помощь. Кроме того, математика не совсем складывается: +1 от Adil, qwertymk и я должны получить больше +2 :) - person Nick; 11.07.2012
comment
К вашему сведению, Роб. Я протестировал локальную функцию и пропуск селектора (который мне не нужен) на jsperf .com/jquery-quickest-visible-check/4. Мне было бы интересно узнать теоретические различия в производительности между $.fn и локальной функцией, если вы понимаете такие вопросы :) - person Nick; 11.07.2012
comment
@Nick Это стоит нового вопроса. Разница вызвана поиском цепочки прототипов и цепочки областей видимости. Последний быстрее первого: jsperf.com/scope-chain-vs- поиск по цепочке прототипов. Однако не помещайте объявления функций в цикл: создание функций не бесплатно. - person Rob W; 11.07.2012
comment
Результаты сильно отличаются от Safari, Chrome и Firefox. Safari, кажется, не наказывает создание локальных функций, тогда как Firefox забивает его. - person Nick; 11.07.2012

Один из способов — перебирать дочерние элементы с помощью each(). Но это будет так же медленно, как и ваш код, если видимый элемент будет последним :)

Другой вариант — проверить высоту контейнера.

if($('#container').height() > 0) { ... }

Если дочерние элементы занимают пространство (высоту), то контейнер также будет иметь высоту > 0.

person algiecas    schedule 10.07.2012
comment
Что, если дети абсолютно позиционированы? Тогда проверка не будет надежной. - person Rob W; 10.07.2012
comment
Вот так. Не во всех случаях сработает, но в его случае может сработать :) И если сработает в его случае, то будет ОЧЕНЬ быстро - person algiecas; 10.07.2012
comment
Я поставил +1, потому что это действительно быстро (надежно, если точные условия известны заранее и выполнены). Я не включил ваш код в свой тестовый пример, потому что он несопоставим. - person Rob W; 10.07.2012

Сделай сам:

var $container = $('#container'), $children = $container.children(), found = false;

for (var i = 0; i < $container.length; i++) {
    var elem = $children[i];
    if (!( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none")){
        found = true;
        break;
    }
}

источник

person qwertymk    schedule 10.07.2012
comment
return внутри цикла? Вы имели в виду break? - person Rob W; 10.07.2012
comment
Ваш код не ограничен divs. Я создаю JSPerf для проверки всех методов. Я заменяю .children() на .children('div'), чтобы сравнение было равным. РЕДАКТИРОВАТЬ: width и height не определены, выдает ошибку в тестовом примере. - person Rob W; 10.07.2012
comment
Спасибо за это. Я работаю с усилиями Роба, но это тоже было доблестно :) - person Nick; 10.07.2012

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

isAtleastVisible = false;
$this.children('div').each(function (){
    if($(this).is(':visible') == true)
    {
       isAtleastVisible = true;
       return false;  //This will break each loop at the first visible div
    }
});

if(isAtleastVisible)
   alert("atleast one is visible");
else
   alert("None is visible");

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

Как объяснил @Rob W в ответе о работе is('visible'), который показывает, что функция проверяет видимость по этим параметрам.

  1. Ширина и высота равны нулю
  2. style.display нет

Вы можете использовать один из них для повышения производительности, предположим, что элементы скрыты, установив display = none. Это условие может заменить метод jQuery .is(':visible') == true оператором javascript .style.display == 'block'

    isAtleastVisible = false;
    $this.children('div').each(function (){
        if(this.style.display == 'block')
        {
           isAtleastVisible = true;
           return false;  //This will break each loop at the first visible div
        }
    });
person Adil    schedule 10.07.2012
comment
Это единственное неверное решение на этой странице. .is('visible') проверяет, является ли выбранный элемент элементом <visible>. Правильная реализация .is() опубликована в этом ответе (хотя он содержит устаревшую двойную оболочку jQuery: $($(...))). - person Rob W; 10.07.2012
comment
Спасибо @Rob W, я попытался улучшить свой ответ, пожалуйста, посмотрите. - person Adil; 11.07.2012
comment
Предложение не обязательно будет работать: style.display может быть "" (и все еще видимым), или родитель может быть скрыт. Предложение очень конкретное: например, оно не учитывает display:table-cell;. - person Rob W; 11.07.2012