2

I'm working on a "slide toggle" transition to get functionality similar to jQuery's .slideToggle() method. I have it working but there's one line needed to make it work and I'm not sure why. When I remove var neededForSomeReason = el.offsetHeight; from the enter() and leave() functions, there is no animation, the div just snaps open and closed.

Why is that line needed for the transition to work? And is there a way to make the transition work without it?

new Vue({
  el: '#app',
  data: {
    height: 0,
    open: false,
  },
  methods: {
    toggle() {
      this.open = !this.open;
    },
    beforeEnter: function(el) {
      el.style.height = 'auto';
      el.style.display = 'block';
      this.height = el.offsetHeight;
      el.style.height = '0px';
    },
    enter: function(el) {
      // If this line is removed the transition doesn't work.
      var removeToBreak = el.offsetHeight;
      el.style.height = this.height + 'px';
    },
    afterEnter: function(el) {
      el.style.height = 'auto';
    },
    beforeLeave: function(el) {
      el.style.height = el.offsetHeight + 'px';
    },
    leave: function(el) {
      // If this line is removed the transition doesn't work.
      var removeToBreak = el.offsetHeight;
      el.style.height = '0px';
    },
  },
})
.transition-height {
  transition: height 250ms;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/0.5.3/tailwind.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.min.js"></script>

<div id="app" class="p-4 bg-grey-light min-h-screen">
  <div class="bg-white rounded-sm shadow overflow-hidden">
    <div @click.prevent="toggle" class="px-8 py-4 bg-blue text-white">
      Click to Toggle
    </div>
    <transition 
      v-on:before-enter="beforeEnter" 
      v-on:enter="enter" 
      v-on:after-enter="afterEnter" 
      v-on:before-leave="beforeLeave" 
      v-on:leave="leave"
    >
      <div v-show="open" class="transition-height">
        <div class="p-8 leading-normal text-grey-darker">
          Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut quis orci vitae turpis laoreet mattis. Morbi quis viverra orci. Sed vitae tincidunt nisl. Phasellus lobortis nisi mauris, non semper neque maximus at. Integer neque enim, tristique a dui sed, ultrices eleifend dolor. Mauris non tristique leo. Ut erat quam, feugiat eget venenatis eu, euismod et tortor.
        </div>
      </div>
    </transition>
  </div>
</div>

2
  • Try doing console.log(el);console.log(el.offsetHeight);console.log(el); in place of the two breaking assignment operations. What you'll likely notice is that the offsetHeight property on the two el calls remains the same, and that the el.offsetHeight call differs. If the offsetHeight is 160 when open, you'll see 160 when opening on el but 0 on el.offsetHeight, and when closing you'll see 0 on el and 160 on el.offsetHeight. Commented May 11, 2018 at 19:47
  • In other words, what we see on the element is the opposite of what is calculated when accessing the offset height directly. This is odd behavior, but it may be that accessing the offsetHeight property directly forces Vue to perform a calculation that is ordinarily deferred until the node is rendered, allowing your transition methods to pick up on the change earlier. As for why it requires seeing the offsetHeight change in order to work properly, I don't have an answer. Hopefully this gives you a direction to look into, though :) Commented May 11, 2018 at 19:49

1 Answer 1

1

This probably has to do with the way the browser batches a set of simultaneous style changes.

For example:

el.style.height = '10px'
el.style.height = '20px'
el.style.height = '30px'

The browser does not perform a layout immediately after every style change (for performance reasons), instead it will batch them all together and perform a single layout at a later time.

You can force the browser to perform a layout by accessing offsetHeight of that element. See What forces layout / reflow.

So your example is essentially this:

el.style.height = 'auto'
height = el.offsetHeight  // Recalculate el height (auto)
el.style.height = '0px'
el.offsetHeight           // Recalculate el height (0px)
el.style.height = height

Without the el.offsetHeight line, the browser only sees a difference in the computed height value of auto to a known length value. Transitions to/from auto will not be performed.

If you force a layout with el.offsetHeight, then the computed height value goes from 0px to a known length value, which is a valid transition.

Reading the CSS transitions spec might provide some insight:

Since this specification does not define when a style change event occurs, and thus what changes to computed values are considered simultaneous, authors should be aware that changing any of the transition properties a small amount of time after making a change that might transition can result in behavior that varies between implementations, since the changes might be considered simultaneous in some implementations but not others.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.