slideDown, show and more stopped working for `display: none` elements #2308

Closed
phistuck opened this Issue May 13, 2015 · 51 comments

Projects

None yet
@phistuck

Test case -

<!doctype html>
<script src="http://code.jquery.com/jquery-git2.js"></script>
<style>
div, p { display: none }
</style>
<div>stuff<br/>that<br/>takes<br/>a<br/>height</div>
<p>stuff<br/>that<br/>takes<br/>a<br/>height</p>
<script>
$("p").show();
$("div").slideDown();
</script>

Changing jquery-git2 to jquery-2.1.1 fixes the issue. This is a regression.

This is caused by 86419b1

@matthaywardwebdesign

@phistuck Can confirm, did break a client's site which happened to be using jquery-git.js. Seeing identical behaviour to above.

@mgol
Member
mgol commented May 13, 2015

Wow, don't use jquery-git.js in production, ever.

@matthaywardwebdesign

@mzgol Haha yep, never have since. This site happened to be my first paid job many a year ago. Definitely a bad idea, I'm surprised it survived as long as it did.

@timmywil
Member

This is an intended behavior change for jQuery 3.0. It fixes a few long-standing issues.

@timmywil timmywil closed this May 13, 2015
@scottgonzalez
Member

So for the extremely common case that was presented, what is the recommended solution?

@jzaefferer
Member

Somewhat related, if its supposed to go into 3.0, why is it changing jquery-gi2t?

@mgol
Member
mgol commented May 13, 2015

@jzaefferer jquery-git2 currently mirrors jquery-git, jquery-git1 mirrors jquery-git-compat, i.e. both development lines. I've set it up this way some time ago so that some tools (like jsFiddle/jsBin) that pull the newest jQuery for testing still works. jsFiddle & jsBin now use the new URLs so we should just remove jquery-git2 & jquery-git1.

@jzaefferer
Member

I see. Well, you should start with permanent redirects before deleting them. For jQuery UI we use jquery-git and jquery-git2 for testing. Other projects might be doing the same, so some kind of official information about these changes would be quite useful.

@mgol
Member
mgol commented May 13, 2015

@jzaefferer jquery-git & jquery-git2 point to the same file now so you're effectively testing master twice & compat not at all.

@scottgonzalez
Member

He meant that we test against jquery-git and jquery-git1.

@scottgonzalez
Member

Getting back on track though, what is the recommended solution to the extremely common use case that was presented?

@timmywil
Member

To hide with a class and remove that class (e.g. .removeClass('hidden')) or to put style="display: none" on the element itself, which can be done beforehand in HTML or later in JS (.css('display', 'block').hide()).

http://jsbin.com/tiqico/1/edit?html,css,js,output

That said, this behavior change is a trial run. With more tickets like this, we'll have to think of other solutions to the performance and cascade issues we fixed.

@timmywil
Member

Or possibly revert and live with certain issues.

@scottgonzalez
Member

To hide with a class and remove that class (e.g. .removeClass('hidden'))

So, to eliminate or unnecessarily complicate the use of basic animations. Also, this is impossible to do for any plugin author.

or to put style="display: none" on the element itself, which can be done beforehand in HTML or later in JS (.css('display', 'block').hide()).

So, inline styles or using JavaScript to set the preferred initial rendering state.

That said, this behavior change is a trial run. With more tickets like this, we'll have to think of other solutions to the performance and cascade issues we fixed.

I'd say this must be reverted or we'll end up with a massive uproar.

@timmywil
Member

So, to eliminate or unnecessarily complicate the use of basic animations

It's not that different than setting inline styles. http://jsbin.com/tiqico/2/edit?html,css,js,output

@timmywil
Member

I'd say this must be reverted or we'll end up with a massive uproar.

I don't think it's that simple. Basic usage of show/hide was causing noticeable performance issues.

@timmywil
Member

But we're still discussing this, so I'll reopen.

@timmywil timmywil reopened this May 13, 2015
@dmethvin
Member

The reason we did this was exactly to see how much of an effect it would have, this is WIP after all and we haven't yet done a beta release or blog post about the changes.

There are certainly valid use cases for this gun, but unfortunately a common use seems to be for shooting feet. The compatibility arguments are definitely for keeping all the quirky edge cases so existing code will not have to change.

If we have to revert this I'll go back to recommending that people just avoid show/hide and use classes, since the nuances are too hard for most developers to understand. I don't say that to be disrespectful of them, it's just that these methods have evolved to be a lot more complex and expensive than a developer's intuition says they should be.

@scottgonzalez
Member

Well, no matter how much you recommend that, it will never be a viable solution for plugin authors.

@gibson042
Member

As I alluded to in #2180 (comment), animating cascade-hidden elements can still be accomplished with "bring your own" defaultDisplay logic—which will be trivial in many cases (.removeClass) and damn near impossible in others. But our built-in all-singing all-dancing iframe implementation is completely untenable in my opinion—there's just no reliable way for us to figure out the proper display value of arbitrary hidden elements.

@Mottie
Mottie commented May 13, 2015

@scottgonzalez mentions, this new method does make things very difficult for plugin authors.

This new method doesn't override HTML5's hidden attribute (demo). So now I would need to use:

$el.removeClass("hidden").removeAttr('hidden').show();

I don't understand why an iframe was needed previously, but given that the display option has a finite number of settings (and some that wouldn't even need to be supported like table-column & table-column-group), couldn't a cross-reference of display settings based on the element nodeName work just as well?

@dmethvin
Member

If the content is a hide/show candidate, it is unlikely you'd add or remove the hidden attribute dynamically. The spec says:

The hidden attribute must not be used to hide content that could legitimately be shown in another presentation. For example, it is incorrect to use hidden to hide panels in a tabbed dialog, because the tabbed interface is merely a kind of overflow presentation — one could equally well just show all the form controls in one big page with a scrollbar. It is similarly incorrect to use this attribute to hide content just from one presentation — if something is marked hidden, it is hidden from all presentations, including, for instance, screen readers.

https://html.spec.whatwg.org/multipage/interaction.html#the-hidden-attribute

@gibson042
Member

couldn't a cross-reference of display settings based on the element nodeName work just as well?

Aside from being enormous (and necessarily incomplete, because custom elements are now not only possible but headed towards standardization), such a list could also be rendered inaccurate by CSS overriding default display values. It's edge cases like those that first drove us to look up default values with an iframe when clearing inline display failed to show an element, and now drive us to stop trying. Like I keep saying, you know your application better than we do, and therefore know the right way to reveal an element for animation. We could leverage a cross-platform way to inspect the full cascade for getting it right, but absent that it doesn't seem worth the necessary contortions.

All that said, however, hidden is a special case that we can clear before every "show" animation, which I would be willing to do.

@timmywil
Member

All that said, however, hidden is a special case that we can clear before every "show" animation, which I would be willing to do.

But, like Dave pointed out, that doesn't even seem like correct usage of hidden.

@timmywil
Member

Like I keep saying, you know your application better than we do, and therefore know the right way to reveal an element for animation.

Playing devil's advocate for a second. While this is totally true, sometimes making a best guess is better than nothing. I was nervous about the behavior change with stylesheet-hidden elements and I still get the feeling this is going to cause an uproar. However, I don't want to revert everything we've done. What about falling back to display: block for stylesheet-hidden elements? We wouldn't need to read the display value (which was the performance issue on Obama's website), and we'd only do it when we don't have a display in data. If the user cares about different display values in responsive layouts, there would still be a way to make it work (i.e. not hiding elements in the stylesheet).

@gibson042
Member

I mentioned the possibility in #2180 (comment) , but I'm curious how you intend to detect stylesheet-hidden elements without reading the display value (cf. 86419b1#diff-414c8f59bff0d1b63680b64763d8c529L168 , replaced by 86419b1#diff-f584c95a54f22b45ff73937ddd094f4dR19 ).

@timmywil
Member
timmywil commented Jun 1, 2015

We're going to do a blog post asking for community feedback on this issue.

@timmywil timmywil added Blocker and removed Needs review labels Jun 1, 2015
@timmywil timmywil added this to the 3.0.0 milestone Jun 1, 2015
@markelog
Member
markelog commented Jun 2, 2015

Some code for the one of those ideas i mentioned at the meeting - #2374

@lazd
lazd commented Jul 14, 2015

@dmethvin, that paragraph under the example is a bit confusing, and I think you may have misinterpreted it when you said this:

If the content is a hide/show candidate, it is unlikely you'd add or remove the hidden attribute dynamically.

The example in the spec shows a situation where you set the hidden property dynamically -- the #login form is hidden and the #game element is shown once the user logs in.

I think we can agree that this is exactly how folks use jQuery's show() and hide() today.

To quote the guidelines in the spec:

The hidden attribute must not be used to hide content that could legitimately be shown in another presentation. ... It is similarly incorrect to use this attribute to hide content just from one presentation — if something is marked hidden, it is hidden from all presentations, including, for instance, screen readers.

I interpret this as meaning that, if you intend for a screenreader to still be able to access the content, you shouldn't use [hidden]. But the same holds true for display: none -- when you use it, the element you use it on is totally hidden from everything (including screen readers).

For example, it is incorrect to use hidden to hide panels in a tabbed dialog, because the tabbed interface is merely a kind of overflow presentation — one could equally well just show all the form controls in one big page with a scrollbar.

This makes a lot of assumptions about how a tabpanel is being used, and I think it's what tripped you up.

When read one way, it implies that you shouldn't use [hidden] (or even display: none!) on a tabpanel, which is not true. Every tabpanel implementation I've seen uses display: none or [hidden] on hidden on all panels not associated with the selected tab (and sometimes also aria-hidden to really hammer it in that it's hidden from screen readers too).

Read another way (and I think this was the intended meaning), it means that, if your intention is to perform "overflow management" -- that is, presenting the user with a subset of a large set of content -- then [hidden] is the wrong tool to use as it hides the rest of the content completely, when it should really only be "out of sight," yet not actively hidden from assistive technology and keyboard navigation.

Without debating the intention of a tabpanel, I think we can agree that, when a user calls jQuery.hide(), they intend for the content to be hidden visually and from screenreaders. That's exactly how it works now when hide() sets display: none, and I believe that toggling of the [hidden] attribute in jQuery's show() and hide() would be in line with the spec.

@Eccenux
Eccenux commented Jul 14, 2015

To my understanding the problem of using the iframe is with default display of some elements right? If so then why not just use something like in PR #2374 and simply allowing adding edge-cases for advanced developers. I would see this by documenting how to add new {elementName:defaultDisplay} mapping (to support defining new elements if someone must).

I also like the approach in jQueryMobile where they have a standard click event that just works and vclick for those that need more performance. Forcing someone to use something more complicated for something simple might end up just loosing him on the way. So, for high performance maybe introduce something like showBlock, hideBlock... and simply assume you always show/hide something that is an element with display:block. Or introduce fastShow, fastHide... that takes default display as the first parameter.

@blyndcide

+1 for keeping the old behavior of hide/fade/slide around in some form. I attempted to use the velocity.js version of fade/slide. Yeah, it is faster, but not taking into account edge cases made it less useful.

@e-oz
e-oz commented Jul 17, 2015

Please don't do it. CSS shouldn't depend on what JS lib you use. And plugin authors can't dictate to users if they can use display: none or not - it's ridiculous to expect all CSS styles will be revised just because some new jQuery plugin want it. They just will use jQuery 2.x and forget about upgrade.

Main point: CSS shouldn't care about JS libs, please follow single responsibility principle.

@mgol
Member
mgol commented Jul 17, 2015

And plugin authors can't dictate to users if they can use display: none or not

One solution to this problem would be what @timmywil suggested in this comment, i.e.:

elem.css('display', 'block').hide();

This requires you to know the expected display value beforehand but otherwise will work without modifying the input stylesheets.

@Eccenux
Eccenux commented Jul 17, 2015

Unless jQuery will do some magic delay for css('display', 'block') running elem.css('display', 'block') will make the browser render elem and all it's descendants... Seems like a worse solution then using an inframe (at least performance wise).

If you really want to go that way why not just remove all show/hide alike functions. Seriously. If jQuery 3.0 won't be doing that to move away complication from devs, than maybe some other library will. Or maybe move it to UI or some optional jQuery build.

@gibson042
Member

This is starting to look like general griping, so I'm going to ask some specific questions:

  • Have you tried the jQuery 3.0 alpha release? Excepting @phistuck and @matthaywardwebdesign, who obviously have.
  • Given that .show()ing a display: none element should generally leave the element without any inline display property (for e.g. responsive stylesheets), what is your expected end inline CSS of a cascade-hidden element? Keep in mind that we could only guess at a proper display, and that it may actually differ between elements of the same name.
@Mottie
Mottie commented Jul 18, 2015

If the code is basically being boiled down to be a shortcut for elem.css('display', '') & elem.css('display', 'block'), I would have to agree with @Eccenux and say completely remove show, hide & toggle from jQuery.

The current code can be moved into an addon, if you even want to continue to support it. Either way, this change will make devs think more carefully about how to hide or show elements on their pages.

@Eccenux
Eccenux commented Jul 19, 2015

@gibson042 Do you mean a responsive website or a squishy website? The difference is that responsive website can render fine on a certain device. Once something is show it stays shown. Squishy website is something you showoff to your boss or friends by resizing a browser. Some things might disappear and then show again while you squish the browser back and forth. But most of the time it's not an actual use case.

Also I did try 3.0 and there is one solution I can see to make show/hide work as expected and that is running something like $('.hidden').hide().removeClass('hidden');. But that would break squishiness too (if I understand the use case correctly).

@Mr21
Contributor
Mr21 commented Jul 19, 2015

@Eccenux, hmmm something not responsive is a web application who are detecting the device on the server-side and send back something only for this specific device. Having something responsive is having only one version of the design's code. So if the website doesn't respond when you resize your tab, it's not Responsive Web Design.

@phistuck

@Mr21 - I actually agree with @Eccenux - resizing the tab is not necessarily the use case for a responsive design. It is a nice side effect. After all, resizing the tab to a mobile breakpoint may bring, for example, Geolocation features that are irrelevant to desktop users. Just because the user resized the tab, it does not mean they are suddenly mobile.

@Mr21
Contributor
Mr21 commented Jul 19, 2015

@phistuck, okay, maybe if you are doing something like:

$(function() {
  var w = $(window).width();
  switch( true ) {
    case w < 1000:
      // ...
    case w < 500:
      // ...
  }
})

But, the responsive is only one version of the code for all, and 90% of the responsive design is about media queries.

@Eccenux
Eccenux commented Jul 19, 2015

Sorry, I didn't want to spark a new discussion about responsiveness and squishiness. The concept of being responsive just to show off squishiness was discussed by Brad Frost in his presentation. I kind of thought it got estabilished by now that responsiveness is not about squishiness but about adapting to screen size (or more exactly to device).

Yes, responsive websites should use media queries, but once the page is loaded, and initial view is established, most o the time you don't need to re-adapt (because -- other then for testing -- people don't squish websites around).

Besides. If you must you can always use !important in RWD.

But again I don't really see all this to be important for show/hide. Usually things that are shown are in some containers that don't change display that much (most of the time just change width and maybe loose float attribute).

@Eccenux
Eccenux commented Jul 19, 2015

BTW. I've made a fiddle with two simple examples that doesn't work in jQ3-alpha
https://jsfiddle.net/c50p3Lmv/

Switch to jQ3 from 2.1 on the left. The workaround is not as bad as I originally thought (shouldn't cause performance issues), but I still find it confusing. Note that this is a very simple example as it assumes you use one single class to hide elements that are to be shown and that you can add display:none for all of them onload.

@phistuck

@Mr21 - an example is very simple - showing or hiding an off canvas menu on mobile, but the menu is horizontally open on desktop (the media query hides or shows the button and the menu, but showing the menu on mobile needs some show and hide mechanism that the button triggers).

@phistuck

@Eccenux - by the way, one specific use case for squishiness in responsive design is changing orientation.

@Eccenux
Eccenux commented Jul 19, 2015

@phistuck

by the way, one specific use case for squishiness in responsive design is changing orientation."

Yes, but I don't see any real-world use case where you would want to change display upon orientation change. I dont' think you should hide slide-out menu upon oriention change. If you can show me a use case I can show you an easy workaround.

Can someone show me a workarond for this one (without changing HTML or CSS):
https://jsfiddle.net/40zuavo2/2/
(note that it works fine if you change to jQuery 2.x)

@mgol
Member
mgol commented Jul 19, 2015

Can someone show me a workarond for this one (without changing HTML or CSS):
https://jsfiddle.net/40zuavo2/2/

https://jsfiddle.net/40zuavo2/3/

@Eccenux
Eccenux commented Jul 19, 2015

Can someone show me a workarond for this one (without changing HTML or CSS):
https://jsfiddle.net/40zuavo2/2/

https://jsfiddle.net/40zuavo2/3/

That won't work if I have more dialogs e.g. on other pages.

@Eccenux
Eccenux commented Jul 19, 2015

Also this is the one which makes the browser render the dialog (and all other hidden elements) for a brief moment. This will probably not be a problem on desktop, but might be a problem for mobile -- especially when other elements need to be recalculated when elements are shown.

@mgol
Member
mgol commented Jul 19, 2015

Also this is the one which makes the browser render the dialog (and all other hidden elements) for a brief moment.

Do you have an example of a browser in which that happens? Browsers batch style recalculations so multiple changes don't trigger multiple reflows unless someone asks for the resolved style of the element in the meantime (because then the browser has to know the real value to return it). So this:

div.style.display = 'block';
div.style.display = 'none';

shouldn't show the element in any browser. The new .hide() doesn't check the resolved display so a reflow shouldn't happen.

It's true, though, that the reflow would happen if such code was executed with jQuery 2.1.4 or older where .hide() does check the resolved display.

@Eccenux
Eccenux commented Jul 21, 2015

It's true, though, that the reflow would happen if such code was executed with jQuery 2.1.4 or older where .hide() does check the resolved display .

Ahhhh. So that's what happened :-).

OK. So indeed .css('display', 'block').hide() should be a valid workaround for most cases.

@Eccenux
Eccenux commented Jul 21, 2015

BTW. Surprisingly most of jQuery UI works. Only dialogs and drop-downs don't like jQ3.

@gibson042 gibson042 was assigned by timmywil Nov 18, 2015
@gibson042 gibson042 added a commit to gibson042/jquery that referenced this issue Jan 11, 2016
@gibson042 gibson042 CSS: Restore cascade-override behavior in .show
Fixes gh-2654
Fixes gh-2308
Ref 86419b1
aabf960
@timmywil timmywil pushed a commit that closed this issue Jan 13, 2016
@gibson042 gibson042 + Timmy Willison CSS: Restore cascade-override behavior in .show
Fixes gh-2654
Fixes gh-2308
Close gh-2810
Ref 86419b1
dba93f7
@timmywil timmywil closed this in dba93f7 Jan 13, 2016
@sqrthree sqrthree referenced this issue in sqrthree/sqrthree.github.io Feb 20, 2016
Open

[译] jQuery 3.0 以及兼容版的 Alpha 版本发布 #3

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment