I'm currently designing a kind of CSS 'mega dropdown' menu - basically a normal CSS-only dropdown menu, but one that contains different types of content.
At the moment, it appears that CSS3 Transitions don't apply to the 'display' property, i.e. you can't do any sort of transition from display: none
to display: block
(or any combination).
Can anyone think of a way for the second-tier menu from the above example to 'fade in' when someone hovers over one of the top level menu items?
I'm aware that you can use transitions on the visibility:
property, but I can't think of a way to utilise that effectively.
I've also tried using height but that just failed miserably.
I'm also aware that it's trivial to achieve this using JavaScript, but I wanted to challenge myself to use just CSS and I think I'm coming up a little short.
All and any suggestions most welcome.
转载于:https://stackoverflow.com/questions/3331353/transitions-on-the-display-property
Instead of using display you could store the element 'off-screen' until you needed it, then set its position to where you want it and transform it at the same time. This brings up a whole host of other design issues though, so ymmv. You probably wouldn't want to use display anyway, as you'd want the content to be accessible to screen readers, which for the most part try to obey rules for visibility - i.e., if it shouldn't be visible to the eye, it won't show up as content to the agent.
You need to hide the element by other means in order to get this to work.
I accomplished the effect by positioning both <div>
s absolutely and setting the hidden one to opacity: 0
.
If you even toggle the display
property from none
to block
, Your transition on other elements will not occur.
To work around this, always allow the element to be display: block
, but hide the element by adjusting any of these means:
height
to 0
.opacity
to 0
.overflow: hidden
.There are likely more solutions, but you cannot perform a transition if you toggle the element to display: none
. For example, you may attempt to try something like this:
div {
display: none;
transition: opacity 1s ease-out;
opacity: 0;
}
div.active {
opacity: 1;
display: block;
}
But that will not work. From my experience, I have found this to do nothing.
Because of this, you will always need to keep the element display: block
- but you could get around it by doing something like this:
div {
transition: opacity 1s ease-out;
opacity: 0;
height: 0;
overflow: hidden;
}
div.active {
opacity: 1;
height: auto;
}
display
is not one of the properties that transition works upon.
See http://www.w3.org/TR/css3-transitions/#animatable-properties- for the list of properties that can have transitions applied to them.
At the time of this post all major browsers disable CSS transitions if you try to change the display
property, but CSS animations still work fine so we can use them as a work-around.
Example Code:- (You can apply it to your menu accordingly) Demo
Add the following CSS to your stylesheet:-
@-webkit-keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
Then apply the fadeIn
animation to the child on parent hover:- (and of course set display: block
)
.parent:hover .child {
display: block;
-webkit-animation: fadeIn 1s;
animation: fadeIn 1s;
}
I suspect anyone just starting CSS transitions quickly discovers that they don't work if you're modifying the display property (block/none) at the same time. One work-around that hasn't yet been mentioned is that you can continue to use display:block/none to hide/show the element, but set its opacity to 0 so that even when it's display:block, it's still invisible. Then to fade it in, add another CSS class such as "on" which sets the opacity to 1 and defines the transition for opacity. As you may have imagined, you'll have to use JavaScript to add that "on" class to the element, but at least you're still using CSS for the actual transition.
P.S. If you find yourself in a situation where you need to do both display:block, and add class "on", at the same time, defer the latter using setTimeout. Otherwise the browser just sees both things as happening at once and disables the transition.
I ran into this today, with a position: fixed
modal that I was reusing. I couldn't keep it display: none
and then animate it, as it just jumped into appearance, and and z-index
(negative values, etc) did weird things as well.
I was also using a height: 0
to height: 100%
, but it only worked when the modal appeared. This is the same as if you used left: -100%
or something.
Then it struck me that there was a simple answer. Et voila:
First, your hidden modal. Notice the height
is 0
, and check out the height
declaration in transitions... it has a 500ms
, which is longer than my opacity
transition. Remember, this affects the out-going fade-out transition: returning the modal to its default state.
#modal-overlay {
background: #999;
background: rgba(33,33,33,.2);
display: block;
overflow: hidden;
height: 0;
width: 100%;
position: fixed;
top: 0;
left: 0;
opacity: 0;
z-index: 1;
-webkit-transition: height 0s 500ms, opacity 300ms ease-in-out;
-moz-transition: height 0s 500ms, opacity 300ms ease-in-out;
-ms-transition: height 0s 500ms, opacity 300ms ease-in-out;
-o-transition: height 0s 500ms, opacity 300ms ease-in-out;
transition: height 0s 500ms, opacity 300ms ease-in-out;
}
Second, your visible modal. Say you're setting a .modal-active
to the body
. Now the height
is 100%
, and my transition has also changed. I want the height
to be instantly changed, and the opacity
to take 300ms
.
.modal-active #modal-overlay {
height: 100%;
opacity: 1;
z-index: 90000;
-webkit-transition: height 0s, opacity 300ms ease-in-out;
-moz-transition: height 0s, opacity 300ms ease-in-out;
-ms-transition: height 0s, opacity 300ms ease-in-out;
-o-transition: height 0s, opacity 300ms ease-in-out;
transition: height 0s, opacity 300ms ease-in-out;
}
That's it, it works like a charm.
I suspect that the reason that transitions are disabled if “display” is changed is because of what display actually does. It does not change anything that could conceivably be smoothly animated.
“display: none;” and “visibility: hidden;” are two entirely different things. Both do have the effect of making the element invisible, but with “visibility: hidden;” it’s still rendered in the layout, but just not visibly so. The hidden element still takes up space, and is still rendered inline or as a block or block-inline or table or whatever the “display” element tells it to render as, and takes up space accordingly. Other elements do not automatically move to occupy that space. The hidden element just doesn’t render its actual pixels to the output.
“display: none” on the other hand actually prevents the element from rendering entirely. It does not take up any layout space. Other elements that would’ve occupied some or all of the space taken up by this element now adjust to occupy that space, as if the element simply did not exist at all.
“display” is not just another visual attribute. It establishes the entire rendering mode of the element, such as whether it’s a block, inline, inline-block, table, table-row, table-cell, list-item, or whatever! Each of those have very different layout ramifications, and there would be no reasonable way to animate or smoothly transition them (try to imagine a smooth transition from “block” to “inline” or vice-versa, for instance!).
This is why transitions are disabled if display changes (even if the change is to or from “none” — “none” isn’t merely invisiblity, it’s its own element rendering mode that means no rendering at all!),
The simplest universal solution to the problem is: feel free to specify display:none
in your CSS, however you will have change it to block
(or whatever else) using JavaScript, and then you'll also have to add a class to your element in question that actually does the transition with setTimeout(). That's all.
I.e.:
<style>
#el {
display: none;
opacity: 0;
}
#el.auto-fade-in {
opacity: 1;
transition: all 1s ease-out; /* future, future, please come sooner! */
-webkit-transition: all 1s ease-out;
-moz-transition: all 1s ease-out;
-o-transition: all 1s ease-out;
}
</style>
<div id=el>Well, well, well</div>
<script>
var el = document.getElementById('el');
el.style.display = 'block';
setTimeout(function () { el.className = 'auto-fade-in' }, 0);
</script>
Tested in the latest sane browsers. Obviously shouldn't work in IE9 or earlier.
Change overflow:hidden
to overflow:visible
. It works better. I use like this:
#menu ul li ul {
background-color:#fe1c1c;
width:85px;
height:0px;
opacity:0;
box-shadow:1px 3px 10px #000000;
border-radius:3px;
z-index:1;
-webkit-transition:all 0.5s ease;
-moz-transition:all 0.6s ease;
}
#menu ul li:hover ul {
overflow:visible;
opacity:1;
height:140px;
}
visible
is better because overflow:hidden
act exactly like a display:none
.
My neat JavaScript trick is to separate the entire scenario into two different functions!
To prepare things, one global variable is declared and one event handler is defined:
var tTimeout;
element.addEventListener("transitionend", afterTransition, true);//firefox
element.addEventListener("webkitTransitionEnd", afterTransition, true);//chrome
Then, when hiding element, I use something like this:
function hide(){
element.style.opacity = 0;
}
function afterTransition(){
element.style.display = 'none';
}
For reappearing the element, I am doing something like this:
function show(){
element.style.display = 'block';
tTimeout = setTimeout(timeoutShow, 100);
}
function timeoutShow(){
element.style.opacity = 1;
}
It works, so far!
You can simply use css visibility: hidden/visible instead of display : none/block
div {
visibility:hidden;
-webkit-transition: opacity 1s ease-out;
-moz-transition: opacity 1s ease-out;
-o-transition: opacity 1s ease-out;
transition: opacity 1s ease-out;
opacity: 0;
}
parent:hover > div {
opacity: 1;
visibility: visible;
}
you can also use this:
.dropdown {
height: 0px;
width: 0px;
opacity: .0;
color: white;
}
.dropdown:hover {
height: 20px;
width: 50px;
opacity: 1;
transition: opacity 200ms;
/* Safari */
-webkit-transition: opacity 200ms;
}
Taking from a few of these answers and some suggestions elsewhere, the following works great for hover menus (I'm using this with bootstrap 3, specifically):
nav .dropdown-menu {
display: block;
overflow: hidden;
max-height: 0;
opacity: 0;
transition: max-height 500ms, opacity 300ms;
-webkit-transition: max-height 500ms, opacity 300ms;
}
nav .dropdown:hover .dropdown-menu {
max-height: 500px;
opacity: 1;
transition: max-height 0, opacity 300ms;
-webkit-transition: max-height 0, opacity 300ms;
}
You could also use height
in place of max-height
if you specify both values since height:auto
is not allowed with transition
s. The hover value of max-height
needs to be greater than the height
of the menu can possibly be.
According to W3C Working Draft 19 November 2013 display
is not an animatable property. Fortunately, visibility
is animatable. You may chain its transition with a transition of opacity (JSFiddle):
HTML:
<a href="http://example.com" id="foo">Foo</a>
<button id="hide-button">Hide</button>
<button id="show-button">Show</button>
CSS:
#foo {
transition-property: visibility, opacity;
transition-duration: 0s, 1s;
}
#foo.hidden {
opacity: 0;
visibility: hidden;
transition-property: opacity, visibility;
transition-duration: 1s, 0s;
transition-delay: 0s, 1s;
}
JavaScript for testing:
var foo = document.getElementById('foo');
document.getElementById('hide-button').onclick = function () {
foo.className = 'hidden';
};
document.getElementById('show-button').onclick = function () {
foo.className = '';
};
Note that if you just make the link transparent, without setting visibility: hidden
, then it would stay clickable.
No javascript required, and no outrageously huge max-height needed. Instead, set your max-height on your text elements, and use a font relative unit such as rem or em. This way, you can set a max height larger than your container, while avoiding a delay or "popping" when the menu closes:
HTML
<nav>
<input type="checkbox" />
<ul>
<li>Link 1</li>
<li>Link 1</li>
<li>Link 1</li>
<li>Link 1</li>
</ul>
</nav>
CSS
nav input + ul li { // notice I set max-height on li, not ul
max-height: 0;
}
nav input:checked + ul li {
max-height: 3rem; // a little bigger to allow for text-wrapping - but not outrageous
}
See an example here: http://codepen.io/mindfullsilence/pen/DtzjE
I know this is a very old question but for people who are looking at this thread, you can add a custom animation to the block property now.
@keyframes showNav {
from {opacity: 0;}
to {opacity: 1;}
}
.subnav-is-opened .main-nav__secondary-nav {
display: block;
animation: showNav 250ms ease-in-out both;
}
In this demo the sub-menu changes from display:none
to display:block
and still manages to fade.
After the accepted answer from Guillermo was written the CSS transition Spec of 3 April 2012 changed the behavior of the visibility transition and now it is possible to solve this problem in a shorter way, without the use of transition-delay:
.myclass > div {
transition:visibility 1s, opacity 1s;
visibility:hidden; opacity:0
}
.myclass:hover > div
{ visibility:visible; opacity:1 }
The run time specified for both transitions should usually be identical (although a slightly longer time for visibility is not a problem). For a running version, see my blog http://www.taccgl.org/blog/css-transition-visibility.html#visibility-opacity.
W.r.t. the title of the question "Transitions on the display: property" and in response to comments from Rui Marques and josh to the accepted answer: This solution works in cases where it is irrelevant if the display or visibility property is used (as it probably was the case in this question). It will not completely remove the element as display:none, just make it invisible but it still stays in the document flow and influences the position of the following elements. Transitions that completely remove the element similar to display:none can be done using height (as indicated by other answers and comments), max-height, or margin-top/bottom, but also see How can I transition height: 0; to height: auto; using CSS? and my blog http://www.taccgl.org/blog/css_transition_display.html.
In response to comment from GeorgeMillo: Both properties and both transitions are needed: The opacity property is used to create a fade-in and fade-out animation and the visibility property to avoid the element still reacting on mouse events. Transitions are needed on opacity for the visual effect and on visibility to delay hiding until the fade-out is finished.
I think SalmanPK has the closest answer, it does fade an item in or out, with the following CSS animations. However the display property does not animate smoothly, only the opacity.
@-webkit-keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
@-webkit-keyframes fadeOut {
from { opacity: 1; }
to { opacity: 0; }
}
If you want to animate the element moving from display block to display none, I can't see that it is currently possible just with CSS, you have to get the height and use a CSS animation to decrease the height. This is possible with CSS as shown in the example below, but it would be tricky to know the exact height values you need to animate for an element.
jsFiddle example
@-webkit-keyframes pushDown {
0% {
height: 10em;
}
25% {
height: 7.5em;
}
50% {
height: 5em;
}
75% {
height: 2.5em;
}
100% {
height: 0em;
}
}
.push-down {
-webkit-animation: pushDown 2s forwards linear;
}
var element = document.getElementById("element");
// push item down
element.className = element.className + " push-down";
Edit: display none is not being applied in this example.
@keyframes hide {
0% {
display: block;
opacity: 1;
}
99% {
display: block;
}
100% {
display: none;
opacity: 0;
}
}
What's happening above is that through 99% of the animation display is set to block while the opacity fades out. In the last moment display property is set to none.
And the most important bit is to retain the last frame after the animation ends using animation-fill-mode: forwards
.hide {
animation: hide 1s linear;
animation-fill-mode: forwards;
}
Here are two examples: https://jsfiddle.net/qwnz9tqg/3/
I feel almost bad answering to a question with that many answers, but this solution has excellent compatibility and I haven't seen it yet:
.hidden-element {
position: absolute;
z-index: -1;
pointer-events: none;
visibility: hidden;
opacity: 0;
transition: visibility 0s, opacity .5s ease-out;
}
.hidden-element.visible {
position: static;
z-index: auto;
pointer-events: auto;
visibility: visible;
opacity: 1;
}
Explanation: it uses the visibility: hidden
trick (which is compatible with “show-and-animate” in one step) but uses the combination position: absolute; z-index: -1; pointer-events: none;
to make sure that the hidden container does not take space and does not answer to user interactions.
You can get this to work the natural way you expected - using display - but you have to throttle the browser to get it to work, using either JS or as others have suggested a fancy trick with one tag inside another. I don't care for the inner tag as it further complicates CSS and dimensions, so here's the JS solution:
https://jsfiddle.net/b9chris/hweyecu4/1/
Starting with a box like:
<div class="box hidden">Lorem</div>
A hidden box. You can have it transition on click with:
function toggleTransition() {
var el = $("div.box1");
if (el.length) {
el[0].className = "box";
el.stop().css({maxWidth: 10000}).animate({maxWidth: 10001}, 2000, function() {
el[0].className = "box hidden";
});
} else {
el = $("div.box");
el[0].className = "box";
el.stop().css({maxWidth: 10001}).animate({maxWidth: 10000}, 50, function() {
el[0].className = "box box1";
});
}
return el;
}
someTag.click(toggleTransition);
The CSS is what you'd guess:
.hidden {
display: none;
}
.box {
width: 100px;
height: 100px;
background-color: blue;
color: yellow;
font-size: 18px;
left: 20px;
top: 20px;
position: absolute;
-webkit-transform-origin: 0 50%;
transform-origin: 0 50%;
-webkit-transform: scale(.2);
transform: scale(.2);
-webkit-transition: transform 2s;
transition: transform 2s;
}
.box1{
-webkit-transform: scale(1);
transform: scale(1);
}
The key is throttling the display property. By removing the hidden class then waiting 50ms, then starting the transition via the added class, we get it to appear and then expand like we wanted, instead of it just blipping onto the screen without any animation. Similar occurs going the other way, except we wait till the animation is over before applying hidden.
Note: I'm abusing .animate(maxWidth)
here to avoid setTimeout
race conditions. setTimeout
is quick to introduce hidden bugs when you or someone else picks up code unaware of it. .animate()
can easily be killed with .stop()
. I'm just using it to put a 50ms or 2000ms delay on the standard fx queue where it's easy to find/resolve by other coders building on top of this.
You can do this with transition events, so what you so is, you build 2 css classes for the transition, one holding the animation other, holding the display none state. and you switch them after the animation is ended? In my case i can display the divs again if i press a btn, and remove both classes.
Try the snipped below...
$(document).ready(function() {
//assign transition event
$("table").on("animationend webkitAnimationEnd", ".visibility_switch_off", function(event) {
//we check if this is the same animation we want
if (event.originalEvent.animationName == "col_hide_anim") {
//after the animation we assign this new class that basically hides the elements.
$(this).addClass("animation-helper-display-none");
}
});
$("button").click(function(event) {
$("table tr .hide-col").toggleClass(function() {
//we switch the animation class in a toggle fashion...
//and we know in that after the animation end, there is will the animation-helper-display-none extra class, that we nee to remove, when we want to show the elements again, depending on the toggle state, so we create a relation between them.
if ($(this).is(".animation-helper-display-none")) {
//im toggleing and there is already the above classe, then what we want it to show the elements , so we remove both classes...
return "visibility_switch_off animation-helper-display-none";
} else {
//here we just want to hide the elements, so we just add the animation class, the other will be added later be the animationend event...
return "visibility_switch_off";
}
});
});
});
table th {
background-color: grey;
}
table td {
background-color: white;
padding:5px;
}
.animation-helper-display-none {
display: none;
}
table tr .visibility_switch_off {
animation-fill-mode: forwards;
animation-name: col_hide_anim;
animation-duration: 1s;
}
@-webkit-keyframes col_hide_anim {
0% {opacity: 1;}
100% {opacity: 0;}
}
@-moz-keyframes col_hide_anim {
0% {opacity: 1;}
100% {opacity: 0;}
}
@-o-keyframes col_hide_anim {
0% {opacity: 1;}
100% {opacity: 0;}
}
@keyframes col_hide_anim {
0% {opacity: 1;}
100% {opacity: 0;}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table>
<theader>
<tr>
<th>Name</th>
<th class='hide-col'>Age</th>
<th>Country</th>
</tr>
</theader>
<tbody>
<tr>
<td>Name</td>
<td class='hide-col'>Age</td>
<td>Country</td>
</tr>
</tbody>
</table>
<button>Switch - Hide Age column with fadeout animation and display none after</button>
</div>
It can be handle by using transition timing functions step-end
and step-start
For example: https://jsfiddle.net/y72h8Lky/
$(".run").on("click", function() {
$(".popup").addClass("show");
});
$(".popup").on("click", function() {
$(".popup").removeClass("show");
})
.popup {
opacity: 0;
display: block;
position: absolute;
top: 100%;
bottom: 0;
left: 0;
right: 0;
z-index: 1450;
background-color: rgba(0, 175, 236, 0.6);
transition: opacity 0.3s ease-out, top 0.3s step-end;
}
.popup.show {
transition: opacity 0.3s ease-out, top 0.3s step-start;
opacity: 1;
top: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="popup"></div>
<button class="run" style="font-size: 24px;">Click on me</button>
</div>
I started an open source skeleton project called Toggle Display Animate
https://marcnewton.github.io/Toggle-Display-Animate/
This skeleton helper will allow you to easily mimic jQuery show/hide but with in/out CSS3 transition animations.
It uses class toggles so you can use any css methods you want on elements besides display:none|block|table|inline etc as well as other alternate uses that can be thought up.
Its main design purpose is for element toggle states, it supports a revert state where hiding the object allows you to run your keyframe in reverse or play an alternate animation for hiding the element.
Most of the markup for the concept I am working on is CSS, there is very little javascript actually used.
There is a demo here: http://marcnewton.co.uk/projects/toggle-display-animate/
I've came across this issue multiple times and now simply went with:
.block {
opacity: 1;
transition: opacity 250ms ease;
}
.block--invisible {
pointer-events: none;
opacity: 0;
}
By adding the class block--invisible
the whole Elements will not be clickable but all Elements behind it will be because of the pointer-events:none
which is supported by all major browsers (no IE < 11).
I finally found a solution for me, by combining opacity
with position absolute
(not to occupy space when hidden).
.toggle {
opacity: 0;
position: absolute;
transition: opacity 0.8s;
}
.parent:hover .toggle {
opacity: 1;
position: static;
}
Instead of callbacks, which don't exist in CSS, we can use transition-delay
property.
#selector {
overflow: hidden; // hide the element content, while height = 0
height: 0; opacity: 0;
transition: height 0ms 400ms, opacity 400ms 0ms;
}
#selector.visible {
height: 100%; opacity: 1;
transition: height 0ms 0ms, opacity 600ms 0ms;
}
So, what's going on here?
When visible
class is added, both height
and opacity
start animation without delay (0ms), though height
takes 0ms to complete animation (equivalent of display: block
) and opacity
takes 600ms.
When visible
class is removed, opacity
starts animation (0ms delay, 400ms duration), and height waits 400ms and only then instantly (0ms) restores initial value (equivalent of display: none
in the animation callback).
Note, this approach is better, than ones using visibility
. In such case the element still occupies the space on the page, and it's not always suitable.
For more examples please refer this article.
If you are using jQuery to set your classes, this will work 100%:
$(document).ready(function() {
$('button').click(function() {
var container = $('.container');
if (!container.hasClass('active')) {
container.addClass('show').outerWidth();
container.addClass('active');
}
else {
container.removeClass('active').one('transitionend', function() {
container.removeClass('show');
});
}
});
});
.container {
display: none;
opacity: 0;
transition: opacity 0.3s ease;
}
.container.show {
display: flex;
}
.container.active {
opacity: 1;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button type="button">Toggle</button>
<div class="container">
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
</div>
Of course you could just use jQuery .fadeIn()
and .fadeOut()
functions, but the advantage of setting classes instead is in case you want to transition to a display value other than block
(as is the default with .fadeIn()
and .fadeOut()
).
Here I am transitioning to display flex
with a nice fade effect.
</div>