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 Javascript 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 Javascript solution:
https://jsfiddle.net/b9chris/hweyecu4/17/
Starting with a box like:
<div id="box" class="hidden">Lorem</div>
A hidden box.
div.hidden {
display: none;
}
#box {
transition: opacity 1s;
}
We're going to use a trick found in a related q/a, checking offsetHeight to throttle the browser instantaneously:
https://stackoverflow.com/a/16575811/176877
First, a library formalizing the above trick:
$.fn.noTrnsn = function () {
return this.each(function (i, tag) {
tag.style.transition = 'none';
});
};
$.fn.resumeTrnsn = function () {
return this.each(function (i, tag) {
tag.offsetHeight;
tag.style.transition = null;
});
};
Next, we're going to use it to reveal a box, and fade it in:
$('#button').on('click', function() {
var tag = $('#box');
if (tag.hasClass('hidden'))
tag.noTrnsn().removeClass('hidden')
.css({ opacity: 0 })
.resumeTrnsn().css({ opacity: 1 });
else
tag.css({ opacity: 0 });
});
This fades the box in and out. So, .noTrnsn()
turns off transitions, then we remove the hidden
class, which flips display
from none
to its default, block
. We then set opacity to 0 to get ready for fading in. Now that we've set the stage, we turn transitions back on, with .resumeTrnsn()
. And finally, kick off the transition by setting opacity to 1.
Without the library, both the change to display and the change to opacity would've gotten us undesirable results. If we simply removed the library calls, we'd get no transitions at all.
Note that the above does not set display to none again at the end of the fadeout animation. We can get fancier though. Let's do so with one that fades in and grows in height from 0.
Fancy!
https://jsfiddle.net/b9chris/hweyecu4/22/
#box {
transition: height 1s, opacity 1s;
}
We're now transitioning both height and opacity. Note that we are not setting height, which means it is the default, auto
. Conventionally this cannot be transitioned - moving from auto to a pixel value (like 0) will get you no transition. We're going to work around that with the library, and one more library method:
$.fn.wait = function (time, fn) {
if (time)
this.delay(time);
if (!fn)
return this;
var _this = this;
return this.queue(function (n) {
fn.call(_this);
n();
});
};
This is a convenience method that lets us participate in jQuery's existing fx/animation queue, without requiring any of the animation framework that's now excluded in jQuery 3.x. I'm not going to explain how jQuery works, but suffice to say, the .queue()
and .stop()
plumbing that jQuery provides help us prevent our animations from stepping on each other.
Let's animate the slide down effect.
$('#button').on('click', function() {
var tag = $('#box');
if (tag.hasClass('hidden')) {
// Open it
// Measure it
tag.stop().noTrnsn().removeClass('hidden').css({
opacity: 0, height: 'auto'
});
var h = tag.height();
tag.css({ height: 0 }).resumeTrnsn()
// Animate it
.css({ opacity: 1, height: h })
.wait(1000, function() {
tag.css({ height: 'auto' });
});
} else {
// Close it
// Measure it
var h = tag.noTrnsn().height();
tag.stop().css({ height: h })
.resumeTrnsn()
// Animate it
.css({ opacity: 0, height: 0 })
.wait(1000, function() {
tag.addClass('hidden');
});
}
});
This code begins by checking on #box
and whether it's currently hidden, by checking on its class. But it accomplishes more using the wait()
library call, by adding the hidden
class at the end of the slideout/fade animation, which you'd expect to find if it is in fact hidden - something the above simpler example could not do. This happens to also enable display/hiding the element over and over, which was a bug in the previous example, because the hidden class was never restored.
You can also see CSS and class changes being called after .noTrnsn()
to generally set the stage for animations, including taking measurements, like measuring what will be the final height of #box
without showing that to the user, before calling .resumeTrnsn()
, and animating it from that fully-set stage to its goal CSS values.
Old Answer
https://jsfiddle.net/b9chris/hweyecu4/1/
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 and then waiting 50 ms, 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 50 ms or 2000 ms delay on the standard fx queue where it's easy to find/resolve by other coders building on top of this.
z-index:0
as well. – DanManvisibility: hidden
unless you want screenreaders to read it (whereas typical browsers won't). It only defines the visibility of the element (like sayingopacity: 0
), and it's still selectable, clickable, and whatever it used to be; it's just not visible. – Forest Katschpointer-events
in IE 8,9,10, so it's not always ok – Steven Pribilinskiy