Правильные show/hide/toggle

Форум

Учебник Node.JS скринкаст Стандарт языка

Справочник

Discord чат

 
Статьи Тест знаний Аналоги функций PHP

Курсы javascript

 

Как правильно реализовать универсальные функции show и hide для DOM элемента? Есть несколько распространенных вариантов, с различными граблями, которые мы рассмотрим, и выберем оптимальный.

Самое интересное, что даже самые лучшие show/hide функции из хороших javascript-библиотек не универсальны.

Функция toggle: стандартный подход 1

Рассмотрим самую простую функцию toggle:

function toggle(el) {
  el.style.display = (el.style.display == 'none') ? 'block' : 'none'
}

Недостатки такого подхода легко видеть. Например, ссылки <a href="...">имеют по умолчанию display: inline. А этот код поставит display = "block".

Для проверки - кликните на любом месте этого желтого div, при этом вызовется toggle ссылки. И затем - кликните еще раз для повторного toggle.

click ссылка me

Как видно, прячет оно нормально, а вот показ - некорректный.

Аналогичная проблема будет с ячейками таблицы, у которых по стандарту display: table-cell.

Функция toggle: стандартный подход 2

Этой проблемы лишен следующий вариант:

function toggle(el) {
  el.style.display = (el.style.display == 'none') ? '' : 'none'
}

Вместо block свойство display сбрасывается - при этом элемент получает display из CSS по умолчанию, то есть то, которое было изначально:

click ссылка me

Как видите, все работает верно.

Проблема подхода

Но при этом возникает другая проблема. Свойство display у элемента может отличаться от унаследованного из CSS, например:

<a href="#" style="display:block">...</a>

Или особое значение display могло быть установлено из javascript.

При этом обнуление display сбросит это особое значение.

В DIV ссылка - <a href="#" onclick="return false" style="display:block">. При повторном показе функция toggle сбрасывает display в значение по умолчанию, поэтому элемент будет показан уже с display:inline.

click ссылка me

Функции show/hide. Попытка номер 3

Для лучшей применимости разобьем toggle на две части: show и hide и отладим их по отдельности.

Чтобы обойти описанную выше проблему с перезаписыванием display - при скрытии элемента будем записывать старое значение display в атрибут el.displayOld, а при показе - восстанавливать.

function hide(el) {
	if (!el.hasAttribute('displayOld')) {
		el.setAttribute("displayOld", el.style.display)
	}

	el.style.display = "none"
}

function show(el) {
	var old = el.getAttribute("displayOld");
	el.style.display = old || "";
}

Теперь show/hide для ссылки со своим display будет работать корректно.

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

Функция hide работает отлично. О show такого не скажешь.

  1. Во-первых, вызов show до hide сбросит display. Это поведение - некорректное, его нужно поправить.
  2. Во-вторых, что более важно, если элемент спрятан CSS-классом, то show его не покажет.

    Например, show не покажет ссылку из этого примера:

    <style>
    .hidden {display:none}
    </style>
    <a href="#" class="hidden">Ссылка</a>
    

Чтобы решить эти проблему, функция show должна знать, показывается ли элемент реально или нет. Это нам поможет сделать доступ к Computed Style - вычисленному стилю элемента, доступ к которому делается по-разному, в зависимости от браузера.

Реальный display элемента

Следующая функция берет реальное значение display из Computed Style, то есть такое. которое получается в результате применения всех CSS-классов и свойств.

function getRealDisplay(elem) {
	if (elem.currentStyle) {
		return elem.currentStyle.display
	} else if (window.getComputedStyle) {
		var computedStyle = window.getComputedStyle(elem, null)

		return computedStyle.getPropertyValue('display')
	} 
}

Если элемент не показывается из-за CSS-класса, то getRealDisplay вернет для него 'none'.

Функция show с учетом CSS-класса

displayCache = {} 

function show(el) {
	if (getRealDisplay(el) != 'none') return // (1)

	el.style.display = el.getAttribute("displayOld") || "" // (2)

	if ( getRealDisplay(el) === "none" ) { // (3)
		var nodeName = el.nodeName, body = document.body, display

		if ( displayCache[nodeName] ) { // (3.1)
			display = displayCache[nodeName]
		} else { // (3.2)
			var testElem = document.createElement(nodeName)
			body.appendChild(testElem)
			display = getRealDisplay(testElem)

			if (display === "none" ) { // (3.2.1)
				display = "block"
			}

			body.removeChild(testElem)
			displayCache[nodeName] = display
		}

		el.setAttribute('displayOld', display)  // (3.3)
		el.style.display = display
	}
}
  1. Проверка, показывается ли элемент. Эта строчка нужна для безопасности. Если ее убрать, то (2) обнулит свойство display, а оно могло быть нестандартным. Этот глюк присутствует в ряде javascript-библиотек, например, в jQuery (проверено в 1.4 на момент написания).

    Вы можете захотеть убрать эту строчку в целях оптимизации, чтобы сделать show быстрее.

    Но при этом show станет некорректно работать при вызове без предшествующего hide на элементе с нестандартным display.

  2. Получить старое значение display, если оно сохранено hide и поставить его элементу. Если старого значения нет - на безрыбье и рак рыба, просто обнуляем display.
  3. Показывается ли элемент? Элемент может не показываться, например, из-за того, что в его CSS классе прописано display:none. Если так, то для показа элемента придется найти и применить подходящее значение display.

    Где взять значение display для показа изначально скрытого элемента? Это совсем не обязательно block, т.к. элемент мог быть ссылкой, ячейкой таблицы, да и вообще - "правильный" display для показа элемента зависит от места, времени и настроения программиста.

    В блоке (3.2) функция помещает элемент с таким же тэгом в конец <body> и получает его display, которое кеширует во вспомогательном объекте displayCache. Конечно, это всего лишь догадка, однако в простых случаях она работает.

    Этот display и используется для назначения элементу.

    1. Попробовать получить правильное значение display из кэша.
    2. В кэше нет - добавляем пустой тэг к <body>, затем берем его display.
      1. Если и этот тэг имеет реальный display:none - угадать не получилось. Возьмем block: что еще делать, элемент-то показать надо.
    3. Угаданное значение display применяем к элементу и сохраняем для дальнейшего использования.

Правильная функция toggle.

Итак, теперь на основе show и hide можно сделать функцию toggle, которая видимый элемент скрывает, а невидимый - наоборот, показывает. Иначе говоря, переключает состояние элемента.

Функция toggle сама по себе очень проста:

function toggle(el) {
	isHidden(el) ? show(el) : hide(el)
}

Для ее работы необходима вспомогательная функция isHidden, которая определяет, виден ли элемент. Само собой, имеется в виду реальная видимость, а не свойство display элемента.

Быстрая проверка видимости

Используем для этого трюк с offsetWidth/offsetHeight:

function isHidden(el) {
	var width = el.offsetWidth, height = el.offsetHeight,
		tr = el.nodeName.toLowerCase() === "tr"

	return width === 0 && height === 0 && !tr ?
		true : width > 0 && height > 0 && !tr ? false : getRealDisplay(el)
}

Эта реализация пытается получить ответ, по возможности используя проверку offsetWidth/offsetHeight, т.к. это быстрее, чем getRealDisplay.

Резюме

Итак, вот итоговый код toggle.js.

function getRealDisplay(elem) {
	if (elem.currentStyle) {
		return elem.currentStyle.display
	} else if (window.getComputedStyle) {
		var computedStyle = window.getComputedStyle(elem, null )

		return computedStyle.getPropertyValue('display')
	}
}

function hide(el) {
	if (!el.getAttribute('displayOld')) {
		el.setAttribute("displayOld", el.style.display)
	}

	el.style.display = "none"
}

displayCache = {}

function isHidden(el) {
	var width = el.offsetWidth, height = el.offsetHeight,
		tr = el.nodeName.toLowerCase() === "tr"

	return width === 0 && height === 0 && !tr ?
		true : width > 0 && height > 0 && !tr ? false :	getRealDisplay(el)
}

function toggle(el) {
	isHidden(el) ? show(el) : hide(el)
}


function show(el) {

	if (getRealDisplay(el) != 'none') return

	var old = el.getAttribute("displayOld");
	el.style.display = old || "";

	if ( getRealDisplay(el) === "none" ) {
		var nodeName = el.nodeName, body = document.body, display

		if ( displayCache[nodeName] ) {
			display = displayCache[nodeName]
		} else {
			var testElem = document.createElement(nodeName)
			body.appendChild(testElem)
			display = getRealDisplay(testElem)

			if (display === "none" ) {
				display = "block"
			}

			body.removeChild(testElem)
			displayCache[nodeName] = display
		}

		el.setAttribute('displayOld', display)
		el.style.display = display
	}
}

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

Открыть пример в новом окне

Эта универсальная функция toggle широко используется в различных библиотеках, в частности, в jQuery.

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

Пусть ваш toggle всегда работает так, как задумано!


Автор: 4vanger, дата: 11 января, 2010 - 22:03

#permalink

1) не мешало бы добавить проверку на not null для el и на соответствующие nodeType
2) Мне кажется, что для хранения старого значения лучше использовать JS поле - в простейшем случае el.displayOld, а лучше отдельный глобальный хэш.
Модифицировать коллекцию аттрибутов - как-то нехорошо.


Автор: Kolyaj, дата: 12 января, 2010 - 12:39

#permalink

Правильный hide

function hide(el) {
    el.className += ' element_hide';
}

А в CSS-классе element_hide уже выставляются нужные свойства, будь то display: none, left: -9999px, visibility: hidden или что-то ещё. Т.е. функции show/hide вообще не нужны, нужны addClass/removeClass.


Автор: vanicon (не зарегистрирован), дата: 17 января, 2010 - 21:03

#permalink

при отображении вкладок tabs использовал такой вариант

$(document).ready(function(){

$('#tabsTitle li').mouseover(function(){ $('#content4 > div').hide();

$('#' + $(this).attr('class')).toggle(); });

});

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


Автор: cooli0, дата: 29 января, 2010 - 18:18

#permalink

Абсолютно согласен с Kolyaj. На самом деле, никогда нельзя обращаться из JS к свойству style, если на то нет весомых причин, которой может являться, к примеру, анимация. Во-первых изменения стиля из джаваскрипта - медленная операция, а во-вторых - не кашерно. Я уверен, что если посчитать(в байтах) ваш код и реализацию css classes+js, то в итоге второй вариант выйграет даже здесь.


Автор: Гость (не зарегистрирован), дата: 6 марта, 2010 - 16:48

#permalink

если элемент изначально скрыт, ни show(), ни toggle() его не покажет


Автор: YISHIMITSY, дата: 19 марта, 2010 - 17:54

#permalink

По моему, в hide не помешает тоже добавить проверку на isHidden(el), иначе если hide будет вызван 2 раза подряд, show поставит ему block вместо изначального значения...


Автор: Aleko (не зарегистрирован), дата: 28 апреля, 2010 - 07:55

#permalink

Написал аналог toggle но с запоминание на печеньках. Защиты от дураков (как в статье выше) нет, поэтому желательно заранее знать, к каким элементам мы его будем применять.

Код:

(toggle=function(c){var a=(document.cookie.match(/hiddenData=([\w\|]+)/)||[,','])[1].split('|'),b=-1,d={};try{if(c)a.push((document.getElementById(c).style.display=='none')*1+c)}catch(e){return}while(a[++b]){try{document.getElementById(a[b].substr(1)).style.display=a[b].charAt(0)>0?'':'none';d[a[b].substr(1)]=a[b]}catch(e){continue}}a=[];for(key in d){a.push(d[key])}document.cookie="hiddenData="+a.join("|")+"; expires=Mon, 1 Jan 3000 00:00:00 UTC"})()

Использование:

toggle(id элемента)

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


Автор: Nominus umbra, дата: 28 апреля, 2010 - 08:22

#permalink

А зачем вообще трогать display?
Это семантически разве верно?
display отвечает за модель отображения контента.
За видимость отвечает visibility.
Разве не проще просто его включать-выключать?


Автор: Гость (не зарегистрирован), дата: 7 мая, 2010 - 22:27

#permalink

Взял скрипт toggle.js с этой страницы.
Код HTML:

Интересующие меня элементы:

таблица нормально скрыта, должна показываться как:

В IE6 и Opera все работает идеально. Firefox ругается:
el.nodeName is undefined на строку tr = el.nodeName.toLowerCase() === "tr"
(width и height в пред. строке тоже не работают)

Как исправить?


Автор: Гость (не зарегистрирован), дата: 13 июня, 2010 - 21:16

#permalink

Если пытаться применить функцию toggle отсюда:
http://javascript.ru/files/toggle/toggle.js
к изначально скрытой таблице, в Опере все работает нормально, а FF 3.6 присваивает переменной el не id-шник интересующей меня таблицы, а объект Window. Как изменить скрипт, чтобы он работал во всех браузерах, не привязываясь к конкретной разметке?
З.Ы. В IE6 тоже не работает, но работоспособность в IE не требуется :-)


Автор: Гость (не зарегистрирован), дата: 16 июня, 2010 - 11:32

#permalink

Пытаюсь применить данный скрипт
http://javascript.ru/files/toggle/toggle.js
для показа/скрытия таблицы. В Opera работает идеально, а FF 3.6 и IE при вызове функции isHidden() при изначально скрытой таблице выставляют объектом el не интересующую меня таблицу, а объект Window.
Есть ли способ сделать скрипт универсальным для Opera и FF не приписывая отдельно больших кусков под FF ?


Автор: kibal4iw, дата: 25 июня, 2010 - 16:02

#permalink
onclick="toggle(document.getElementById('content'))"

или же если нужно скрыть этот элемент

onclick="toggle(this)"

Автор: Гость (не зарегистрирован), дата: 31 августа, 2010 - 01:19

#permalink

было бы интересно можно ли без цикла определить скрыт ли элемент типа

<div style="float:left"></div>

Учитывая что скрытым может быть и один из предков


Автор: Maxman, дата: 11 января, 2011 - 10:01

#permalink

В процессе написания своего фреймворка, долго думал над этим. На ум пришёл такой вариант:

function hide(element) {
    // создадим элементу свойство, в которое запишем текущий дисплей
    element.realDisplay = element.currentStyle ? element.currentStyle["display"] 
    : getComputedStyle(element, null)["display"];
    // и скроем
    element.style.display = "none";
}
function show(element) {
    // берём записанный в realDisplay дисплей и устанавливаем
    // либо, ставим по умолчанию для данного элемента (сбрасываем)
    element.style.display = element.realDisplay || "";
}
function toggle(element) {
    // если элемент отображается
    if(!(element.offsetHeight == 0 && element.offsetWidth == 0)) {
        // скроем
        hide(element);
    } else {
        // если скрыт, покажем
        show(element);
    }
}

toggle(document.getElementById("test"));

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


Автор: Гость (не зарегистрирован), дата: 22 февраля, 2011 - 10:15

#permalink

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


Автор: Гость (не зарегистрирован), дата: 23 января, 2012 - 09:35

#permalink

Я не нахожу идею написания универсальной функции для этой цели здравой. Для каждого конкретного случая универсальная функция окажется избыточной.
Разумнее писать функцию под свой конкретный случай.


Автор: Гость (не зарегистрирован), дата: 17 февраля, 2012 - 20:30

#permalink

Доброго времени суток! может вы сможете мне помочь? мне надо чтобы вот в этом тексте

$(function () {

function makeTabs(contId) {
var tabContainers = $('#'+contId+' div.tabs > div');
tabContainers.hide().filter(':ID').show();

$('#'+contId+' div.tabs ul.tabNavigation a').click(function () {
tabContainers.hide();
tabContainers.filter(this.hash).show();
$('#'+contId+' div.tabs ul.tabNavigation a').removeClass('selected');
$(this).addClass('selected');
return false;
}).filter(':ID').click();
}

});

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


Автор: frant32, дата: 25 мая, 2012 - 20:07

#permalink

почему-то toggle не работет...
точнее не работает как нужно , срабатует просто как событие onclick ,но по второму клику ничего не происходит..

<style type="text/css">
div#mh {width:200px; height:30px; border-top-left-radius:12px; border-top-left-radius:12px; border-top-right-radius:12px; background:#00CCFF; font-size:18px; font-family: Impact; text-align:center; color: #FFFFFF; padding-top:3px;}
span#tri { border-color: red #00CCFF #00CCFF #00CCFF; border-style: solid;border-width:8px; position:absolute; left:150px; top:19px;}
</style>
<script type="text/javascript">

function toggle(eL) {
var eL=document.getElementById('tri');
    eL1='red #00CCFF #00CCFF #00CCFF';
	eL2='#00CCFF #00CCFF #00CCFF red';
	
  eL.style.borderColor = (eL.style.borderColor == eL1) ? eL2 :eL1  ;
}
</script>
<div id='mh'> Menu <span id='tri'  onclick="toggle()"></span> </div>
<div id='mb'>
  <div id='mtxt'></div>
</div>

Автор: Гость (не зарегистрирован), дата: 13 июня, 2012 - 16:26

#permalink

Ловите:

<script type="text/javascript">
	  
			
function slideToggleDiv(s){ 
	$('#filter').slideToggle(s); 			
}

</script>
...
<body onload="slideToggleDiv(0)">
...
<div id="filter">
	і
</div> 
<a href="javascript: slideToggleDiv(500);">Подробно</a>

Автор: Mygar (не зарегистрирован), дата: 9 сентября, 2012 - 18:01

#permalink

Здравствуйте.

Помогите пожалуйста, я в JS не силен. Пользуюсь вот таким вариантом:

function toggle(el)
{
myEl = document.getElementById(el);
myEl.style.display = (myEl.style.display == 'block') ? 'none' : 'block';
}

При этом в теле находятся вот такие ссылки:

ссылка 1
ссылка 2
ссылка 3
ссылка 4

Показать 1

Показать 2

Показать 3

Показать 4

Как сделать так, чтобы при смене у div id="one" display:none на display:block другим элементам обязательно задавался параметр display:none и при нажатии на "ссылка 2" открытое "Показать 1" исчезало, а "Показать 2" отображалось?


Автор: Mygar (не зарегистрирован), дата: 9 сентября, 2012 - 18:02

#permalink

Почему то код не написался...

Здравствуйте.

Помогите пожалуйста, я в JS не силен. Пользуюсь вот таким вариантом:

<script type="text/javascript">
 function toggle(el)
 {
 myEl = document.getElementById(el);
 myEl.style.display = (myEl.style.display == 'block') ? 'none' : 'block';
 }
 </script>

При этом в теле находятся вот такие ссылки:

<a href="#" onclick="toggle('one')>ссылка 1</a>
<a href="#" onclick="toggle('two')>ссылка 2</a>
<a href="#" onclick="toggle('three')>ссылка 3</a>
<a href="#" onclick="toggle('four')>ссылка 4</a>

<div id="one" style="display:none">Показать 1</div>
<div id="two" style="display:none">Показать 2</div>
<div id="three" style="display:none">Показать 3</div>
<div id="four" style="display:none">Показать 4</div>

Как сделать так, чтобы при смене у div id="one" display:none на display:block другим элементам обязательно задавался параметр display:none и при нажатии на "ссылка 2" открытое "Показать 1" исчезало, а "Показать 2" отображалось?


Автор: Гость (не зарегистрирован), дата: 17 октября, 2012 - 03:47

#permalink

Старые версии IE не поддерживают setAttribute, только setAttributeNode.


Автор: Kerny (не зарегистрирован), дата: 20 марта, 2013 - 15:14

#permalink

Не пойму, почему просто не юзать visibility?


Автор: Гость (не зарегистрирован), дата: 20 июля, 2014 - 17:27

#permalink

Вот моя версия простейшего анимированного тоггла:

function toggle(elem, animdelay, displaytype)
{
var stime = animdelay * 1000
elem.style.transition = animdelay+"s";
if(elem.style.display != 'none')
{
elem.style.opacity = '0';
setTimeout(function()
{
elem.style.display = 'none';
}, stime);
}
else
{
elem.style.display = displaytype;
setTimeout(function()
{
elem.style.opacity = '1';
}, 1);
}
}

Применение:

<div id='txt'>Hello World!</div>
<button onclick='toggle(getElementById("txt"), 1, "block");'>Toggle</button>

Автор: system (не зарегистрирован), дата: 18 октября, 2014 - 17:24

#permalink

Здравствуйте.
Не подскажите, как скрыть блок div на определенной странице, то есть при переходе по определенному адресу, например на главную страницу?
Спасибо.


Автор: kezzyhko (не зарегистрирован), дата: 3 августа, 2016 - 07:28

#permalink

А почему работаем с css-свойством display? Есть же универсальный атрибут hidden, с ним все вообще в одну строчку

function toggle(el) {
    el.hidden=!el.hidden;
}

Автор: Гость (не зарегистрирован), дата: 23 июня, 2017 - 13:41

#permalink

В функции isHidden допущена ошибка:

финальная проверка должна выглядеть как:

getRealDisplay(el) === 'none'


Автор: peter johnee1 (не зарегистрирован), дата: 11 августа, 2020 - 10:22

#permalink

Где взять значение display для показа изначально скрытого элемента? cookie clicker


 
Текущий раздел
Поиск по сайту
Содержание

Дерево всех статей

Последние темы на форуме
Forum