How to transition child when parent goes from disp

2020-07-03 05:02发布

问题:

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 with wrapperElement.offsetHeight;), then add a class that sets opacity: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 transitions transform: 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 proper transition 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>