Универсальный jQuery-скрипт для блоков с вкладками (табами)

к содержанию ↑

Вступление

Универсальный jQuery-скрипт для блоков с вкладками Мое знакомство с фреймворком jQuery произошло в 2008-м году, после того, как, однажды, на одном из англоязычных сайтов я нашел jQuery-скрипт, который позволяет создавать блоки с удобными вкладками. Этому скрипту я посвятил отдельный пост на данном блоге.

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

Почему я называю свой новый скрипт для jQuery-вкладок универсальным:

  • он позволяет создавать неограниченное количество вкладок в пределах одного блока, при этом нет необходимости нумеровать вкладки с помощью CSS-классов, как это было сделано в старом варианте;
  • можно создать сколько угодно таких блоков с вкладками опять же без необходимости нумеровать эти блоки через CSS-классы;
  • содержимое jQuery-скрипта, реализующего вкладки, остается неизменным (всего 0,3 килобайта), т.е. не разрастается в зависимость от количества блоков или вкладок, как это было в старом варианте, при этом размер нового варианта скрипта не больше размера скрипта для одного блока с вкладками из старого варианта.

Что ж, хватит сухих слов, переходим к сути.

к содержанию ↑

Код jQuery-скрипта для переключаемых блоков с вкладками

Не забывайте в первую очередь подключить сам фреймворк jQuery (если он еще не подключен на вашем сайте), например, так (вставляется между тегами <head> и </head>):

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Я предпочитаю «брать» его с Гугла, поскольку, во-первых, велика вероятность, что у посетителя, который зайдет на сайт, jQuery уже закэширован в браузере (значит страница загрузится быстрее), во-вторых, скорость серверов Гугла стабильна и быстра, в-третьих, Гугл отдает его в сжатом виде (gzip), и, например, для версии 1.4.2 размер составляет всего 24 килобайта по сравнению с несжатым файлом (70 Кб).

Вот такой у меня получился скрипт:

(function($) {
$(function() {

	$('ul.tabs__caption').each(function() {
		$(this).find('li').each(function(i) {
			$(this).click(function(){
				$(this).addClass('active').siblings().removeClass('active')
					.closest('div.tabs').find('div.tabs__content').removeClass('active').eq(i).addClass('active');
			});
		});
	});

})
})(jQuery)
к содержанию ↑

Добавлено 07.03.2010 (обновлено 09.04.2015)

В комментариях подсказали еще более сокращенный вариант этого скрипта (обратите внимание, что для него нужно использовать jQuery не ниже версии 1.7):

(function($) {
$(function() {

	$('ul.tabs__caption').on('click', 'li:not(.active)', function() {
		$(this)
			.addClass('active').siblings().removeClass('active')
			.closest('div.tabs').find('div.tabs__content').removeClass('active').eq($(this).index()).addClass('active');
	});

});
})(jQuery);

Для тех, кто еще не знает, как подключать этот скрипт — создать файл с расширением .js, вставить в него код скрипта и подключить по аналогии c jQuery (см. выше), естественно, заменив ссылку на адрес скрипта.

к содержанию ↑

HTML-код, который нужно использовать для скрипта

<div class="tabs">

	<ul class="tabs__caption">
		<li class="active">1-я вкладка</li>
		<li>2-я вкладка</li>
	</ul>

	<div class="tabs__content active">
		Содержимое первого блока
	</div>

	<div class="tabs__content">
		Содержимое второго блока
	</div>

</div><!-- .tabs-->

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

к содержанию ↑

Обязательные CSS-стили для вышеуказанного HTML-кода

.tabs__content {
	display: none; /* по умолчанию прячем все блоки */
}
.tabs__content.active {
	display: block; /* по умолчанию показываем нужный блок */
}

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

к содержанию ↑

Примеры

  • 1-й пример.
  • 2-й пример, в котором запоминается активная вкладка после перезагрузки страницы (с помощью cookie).
  • 3-й пример, в котором запоминается активная вкладка после перезагрузки страницы (с помощью localStorage, меньше кода по сравнению с cookie).
  • 4-й пример, в котором при переходе по ссылке с якорем, указывающим на номер таба, активируется соответствующий таб.
к содержанию ↑

Скачать

P.S. Мне в твиттере как-то сказали, что я «изобретаю колесо», что такой скрипт уже есть в jQuery UI. Ну и пусть, пусть я «изобрел колесо», главное, что я получаю большое удовольствие от данного процесса и одновременно повышаю свой опыт в jQuery. Разве это плохо? =)

* * *

Требуется разработка печатного каталога товаров или услуг? Создание каталога в студии «Верстаем.ru» это оптимальное решение!

Полезные комментарии (8)
Комментарии (918)
  1. 890
    Руслан

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

  2. 891
    Влад

    Огромнейшее спасибо!

  3. 892
    Никита

    А не подскажете как сделать чтобы табы переключались автоматически, по аналогии со слайдером? Спасибо!

  4. 894
    Алексей

    Здравствуйте. Подскажите пожалуйста как сделать так чтобы вкладки все были закрыты и открывались только при клике? например как на авито при просмотре объявления. Спасибо…

  5. 896
    Олег

    Здравствуйте, подскажите пожалуйста как ограничить высоту табов, чтобы на больших вкладках появлялась полоса прокрутки? Спасибо.

  6. 901
    Иван

    Здравствуйте! Хорошая статья и демки. Хотел спросить: можно ли работать с этими табами в php коде в том смысле что содержимое отдельного таба это страничка php типа подгрузка без перезагрузки или это совсем другая история? Я имею ввиду подключение содержимого таким образом сама суть вопроса в том будут ли они сразу все подключены или по мере переключения табов будут прогружатся? Не утяжелит ли этот способ (что я придумал) загрузку сайта — вот что более всего волнует. Благодарю заранее за помощь!

  7. 902
    Иван

    Извиняюсь забыл нажать php при вставке кода «…подключение содержимого таким образом

    <? include(stranichka.php'); ?>...

    «

  8. 904
    Иван

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

  9. 905
    Васиий

    Добрый день! Использую плагин с доступам к табам по ссылке с якорем. Стала задача реализовать закрытие вкладки по повторному клику на активную. Т.е. кликаем на активную вкладку, она закрывается. Не могу догадаться как осуществить( Подскажите пожалуйста, что можно сделать?

  10. 906
    Денис

    Спасибо большое за приведенный код, есть модификация его с обработкой hssh-tag и автивацией нужного таба по нему, но не как не могу завязать список табок где class=»active» будет вписан не в li списка а в его ссылке, т.е. чтобы было 1-я вкладка

    	<script>
    		$(function(){
    				var hash = location.hash;
    				if ( hash !== '') {
    					console.log('hash ' + hash);
    					var i = $('ul.tabs__caption li').has('a[href*=' + hash + ']').index();
    					console.log('i ' + i);
    					$('ul.tabs__caption li').has('a[href*=' + hash + ']').addClass('active').siblings().removeClass('active')
    					.closest('div.tabs').find('div.tabs__content').removeClass('active').eq(i).addClass('active');
    				}
    
    				$('ul.tabs__caption').on('click', 'li:not(.active) a', function() {
    			 $(this).parent()
    			 .addClass('active').siblings().removeClass('active')
    			 .closest('div.tabs').find('div.tabs__content').removeClass('active').eq($(this).parent().index()).addClass('active');
    			 });
    		});
    	</script>
    <div class="tabs">
    
     <ul class="tabs__caption">
     <li class="active"><a href="#tab1>1-я вкладка</a></li>
     <li><a href="#tab2">2-я вкладка</a></li>
     <li><a href="#tab3">3-я вкладка</a></li>
     </ul>
    
     <div class="tabs__content active">
     текст 1
     </div>
    
     <div class="tabs__content">
     текст 2
     </div>
    
     <div class="tabs__content">
     текст 3
     </div>
    
    </div>
  11. 907
    Виталий

    Подскажите, пожалуйста, какие изменения необходимо внести, чтобы работала вложенность табов, т .е чтобы можно было бы размещать табы в табах?

  12. 909
    Виталий
    $(document).on('click','li:not(.current)',function() {
    $(this).addClass('current').siblings().removeClass('current')
    .parents('div.section').eq(0).find('>div.box').hide().eq($(this).index()).show(); 
    })

    Не работало, сделал так как показано выше, заработало. Может, что не так?

  13. 910
    lunt

    я совсем дремучий юзер, осваивающий html-верстку методом тыка. применив рекомендации статьи у себя на сайте, ничего не смог сделать — выводится простой маркированный список. долго ковырял примеры автора и вдруг обнаружил, что автор почему-то в разделе «Обязательные CSS-стили» своей статьи забыл упомянуть про указания в CSS на .tabs и .tabs__caption .tabs__caption li. Как только я их прописал, у меня на сайте все заработало в виде переключаемых вкладок, а до этого просто выводило маркированный список безо всяких вкладок.

  14. 912
    madman

    Красава!!!
    Просто и очень красиво и качественно , пригодилось!

  15. 913
    dima

    Здрасти, а как сделать чтобы класс актив добавлялся к ссылки и переключались блоки? Спасибо

  16. 914
    nickcinee

    В вашем варианте

     $('ul.tabs__caption').on('click', 'li:not(.active)', function() {
     $(this)
     .addClass('active').siblings().removeClass('active')
     .closest('div.tabs').find('div.tabs__content').removeClass('active').eq($(this).index()).addClass('active');
     });
    

    preventDefault не срабатывает, если ссылка имеет активный класс.

    Вот как я решил проблему.

    	$('.tabs-menu li').on('click', function(e) {
    		$(this)
    			.addClass('current').siblings().removeClass('current')
    			.closest('.tabs').find('.tabs-content').removeClass('current').eq($(this).index()).addClass('current');
     			e.preventDefault();
    	});
    
  17. 915
    Виталий

    Добрый день, скажите, а как сделать так чтобы кнопки вкладок и контент вкладок находились в разных блоках? Например:

    <div class="tabs">
     <ul>
     <li>Вкладка 1</li>
     <li>Вкладка 2</li>
     </ul>
    </div>
    <div class="content_tabs">
     <div class="tab_content>...</div>
     <div class="tab_content>...</div>
    </div>
    
  18. 917
    Серый

    Dimox, ты молодец, отличные табы!
    До этого поставил jQuery UI с localStorage, использовал как меню с подменю на всех страницах сайтах.
    Заметил заметные тормоза при открытии некоторых страниц, даже скачав к себе все подключаемые файлы.
    Твой вариант в сто раз лучше, общий вес на порядок меньше, тормозов нет!

  1. 1

    Видимо, это косяк Файерфокса. А по поводу Вконтакте вот тут описывал решение.

  2. 2
    evgen

    путем нехитрых манипуляций делаем Ваш скрипт еще более универсальным

    (function($) {
    	$(function() {
    
    		$('ul.tabs').delegate('li:not(.current)', 'click', function() {
    			$(this).addClass('current').siblings().removeClass('current')
    				.parents('div.section').eq(0).find('>div.box').hide().eq($(this).index()).show();
    			return false;
    		})
    
    	})
    })(jQuery)
    
    <div class="section">
    
    	<ul class="tabs">
    		<li class="current">Первая вкладка</li>
    		<li>Вторая вкладка</li>
    		<li>Третья вкладка</li>
    		<li>Червертая вкладка</li>
    	</ul>
    
    	<div class="box visible">
    	<!-- Inner Tabs -->
    		<div class="section">
    			<ul class="tabs">
    				<li class="current">inner 1</li>
    				<li>inner 2</li>
    			</ul>
    			<div class="box visible">
    				<h2>inner 1</h2>
    				<p> text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text </p>
    			</div>
    			<div class="box">
    				<h2>inner 2</h2>
    				<p> text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text </p>
    			</div>
    		</div>
    	<!--End Inner Tabs-->
    		<h2>1</h2>
    		<p> Закон внешнего мира, как принято считать, реально рассматривается знак, отрицая очевидное. Гегельянство творит катарсис, хотя в официозе принято обратное. Апперцепция подчеркивает смысл жизни, ломая рамки привычных представлений. Представляется логичным, что адживика откровенна.</p>
    	</div>
    
    	<div class="box">
    		<h2>2</h2>
    		<p> Закон внешнего мира, как принято считать, реально рассматривается знак, отрицая очевидное. Гегельянство творит катарсис, хотя в официозе принято обратное. Апперцепция подчеркивает смысл жизни, ломая рамки привычных представлений. Представляется логичным, что адживика откровенна.</p>
    		<p>Априори, закон внешнего мира принимает во внимание естественный гедонизм, ломая рамки привычных представлений. Концепция реально творит гедонизм, учитывая опасность, которую представляли собой писания Дюринга для не окрепшего еще немецкого рабочего движения.</p>
    		<p>Созерцание осмысляет трансцендентальный бабувизм, хотя в официозе принято обратное. Бабувизм абстрактен. Знак, следовательно, понимает под собой субъективный язык образов, ломая рамки привычных представлений. Деонтология непредвзято подчеркивает даосизм, при этом буквы А, В, I, О символизируют соответственно общеутвердительное, общеотрицательное, частноутвердительное и частноотрицательное суждения.</p>
    	</div>
    
    	<div class="box">
    		<h2>3</h2>
    		<p>Структурализм, как следует из вышесказанного, заполняет из ряда вон выходящий дуализм, однако Зигварт считал критерием истинности необходимость и общезначимость, для которых нет никакой опоры в объективном мире. Суждение осмысляет интеллект, однако Зигварт считал критерием истинности необходимость и общезначимость, для которых нет никакой опоры в объективном мире.</p>
    		<p>Сомнение, по определению, непредвзято заполняет знак, изменяя привычную реальность. Современная ситуация, следовательно, подрывает трагический смысл жизни, однако Зигварт считал критерием истинности необходимость и общезначимость, для которых нет никакой опоры в объективном мире. Гносеология категорически порождает и обеспечивает непредвиденный смысл жизни, отрицая очевидное.</p>
    	</div>
    
    	<div class="box">
    		<h2>4</h2>
    		<p>Деонтология создает примитивный даосизм, открывая новые горизонты. Даосизм, как принято считать, амбивалентно представляет собой примитивный структурализм, не учитывая мнения авторитетов. Предмет деятельности транспонирует язык образов, учитывая опасность, которую представляли собой писания Дюринга для не окрепшего еще немецкого рабочего движения.</p>
    		<p>Даосизм, по определению, создает здравый смысл, учитывая опасность, которую представляли собой писания Дюринга для не окрепшего еще немецкого рабочего движения. Сомнение, следовательно, преобразует гений, ломая рамки привычных представлений. Сомнение трогательно наивно.</p>
    		<p>Жизнь откровенна. Предмет деятельности, как следует из вышесказанного, абстрактен. Искусство, как следует из вышесказанного, нетривиально. Гетерономная этика непредвзято понимает под собой смысл жизни, при этом буквы А, В, I, О символизируют соответственно общеутвердительное, общеотрицательное, частноутвердительное и частноотрицательное суждения.</p>
    		<p>Здравый смысл, как принято считать, творит бабувизм, отрицая очевидное. Суждение, следовательно, амбивалентно. Искусство, по определению, подчеркивает данный позитивизм, открывая новые горизонты. Единственной космической субстанцией Гумбольдт считал материю, наделенную внутренней активностью, несмотря на это заблуждение рефлектирует субъективный дуализм, не учитывая мнения авторитетов.</p>
    	</div>
    
    </div>
    

    теперь можно создавать вложенные табы

  3. 3
    ITech

    Ура! Решил у себя проблему с перепрыгиванием в начало страницы при переключении табов! Вот живой пример: http://injoit.com/what-we-do/

    Реализовал твой код, Dimox, на своем сайте с небольшими изменениями. Вся загвоздка в том, что все div.box необходимо обтянуть еще одним div и для него определить фиксированную высоту!

    Так как у меня контент в табах на сайте постоянно меняется, подключил механизм Math.max.apply для расчета максимально возможной высоты вложенного дива .box. Эту высоту и задавал внешнему диву..

    Для эффекта появления использовал вместо show() animate() с указанием четких параметров:

    (function($) {
    $(function() {
    
    	$('ul.tabs').delegate('li:not(.current)', 'click', function() {
    		$(this).addClass('current').siblings().removeClass('current')
    		.parents('div.section').find('div.box').hide().eq($(this).index()).animate({height: 'auto', width: 'auto', opacity: 'show'}, 'slow');
    	})
    
    /*--Определение максимальной высоты для блока-контейнера .box-wrap--*/
    	Array.max = function(array) {
     return Math.max.apply(Math,array);
     };
    	var heights = $('.box-wrap div.box').map(function() {
     return $(this).innerHeight();
     }).get();
    	$(".box-wrap").height(Array.max(heights));
    /*--------*/
    
    })
    })(jQuery)
    

    И собственно HTML:

    <div class="section vertical">
    <ul class="tabs">
    	<li class="current"><span>Mobile</span></li>
    	<li><span>Social and Web</span></li>
    	<li><span>Cloud</span></li>
    </ul>
    <div class="box-wrap">
    <div class="box visible">
    ---
    </div>
    <div class="box">
    ---
    </div>
    <div class="box">
    ---
    </div>
    </div>
    </div>
    

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

  4. 4

    По теме перепрыгивания вверх при переключении табов.
    В комментариях выше был модифицированный скрипт с определением высоты родительского блока, но если например в табы положить меню-аккордеон (оно там прекрасно работает), то при задании фона табам аккордеон будет вылазить за их пределы.
    И так как вся соль заключается в установке определённой высоты блоку-родителю, можно поступить проще:
    Мне лично помогла установка min-height для родительского блока табов.

    .section {
    	min-height: 1px;
    	}

    проверял в ie7> и нормальных браузерах — вроде бы работает.

  5. 5
    maqsim

    Вместо:
    $(‘ul.tabs’).delegate(‘li:not(.current)’, ‘click’, function() {

    Поставьте:
    $(‘ul.tabs li:not(.current)’).live(‘click’, function() {

    Проблема первого элемента в том, что некорректно связываются события через метод delegate, когда разметка с вкладками подгружена через ajax. Второй вариант работает отлично + мы делаем 1 раз выборку, а не 2

  6. 6
    Metaller

    Проблема прыгающей страницы при переключении табов в том, что в момент переключения сначала прячется содержимое всех табов, а потом показывается содержимое активного таба. В момент, когда содержимое всех табов спрятано — контента на странице становится меньше и браузер прокручивает страницу вверх. Когда новый таб появляется — скрол остается на той же странице.

    Решение — не прятать все табы, а только ненужные:

    .parents('div.section').find('div.box').eq($(this).index()).fadeIn(150).siblings().hide();
    
  7. 7

    Если кому интересно — ещё одно простое решение для перехода к нужному табу по клику на стороннем элементе.

    $('[data-tab-target]').click(function(e) {
    		$("html, body").animate({scrollTop:$('#'+$(this).data('tab-target')).offset().top-50}, 500); //прокручиваем страницу чуть выше положения цели
    		$('#'+$(this).data('tab-target')).trigger('click');
    		return false;
    	});
    <span data-tab-target="tab-2">перейти к табу #tab-2</span>

    у

    <div class="section">
     <ul class="tabs">
     <li class="current">1-я вкладка</li>
     <li id="tab-2">2-я вкладка</li>
     </ul>
     <div class="box visible">
     Содержимое первого блока
     </div>
     <div class="box">
     Содержимое второго блока
     </div>
    </div><!-- .section -->
  8. 8

    Вот код, который это делает (доработан 4-й пример):

    (function($) {
    $(function() {
    
    	$('ul.tabs__caption').on('click', 'li:not(.active)', function() {
    		$(this)
    			.addClass('active').siblings().removeClass('active')
    			.closest('div.tabs').find('div.tabs__content').removeClass('active').eq($(this).index()).addClass('active');
    	});
    
    	var tabIndex = window.location.hash.replace('#tab','')-1;
    	if (tabIndex != -1) {
    		$('ul.tabs__caption li').eq(tabIndex).click();
    		$('html, body').animate({scrollTop: $('div.tabs__content').eq(tabIndex).offset().top - 50}, 700);
    	}
    
    	$('a[href*=#tab]').click(function() {
    		var tabIndex = $(this).attr('href').replace(/(.*)#tab/, '')-1;
    		$('ul.tabs__caption li').eq(tabIndex).click();
    		$('html, body').animate({scrollTop: $('div.tabs__content').eq(tabIndex).offset().top - 50}, 700);
    	});
    
    });
    })(jQuery);

    Число 50 (в двух местах) — это отступ между блоком и верхней границей окна браузера после прокрутки к блоку. 700 — это скорость прокрутки, в данном случае 0,7 секунды.