|
Как правильно реализовать универсальные функции show и hide для DOM элемента? Есть несколько распространенных вариантов, с различными граблями, которые мы рассмотрим, и выберем оптимальный.
Самое интересное, что даже самые лучшие show/hide функции из хороших javascript-библиотек не универсальны.
Рассмотрим самую простую функцию toggle:
function toggle(el) {
el.style.display = (el.style.display == 'none') ? 'block' : 'none'
}
Недостатки такого подхода легко видеть. Например, ссылки <a href="...">имеют по умолчанию display: inline. А этот код поставит display = "block".
Для проверки - кликните на любом месте этого желтого div, при этом вызовется toggle ссылки. И затем - кликните еще раз для повторного toggle.
Как видно, прячет оно нормально, а вот показ - некорректный.
Аналогичная проблема будет с ячейками таблицы, у которых по стандарту display: table-cell.
Этой проблемы лишен следующий вариант:
function toggle(el) {
el.style.display = (el.style.display == 'none') ? '' : 'none'
}
Вместо block свойство display сбрасывается - при этом элемент получает display из CSS по умолчанию, то есть то, которое было изначально:
Как видите, все работает верно.
Но при этом возникает другая проблема. Свойство display у элемента может отличаться от унаследованного из CSS, например:
<a href="#" style="display:block">...</a>
Или особое значение display могло быть установлено из javascript.
При этом обнуление display сбросит это особое значение.
В DIV ссылка - <a href="#" onclick="return false" style="display:block">. При повторном показе функция toggle сбрасывает display в значение по умолчанию, поэтому элемент будет показан уже с display:inline.
Для лучшей применимости разобьем 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 такого не скажешь.
- Во-первых, вызов
show до hide сбросит display. Это поведение - некорректное, его нужно поправить.
- Во-вторых, что более важно, если элемент спрятан CSS-классом, то
show его не покажет.
Например, show не покажет ссылку из этого примера:
<style>
.hidden {display:none}
</style>
<a href="#" class="hidden">Ссылка</a>
Чтобы решить эти проблему, функция show должна знать, показывается ли элемент реально или нет. Это нам поможет сделать доступ к Computed Style - вычисленному стилю элемента, доступ к которому делается по-разному, в зависимости от браузера.
Следующая функция берет реальное значение 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'.
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
}
}
- Проверка, показывается ли элемент. Эта строчка нужна для безопасности. Если ее убрать, то (2) обнулит свойство display, а оно могло быть нестандартным. Этот глюк присутствует в ряде javascript-библиотек, например, в jQuery (проверено в 1.4 на момент написания).
Вы можете захотеть убрать эту строчку в целях оптимизации, чтобы сделать show быстрее.
Но при этом show станет некорректно работать при вызове без предшествующего hide на элементе с нестандартным display.
- Получить старое значение
display, если оно сохранено hide и поставить его элементу. Если старого значения нет - на безрыбье и рак рыба, просто обнуляем display.
- Показывается ли элемент? Элемент может не показываться, например, из-за того, что в его CSS классе прописано
display:none. Если так, то для показа элемента придется найти и применить подходящее значение display.
Где взять значение display для показа изначально скрытого элемента? Это совсем не обязательно block, т.к. элемент мог быть ссылкой, ячейкой таблицы, да и вообще - "правильный" display для показа элемента зависит от места, времени и настроения программиста.
В блоке (3.2) функция помещает элемент с таким же тэгом в конец <body> и получает его display, которое кеширует во вспомогательном объекте displayCache. Конечно, это всего лишь догадка, однако в простых случаях она работает.
Этот display и используется для назначения элементу.
- Попробовать получить правильное значение
display из кэша.
- В кэше нет - добавляем пустой тэг к
<body>, затем берем его display.
- Если и этот тэг имеет реальный
display:none - угадать не получилось. Возьмем block: что еще делать, элемент-то показать надо.
- Угаданное значение
display применяем к элементу и сохраняем для дальнейшего использования.
Итак, теперь на основе 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
#permalink1) не мешало бы добавить проверку на 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"})()Использование:
При загрузке странички скрипт проверит печеньку и скроет или покажет нужные элементы.
Автор: Nominus umbra, дата: 28 апреля, 2010 - 08:22
А зачем вообще трогать 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
#permalinkonclick="toggle(document.getElementById('content'))"или же если нужно скрыть этот элемент
Автор: Гость (не зарегистрирован), дата: 31 августа, 2010 - 01:19
#permalinkбыло бы интересно можно ли без цикла определить скрыт ли элемент типа
Учитывая что скрытым может быть и один из предков
Автор: 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>Автор: Гость (не зарегистрирован), дата: 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