7

Sorry all for so many updates in this question and I'm glad for everyone trying to help me. I think below there is a more clear way to understand this problem:

CSS transitions will not apply if you set the display property of an element to block immediately before changing the property with the transition attached. Consider the following code:

CSS:

#creative {
    display: none;
    opacity: 0;
    transition: opacity 0.5s; 
}

HTML:

<div id="creative">
    <span>Sample text</span>
</div>

Javascript:

var dom = {};
dom.creative = document.getElementById('creative');
dom.creative.style.display = 'block';
dom.creative.style.opacity = 1;

The element will properly show, but without any transition. A workaround for this problem is to force a repaint by accessing one of the visual properties of the element:

dom.creative.style.display = 'block';
var a = dom.creative.offsetHeight; /* <-- forces repaint */
dom.creative.style.opacity = 1;

is there a good way around this? and by good i mean not adding a extra line of javascript code everytime i need to change the display property and a property with a transition attached.

incompleteness
  • 260
  • 1
  • 12
  • 1
    Very good question! I am curious about this too, and will dig to see what I find... – trusktr Jul 05 '16 at 17:36
  • 2
    transition works where numbers are involved ... it goes step by steps, but not with states like none/block where ... you actually have a single step :) (beside how to apply opacity on display:none element ) – G-Cyrillus Jul 05 '16 at 17:38
  • Consider changing your question to define objective criteria of what you expect to see in an answer. Right now it appears you're asking for opinions ("best" "more elegant"), which are generally off-topic. – Heretic Monkey Jul 05 '16 at 17:47
  • 1
    It seems like only hacks exist: http://stackoverflow.com/questions/8840580, http://stackoverflow.com/questions/3485365, and comprehensive list by Paul Irish: https://gist.github.com/paulirish/5d52fb081b3570c81e3a – trusktr Jul 05 '16 at 17:49
  • Do you want to repaint or do you want a transition/animation to be re-executed? – Asons Jul 05 '16 at 18:18
  • @LGSon english is not my first language so i'm sorry for not being clear. I'm looking for a good way to trigger a repaint after setting the display to block and before setting the opacity to 1. – incompleteness Jul 05 '16 at 19:53
  • Not mine either (English) ... are we talking about the transition or just to repaint an element of any kind? – Asons Jul 05 '16 at 19:56
  • The repaint is needed because after the display is set to block the transition will not apply without a repaint. – incompleteness Jul 05 '16 at 19:57

4 Answers4

2

Based on the code you present in your question I'm going on a completely different way here, and use animation instead, which will make the whole repaint issue go away

Updated with a script the set the div to display: block

var dom = {};
dom.creative = document.getElementById('creative');
dom.creative.style.display = 'none';

var butt = document.getElementById('button');
butt.addEventListener('click', function(e) {
  
  if (dom.creative.style.display == 'block') {
    dom.creative.style.display = 'none';    
  } else {
    dom.creative.style.display = 'block';
  }

})
#creative {
  display: none;
  opacity: 0;
  animation: opac 1s forwards;
  margin: 20px;
}
@keyframes opac {
  100% {
    opacity: 1;
  }
}
button {
  margin: 20px;
}
<button id="button">Toggle display</button>

<div id="creative">
  <span>Sample text</span>
</div>

If display: none is not needed, one can use transition and simply toggle a class like this

var dom = {};
dom.creative = document.getElementById('creative');

var butt = document.getElementById('button');
butt.addEventListener('click', function(e) {
  
  dom.creative.classList.toggle('show');

})
#creative {
  opacity: 0;
  transition: opacity 1s;
  margin: 20px;
}
#creative.show {
  opacity: 1;
  transition: opacity 1s;
}
button {
  margin: 20px;
}
<button id="button">Toggle display</button>

<div id="creative">
  <span>Sample text</span>
</div>

For transition, besides the offsetHeight and the setTimeout solution, there is a 3:rd, having the same visual effect as toggle display block/none, setting the height/width to 0.

var dom = {};
dom.creative = document.getElementById('creative');

var butt = document.getElementById('button');
butt.addEventListener('click', function(e) {
  
  dom.creative.classList.toggle('show');

})
#creative {
  width: 0;
  height: 0;
  opacity: 0;
  transition: opacity 1s, width 0s 1s;
  margin: 20px 0;
}
#creative.show {
  width: 100%;
  height: auto;
  opacity: 1;
  transition: opacity 1s, width 0s;
}
button {
  margin: 20px 0;
}
<button id="button">Toggle display</button>

<div id="creative">
  <span>Sample text</span>
</div>

<div>Other content</div>
Asons
  • 84,923
  • 12
  • 110
  • 165
  • 1
    @incompleteness Not needed ... or why you need that? – Asons Jul 05 '16 at 19:41
  • Because I do. Thank you. – incompleteness Jul 05 '16 at 19:45
  • @incompleteness Could you please explain why so I get the right facts for how to answer your question properly – Asons Jul 05 '16 at 19:51
  • 2
    I don't know the real reason OP needs it, but I can imagine quite a few circumstances where you'd want `display:none`, for example if you don't want `.creative` to push down content after it until it fades in. Or what if `.creative` is actually an overlay? Just having it transparent the whole time won't be enough – mmgross Jul 05 '16 at 20:12
  • The real reason I need it: setting the display property to block and some animated property to something else is a very common scenario that, as far I know, requires a extra useless (and forgettable) line of javascript code to work properly. – incompleteness Jul 05 '16 at 20:37
  • @incompleteness Updated my answer – Asons Jul 05 '16 at 20:42
  • That is it. You got it. Thank you. Sorry for the mistakes. – incompleteness Jul 05 '16 at 20:44
  • @incompleteness No need to be sorry :) – Asons Jul 05 '16 at 20:46
  • @LGSon now i'm just being curious, do you have any ideas about how to solve this using the transition instead of the animation? – incompleteness Jul 05 '16 at 20:58
  • @incompleteness They way you asked, to force a repaint... (because of the `display: none` ) ... without, just toggle a class on/off having `opacity: 1; transition: opacity 1s` – Asons Jul 05 '16 at 21:17
  • @incompleteness Updated my answer with a sample toggling a class – Asons Jul 05 '16 at 21:25
  • @LGSon http://stackoverflow.com/questions/38213118/workaround-for-display-block-and-css-transitions-work-properly – incompleteness Jul 05 '16 at 21:38
1

There are no transitions defined for absolute properties, like display. How do you interpolate between none and block? You can't. But you can create some post-functions that will run after the animation is done.

Using setTimeout

You can use setTimeout and execute some code after the animation is over:

ANIMATION_TIME = 0.5 * 1000; // in ms

function show(element) {
  // Display property must be set inmediatly; otherwise, the 'show' animation won't be visible until it ends.
  element.style.display = 'block';
  element.opacity = 1;
}

function hide(element) {
  element.opacity = 0;

  setTimeout(function() {
    element.style.display = 'none';
  }, ANIMATION_TIME);
}

// Call examples
var dom = {};
dom.creative = document.getElementById('creative');
show(dom.creative);
hide(dom.creative);

Using animation events

As @trustk has pointed out, you can also (and preferably) use DOM events:

function show(element) {
  element.style.display = 'block';
  element.opacity = 1;
  element.removeEventListener("animationend", afterHide, false);
}

function afterHide(e) {
  // e.target -> element
  e.target.display = 'none';
}

function hide(element) {
  element.opacity = 0;
  element.addEventListener("animationend", afterHide, false);
}
Wikiti
  • 1,626
  • 13
  • 21
  • But when you call `show()` the display will be none, so we won't see it? – trusktr Jul 05 '16 at 17:54
  • @trusktr You're right. Probably, the `display` property should be set without timeout. – Wikiti Jul 05 '16 at 18:02
  • For sure. There's also animation events that are more accurate than a timeout: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Animations/Using_CSS_animations#Using_animation_events – trusktr Jul 05 '16 at 18:04
  • 1
    You're welcome. :] Additionally, when you set `display = 'none'`, on the following line I think you'd need `var a = element.offsetHeight` in order to repaint the element or the opacity transition won't work (that's what the issue OP was having, see his example). Haven't tested your code though. – trusktr Jul 05 '16 at 20:19
0

If you just add a timeout to the opacity call it should work! The duration can be almost nothing, as long as the display and opacity properties don't get called at the same time.

var dom = {};
dom.creative = document.getElementById('creative');
dom.creative.style.display = 'block';
setTimeout(function(){
  dom.creative.style.opacity = 1;
},1);

You can also use a jQuery fadein, if jQuery is an option. Then you can leave out the opacity altogether and just call it like...

dom.creative.fadeIn(500)
will
  • 1,947
  • 1
  • 13
  • 19
  • This may work, but it is so hacky and unintuitive. I wish browsers had some better way... – trusktr Jul 05 '16 at 17:50
  • Just updated the answer - there's a jQuery function, fadeIn, that handles interpolating between none and block. – will Jul 05 '16 at 17:52
  • I'm not using jQuery and i don't think this solution is useful because, like trusktr said, it is unintuitive. – incompleteness Jul 05 '16 at 19:36
0

You can show / hide element without javascript using css transition only.

<!--html-->
<div id="parent">Some parent text
    <div id="creative">Some details</div>
</div>

/*css*/
#parent #creative{
   opacity:0;
   visibility:hidden;
   clip: rect(0px,0px,0px,0px);
   transition: all 0.5s;
}
#parent:hover #creative{/*trigger transition*/
   opacity:1;
   visibility:visible;
   clip: rect(0px,220px,0px,330px);
}
Alex Kudryashev
  • 9,120
  • 3
  • 27
  • 36