问题
I'm having trouble creating a flyout menu with a specific effect. The flyout goes from display:none to block and then I use jquery to animate opacity from 0 to 1 (and vice versa). This is necessary because otherwise the transition does not happen when the element has just had it's display property changed. I did not think that this would propagate to children. But inside of my flyout I have 4 columns of links that have an opacity transition, each with it's own delay so they come in one at a time. However, this does not work as the flyout appears. They are instantly at opacity: 1 and even with a long delay time it still does not work.
Is there a way around this? I knew that CSS animation alongside a display change on the same element did not work, but finding out that any child animations also do not work is a little frustrating. I'd rather not have to write javascript when the CSS is so simple. But if javascript is the only answer, then that will be an easy solve.
Here's a very simplified example of the code:
$flyout.addClass('in').animate({opacity: 1}, 200, "linear");
"in" is the class that causes the transition on the columns:
.flyout { display: none; }
.flyout.in { display: block; }
.columns li {
opacity: 0;
-webkit-transition: opacity 0.2s;
}
.flyout.in .columns li { opacity: 1; }
// delay increases with each column
.columns > li:first-child {
-webkit-transition-delay: 0.2s;
}
回答1:
Is there a way around this? I knew that CSS animation alongside a display change on the same element did not work, but finding out that any child animations also do not work is a little frustrating.
It does not only apply to the same element, but the entire sub-tree - as the entire sub-tree is not rendered.
- you can set
display: block
on the wrapper, then force a reflow (by flushing the style buffer withwrapperElement.offsetHeight;
), then add a class that setsopacity:1
to your children (or do whatever you're doing to kick off the animations). - you can use a different method of visually hiding your wrapper, eg
width: 0; height: 0; overflow: hidden; visibility: hidden;
(or, for nicer transitionstransform: scale(0); visibility: hidden; pointer-events: none;
)
As soon as display:none
is involved, you're screwed when it comes to transitions. The best way is to avoid it. I've been using the second option without any significant problems for quite a while now.
edit after OP added some demo code:
- the
.animate()
of the wrapper can be done in CSS as well - do not only use the vendor-prefixed CSS property
-webkit-transition
, but the propertransition
as well // delay increases with each column
looks like a misconception. all elements the selector.columns > li:first-child
applies to will have the exact same delay - they won't wait for the previous element to finish its transition. If you want to define that in CSS, you'll have to play with :nth-child() or one of its cousins
回答2:
if you want only to change the opacity
you can use FadeIn and FadeOut functions of JQuery but if you want more complex transition you can use CSS3 (this is a realy good library).
See this DEMO where you can see this two different way.
You can also add a controll to the class like this:
$("OBJECT").click(function(){
if ($("OBJECT").hasClass("CLASS")){
$("OBJECT").removeClass("CLASS");
} else {
$("OBJECT").addClass("CLASS");
}
});
to make two way functions.
$(document).ready(function(){
$("#fadeOut").click(function(){
var duration = 500;
$("#div").fadeOut(duration);
});
$("#css").click(function(){
$("#div").addClass("out");
setTimeout(
function() {
$("#div").css("display", "none");
},
2001);
});
});
#div {
width:200px;
height:200px;
background-color:red;
text-align:center;
vertical-align:middle;
/* Animation CSS */
-webkit-animation-duration: 2s;
animation-duration: 2s;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
}
/* Setup CSS3 animations */
@-webkit-keyframes out {
0% {
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
20%, 60% {
-webkit-transform: rotate3d(0, 0, 1, 80deg);
transform: rotate3d(0, 0, 1, 80deg);
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
40%, 80% {
-webkit-transform: rotate3d(0, 0, 1, 60deg);
transform: rotate3d(0, 0, 1, 60deg);
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
opacity: 1;
}
to {
-webkit-transform: translate3d(0, 700px, 0);
transform: translate3d(0, 700px, 0);
opacity: 0;
}
}
@keyframes out {
0% {
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
20%, 60% {
-webkit-transform: rotate3d(0, 0, 1, 80deg);
transform: rotate3d(0, 0, 1, 80deg);
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
}
40%, 80% {
-webkit-transform: rotate3d(0, 0, 1, 60deg);
transform: rotate3d(0, 0, 1, 60deg);
-webkit-transform-origin: top left;
transform-origin: top left;
-webkit-animation-timing-function: ease-in-out;
animation-timing-function: ease-in-out;
opacity: 1;
}
to {
-webkit-transform: translate3d(0, 700px, 0);
transform: translate3d(0, 700px, 0);
opacity: 0;
}
}
.out {
-webkit-animation-name: out;
animation-name: out;
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
<div id="div"></div>
<button id="fadeOut">fadeOut</button>
<button id="css">CSS3</button>
</body>
</html>
回答3:
I went into this same problem a while ago, my workaround was very hacky but mostly works
When you change a, lets say, non transitionable property, like 'display' thing go wrong really fast, i figured that if you use a timmer to change the not transitionable property and a few miliseconds later you change another transitionable property, it kind of works, an you also need to use another timer for turning things back
The HTML
<div class="content_menu_item">
<a href="#">Im a menu item</a>
<ul>
<li>
<a href="#">Sub Item 1</a>
</li>
<li>
<a href="#">Sub Item 2</a>
</li>
<li>
<a href="#">Sub Item 3</a>
</li>
</ul>
</div><div class="content_menu_item">
<a href="#">Im a menu item</a>
<ul>
<li>
<a href="#">Sub Item 1</a>
</li>
<li>
<a href="#">Sub Item 2</a>
</li>
<li>
<a href="#">Sub Item 3</a>
</li>
</ul>
</div>
The CSS
.content_menu_item{
vertical-align: top;
display:inline-block;
width: 140px;
height: 32px;
position:relative;
border:1px solid #b388ff;
text-align: center;
background-color: #6200ea;
}
.content_menu_item a{
line-height: 32px;
display: inline-block;
text-decoration: none;
color:white;
width: 140px;
}
ul{
padding: 0;
list-style:none;
display:none;
margin: 0;
opacity:0.5;
}
.content_menu_item ul li{
color:white;
background: #1e88e5;
line-height: 26px;
vertical-align: top;
transition:all 385ms cubic-bezier(0.895, 0.03, 0.685, 0.22);
opacity:0;
}
.content_menu_item ul li.on{
opacity:1;
}
.content_menu_item ul li.on:nth-child(1){
transition-delay:0ms;
}
.content_menu_item ul li.on:nth-child(2){
transition-delay:50ms;
}
.content_menu_item ul li.on:nth-child(3){
transition-delay:100ms;
}
.content_menu_item ul li.off{
opacity:0;
}
.content_menu_item ul li.off:nth-child(3){
transition-delay:0ms;
}
.content_menu_item ul li.off:nth-child(2){
transition-delay:50ms;
}
.content_menu_item ul li.off:nth-child(1){
transition-delay:100ms;
}
jQuery for handlig the states of the mouse
$('.content_menu_item').hover(
function(){
// mouse over
$(this).find('ul').show(); // show the sub list of the menu, basicaly display block
timmeron = setTimeout(()=>{ // 10 miliseconds later add the class to change the opacity, the on class has a transition-delay for every element usin nth-child
$(this).find('li').addClass('on');
},10);
},function(){
//mouse out
$(this).find('li').removeClass('on'); // remove the on class
$(this).find('li').addClass('off'); // add the off class to invert the transition-delay
timmeroff = setTimeout(()=>{
$(this).find('ul').hide(); // hide the element with time after the transition completes
$(this).find('li').removeClass('off'); // remove both classes
$(this).find('li').removeClass('on');
},500);
})
And here is a working example https://codepen.io/Teobis/pen/QxmqGQ
Hope this helps
回答4:
@rodneyrehm's answer pretty much sums up everything you need when handling animations with css display property.
You need to trigger a reflow after toggling the display property and apply an animation class after it.
// find elements
const banner = $("#banner")
const button = $(".banner__button")
const text = $(".banner__text")
let isVisible = false
// toggle display
button.on("click", () => {
if (!isVisible) {
text.addClass("display--block")
text.outerWidth()
text.addClass("text--show")
isVisible = true
} else {
text.addClass("text--hide")
.one('webkitAnimationEnd oanimationend msAnimationEnd animationend', () => {
text.removeClass("display--block")
text.removeClass("text--show")
text.removeClass("text--hide")
isVisible = false
})
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#banner {
background: #fff;
border-radius: 4px;
padding: 20px;
font-size: 25px;
text-align: center;
margin: 0 auto;
width: 300px;
height: 150px;
display: flex;
flex-flow: column nowrap;
}
.banner__button {
background: #0084ff;
border: none;
border-radius: 5px;
padding: 8px 14px;
font-size: 15px;
color: #fff;
cursor: pointer;
transition: box-shadow 0.3s, transform 0.6s;
}
.banner__button:hover {
box-shadow: 0 3px 8px 2px #9d9d91;
transform: translateY(-2px)
}
.banner__button:focus {
outline: 0;
}
.flex--1 {
flex: 1;
}
.banner__text {
display: none;
opacity: 0;
transition: all 0.6s;
}
.display--block {
display: block;
}
.text--show {
animation: slide-in 1s forwards;
}
.text--hide {
animation: slide-out 1s forwards;
}
@keyframes slide-in {
0% {
opacity: 0;
transform: translateX(-30px)
}
100% {
opacity: 1;
transform: translateX(0px)
}
}
@keyframes slide-out {
0% {
opacity: 1;
transform: translateX(0px)
}
100% {
opacity: 0;
transform: translateX(30px)
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="banner">
<div class="flex--1">
<p class="banner__text">Hello World</p>
</div>
<button class="banner__button">Toggle Text</button>
</div>
来源:https://stackoverflow.com/questions/27114537/how-to-transition-child-when-parent-goes-from-display-none-to-block