Правильные show/hide/toggle
Как правильно реализовать универсальные функции 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 всегда работает так, как задумано!
|
1) не мешало бы добавить проверку на not null для el и на соответствующие nodeType
2) Мне кажется, что для хранения старого значения лучше использовать JS поле - в простейшем случае el.displayOld, а лучше отдельный глобальный хэш.
Модифицировать коллекцию аттрибутов - как-то нехорошо.
Правильный hide
А в CSS-классе element_hide уже выставляются нужные свойства, будь то display: none, left: -9999px, visibility: hidden или что-то ещё. Т.е. функции show/hide вообще не нужны, нужны addClass/removeClass.
Не плохо бы проверочку сделать было бы на существование этого класса element_hide у этого элемента.
Хорошее дополнение, как правило класс действительно лучше, да.
Полностью согласен.
Концепт такой:
ЦСС, соответственно, содержит правила, как показывать (инлайн, блок,...) и как прятать (дисплей, визибилити, позишн, опейсити,...) конкретно этот елемент. Как бонус - мы разделяем поведение (js) и вид (css).
ПС: код не проверял, пишу по старой памяти. Если есть ошибки - укажите.
ППС: Лучше навешать эти функции как методы на ДОМ-обьект, тогда можно обойтись без elm (заменить на this).
при отображении вкладок tabs использовал такой вариант
$(document).ready(function(){
$('#tabsTitle li').mouseover(function(){ $('#content4 > div').hide();
$('#' + $(this).attr('class')).toggle(); });
});
Однако как прописать , что бы при переходе на страницу открывалась третья по счету вкладка или любая другая, как это задать подскажи код если время есть
Записывать id или номер текущей вкладки при каждом изменении в location.hash, а при первой загрузке страницы читать location.hash и переключать программно.
Даже более-менее семантично.
кста по такому принципу вконтакте списки с выборкой работают
Абсолютно согласен с Kolyaj. На самом деле, никогда нельзя обращаться из JS к свойству style, если на то нет весомых причин, которой может являться, к примеру, анимация. Во-первых изменения стиля из джаваскрипта - медленная операция, а во-вторых - не кашерно. Я уверен, что если посчитать(в байтах) ваш код и реализацию css classes+js, то в итоге второй вариант выйграет даже здесь.
когда ты пишешь javascript-код для своего сайта, то удобней, может, с классами (тоже кстати, классы использую).
А вот когда ты делаешь универсальный код (библиотеку, например), то тогда приходится так.
если элемент изначально скрыт, ни show(), ни toggle() его не покажет
По моему, в hide не помешает тоже добавить проверку на isHidden(el), иначе если hide будет вызван 2 раза подряд, show поставит ему block вместо изначального значения...
Написал аналог toggle но с запоминание на печеньках. Защиты от дураков (как в статье выше) нет, поэтому желательно заранее знать, к каким элементам мы его будем применять.
Код:
Использование:
При загрузке странички скрипт проверит печеньку и скроет или покажет нужные элементы.
А зачем вообще трогать display?
Это семантически разве верно?
display отвечает за модель отображения контента.
За видимость отвечает visibility.
Разве не проще просто его включать-выключать?
Вот только visibility сохраняет пространство, занимаемое элементом.
Взял скрипт toggle.js с этой страницы.
Код HTML:
Интересующие меня элементы:
таблица нормально скрыта, должна показываться как:
В IE6 и Opera все работает идеально. Firefox ругается:
el.nodeName is undefined на строку tr = el.nodeName.toLowerCase() === "tr"
(width и height в пред. строке тоже не работают)
Как исправить?
Sorry, еще раз код
Если пытаться применить функцию toggle отсюда:
http://javascript.ru/files/toggle/toggle.js
к изначально скрытой таблице, в Опере все работает нормально, а FF 3.6 присваивает переменной el не id-шник интересующей меня таблицы, а объект Window. Как изменить скрипт, чтобы он работал во всех браузерах, не привязываясь к конкретной разметке?
З.Ы. В IE6 тоже не работает, но работоспособность в IE не требуется :-)
Пытаюсь применить данный скрипт
http://javascript.ru/files/toggle/toggle.js
для показа/скрытия таблицы. В Opera работает идеально, а FF 3.6 и IE при вызове функции isHidden() при изначально скрытой таблице выставляют объектом el не интересующую меня таблицу, а объект Window.
Есть ли способ сделать скрипт универсальным для Opera и FF не приписывая отдельно больших кусков под FF ?
или же если нужно скрыть этот элемент
toggle(this) прекрасно скроет сам чекбокс, чего мне не надо.
Мне нужно по чекбоксу показывать/скрывать таблицу с id="content"
Таблица изначально скрыта (display: none) поэтому FF похоже вообще не создает никаких элементов с таким id и toggle(document.getElementById('content')) тоже не отрабатывает.
таблицу изначально лучше не скрывать, придумайте альтернативу, багов не оберетесь..
было бы интересно можно ли без цикла определить скрыт ли элемент типа
Учитывая что скрытым может быть и один из предков
Да, можно, по offsetWidth == offsetHeight == 0. Работает на всех элементах, кроме TR, на них в нек случаях багает.
В процессе написания своего фреймворка, долго думал над этим. На ум пришёл такой вариант:
Код намного короче, работает практически универсально.
Спасибо, помогло =)
Рад))
однако если изначально (поставил в CSS) элемент имеет свойство display:none; то не работает.
в функции show "" не поставит (ff 7.0.1) свойство в изначальное такое как предполагается у элемента (если оно none) в w3c, например:
div - блочный элемент т.е. имеет по w3c значение display:block;
span - display:inline;
так вот как узнать у элемента какое у него по умолчанию w3c свойство display ?
document.defaultView им можно получить? если да, то как? smile
Ребят, ведь здесь не учитывается, что у скрываемывого элемента. например дива, есть есть еще вложенные элементы, которые автоматически унаследуют измененные свойства, а после его отображения унаследуют опять же, но это уже могут быть не совсем те свойства ???
Например внутри дива есть ссылки, таблицы и т.п.
Так display не наследуется.
Я не нахожу идею написания универсальной функции для этой цели здравой. Для каждого конкретного случая универсальная функция окажется избыточной.
Разумнее писать функцию под свой конкретный случай.
Доброго времени суток! может вы сможете мне помочь? мне надо чтобы вот в этом тексте
$(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 было не названием (сейчас ид (в ксс) материала у меня работает как просто название) а конкретным, уникальным номером задающим, свое, уникальное название-номер для таба.
Если не сложно конечно...
почему-то toggle не работет...
точнее не работает как нужно , срабатует просто как событие onclick ,но по второму клику ничего не происходит..
Ловите:
Здравствуйте.
Помогите пожалуйста, я в JS не силен. Пользуюсь вот таким вариантом:
function toggle(el)
{
myEl = document.getElementById(el);
myEl.style.display = (myEl.style.display == 'block') ? 'none' : 'block';
}
При этом в теле находятся вот такие ссылки:
ссылка 1
ссылка 2
ссылка 3
ссылка 4
Как сделать так, чтобы при смене у div id="one" display:none на display:block другим элементам обязательно задавался параметр display:none и при нажатии на "ссылка 2" открытое "Показать 1" исчезало, а "Показать 2" отображалось?
Почему то код не написался...
Здравствуйте.
Помогите пожалуйста, я в JS не силен. Пользуюсь вот таким вариантом:
При этом в теле находятся вот такие ссылки:
Как сделать так, чтобы при смене у div id="one" display:none на display:block другим элементам обязательно задавался параметр display:none и при нажатии на "ссылка 2" открытое "Показать 1" исчезало, а "Показать 2" отображалось?
Увас небольшая ошибка в html-коде, а именно не закрыт атрибут onclick.
Старые версии IE не поддерживают setAttribute, только setAttributeNode.
Не пойму, почему просто не юзать visibility?
visibility не освобождает место, выделенное под элемент
Вот моя версия простейшего анимированного тоггла:
Применение:
Здравствуйте.
Не подскажите, как скрыть блок div на определенной странице, то есть при переходе по определенному адресу, например на главную страницу?
Спасибо.
А почему работаем с css-свойством display? Есть же универсальный атрибут hidden, с ним все вообще в одну строчку
В функции
isHidden
допущена ошибка:финальная проверка должна выглядеть как:
getRealDisplay(el) === 'none'
Отличный инструмент, однозначно стоит использовать. Читать статьи websash
Мне полезно читать это. Мне нужно узнать больше на эту тему. Спасибо за письмо этот удивительный пост. free fire garena
Отличный инструмент, однозначно стоит использовать. порно бесплатно
Старые версии IE не поддерживают setAttribute, только setAttributeNode. порно ролики
The approach to the problem you share is interesting and interesting. I would love to read your article. It really helped me a lot. Thanks for sharing.
temple run
I just couldn't leave your website before telling you that I truly enjoyed the top quality info you present to your visitors? Will be back again frequently to check up on new posts.
go here
This is such a great resource that you are providing and you give it away for free. I love seeing blog that understand the value of providing a quality resource for free.
cryptocurrency news
Awesome and interesting article. Great things you've always shared with us. Thanks. Just continue composing this kind of post.
learn here
Great post, you have pointed out some fantastic points , I likewise think this s a very wonderful website.
homicide cleanup
I really thank you for the valuable info on this great subject and look forward to more great posts. Thanks a lot for enjoying this beauty article with me. I am appreciating it very much! Looking forward to another great article. Good luck to the author! All the best!
Body Line
I found this is an informative and interesting post so i think so it is very useful and knowledgeable. I would like to thank you for the efforts you have made in writing this article.
Truly Lovely Kitchen
Only aspire to mention ones content can be as incredible. This clarity with your post is superb and that i may think you’re a guru for this issue. High-quality along with your concur permit me to to seize your current give to keep modified by using approaching blog post. Thanks a lot hundreds of along with you should go on the pleasurable get the job done.
結婚指輪
Your article has piqued a lot of positive interest. I can see why since you have done such a good job of making it interesting.
結婚指輪 福岡
You have done a great job on this article. It’s very readable and highly intelligent. You have even managed to make it understandable and easy to read. You have some real writing talent. Thank you.
婚約指輪
Thanks for taking the time to discuss this, I feel strongly that love and read more on this topic. If possible, such as gain knowledge, would you mind updating your blog with additional information? It is very useful for me.
結婚指輪 手作り
Great because of the knowledge you share with us, I will always follow your blog and will share your blog with my friends.
fnaf
Thank you for sharing this great post, I am very impressed with your post, the information given is very meticulous and easy to understand. I will often follow your next post. driving directions
This is a great inspiring article.I am pretty much pleased with your good work.You put really very helpful information...Keto pills
Где взять значение display для показа изначально скрытого элемента? cookie clicker
This is just the information I am finding everywhere. Thanks for your blog, I just subscribe your blog. This is a nice blog..
ที่เที่ยวพังงา
It’s actually a nice and useful piece of info.
I am glad that you simply shared this helpful info with
us. Please keep us up to date like this. Thanks for sharing 먹튀검증
It’s very effortless to find out any topic on net as compared to textbooks, as I
found this paragraph at this website. 대출
Very nice article, I enjoyed reading your post, very nice share, I want to twit this to my followers. Thanks!.
Domotique
Good website! I truly love how it is easy on my eyes it is. I am wondering how I might be notified whenever a new post has been made. I have subscribed to your RSS which may do the trick? Have a great day!
デジタルノマド
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business.
visit here
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business.
abonnenten24
I'm glad I found this web site, I couldn't find any knowledge on this matter prior to.Also operate a site and if you are ever interested in doing some visitor writing for me if possible feel free to let me know, i am always look for people to check out my web site.
Automação de Hotéis
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business.
Automação de Hotéis
I have not any word to appreciate this post.....Really i am impressed from this post....the person who create this post it was a great human..thanks for shared this with us.
digital marketing
Superbly written article, if only all bloggers offered the same content as you, the internet would be a far better place..
alquiler de carros en barranquilla
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business.
Hausautomation
Because Wordpress is an empty source blogging tool, meaning that it's free and open to everyone, it's a prime target for hackers and ne'er-do-wells. Of course,the Wordpress development team are tireless in constantly working at the script for our own benefit, but none of them of any use if we don't actually getup off our backsides and do a little bit Whatsapp GB APK of are employed at our blogs behind the scenes.
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business.
tree surgeons colchester
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business.
coronavirus isla mujeres
Great write-up, I am a big believer in commenting on blogs to inform the blog writers know that they’ve added something worthwhile to the world wide web!..
best places to eat in cozumel
I can set up my new idea from this post. It gives in depth information. Thanks for this valuable information for all,..
خانه هوشمند
This is just the information I am finding everywhere. Thanks for your blog, I just subscribe your blog. This is a nice blog..
covid isla mujeres
I have been checking out a few of your stories and i can state pretty good stuff. I will definitely bookmark your blog
chemical
Thanks for taking the time to discuss this, I feel strongly that love and read more on this topic. If possible, such as gain knowledge, would you mind updating your blog with additional information? It is very useful for me.
belföldi fuvarozás versenyképesség
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business.
otomatisasi rumah
Great write-up, I am a big believer in commenting on blogs to inform the blog writers know that they’ve added something worthwhile to the world wide web!..
elérhető pályázatírás Debrecen
Superbly written article, if only all bloggers offered the same content as you, the internet would be a far better place..
how to get to cozumel from cancun airport
I read that Post and got it fine and informative. Please share more like that...
treatnheal
I can see that you are an expert at your field! I am launching a website soon, and your information will be very useful for me.. Thanks for all your help and wishing you all the success in your business.
bathroom vanity
Great write-up, I am a big believer in commenting on blogs to inform the blog writers know that they’ve added something worthwhile to the world wide web!..
coronavirus cozumel
Superbly written article, if only all bloggers offered the same content as you, the internet would be a far better place..
taroko gorge day tour
WhatsApp Aero 2021 will help you find new friends and I also want to make new friends
Your article is very useful, the content is great, I have read a lot of articles, but for your article, it left me a deep impression, thank you for sharing. basketball legends
Отправить комментарий
Приветствуются комментарии:Для остальных вопросов и обсуждений есть форум.