3
votes

How to read dimensions and move a div that is hidden before Vue transition starts? For example, a user clicks a button and I want to move a hidden div to appear under the button with a fade-in transition. I need to be able to both read the dimensions and move the top/left position of the hidden div before the transition starts.

Let's say I'm using v-show="active" on the div, where active is my reactive data property I want to set to true and be able to move the div before transition starts.

I've tried all these:

  • Move the div first, then on nextTick set active = true.
  • Use the javascript hook beforeEnter to try to move the div before transitions start.
  • Use the javascript hook enter (and 'done' callback) to try to move the div before transition starts.
  • Tried all the above with updating the DOM immediately with the new position before setting active = true. (In other words, not via data binding, but actually setting element style properties directly like this.$refs.content.style.top = '500px' to avoid any waiting on the virtual DOM.) However, ideally I would like to accomplish this without directly touching the DOM, but using nextTicks instead. Both approaches fail.
  • Tried with some success with a hacky transition: all .8ms ease-in, top 1ms, left 1ms.
  • Tried with success with moving the div first then setting active in a setTimeout. This is not the right solution though.

Update
Thanks to the accepted answer I was able to see that I can read dimensions on nextTick (by which time v-show has turned on display). However, it turns out I needed the transition to be all transition all .3s and that would cause the movement to be included. The DOM will gather up all the changes and apply them together, which means they get lumped into the transition that is later added by Vue. The solution ended up being that I needed to make the movements, then trigger the DOM to repaint first, then trigger the v-show to turn on. Here's an example method:

startTransition () {
  this.$refs.content.offsetHeight // <-- Force DOM to repaint first.
  this.isContentActive = true     // <-- Turns on v-show.
},
1

1 Answers

2
votes

Use v-bind:style to move your window and it all works as intended.

Update: To check the size of the popup itself, it has to be shown, so I'm using v-show instead of v-if. The first thing I do is make it visible; on the next tick, I can measure it and place it.

new Vue({
  el: '.container',
  data: {
    top: 0,
    left: 0,
    width: 0,
    show: false
  },
  methods: {
    showFloater: function(evt) {
      const t = evt.target;

      this.show = true;
      Vue.nextTick(() => {
        const fEl = this.$el.querySelector('.floating');

        this.top = t.offsetTop + 30;
        this.left = t.offsetLeft;
        this.width = fEl.offsetWidth;
        setTimeout(() => this.show = false, 1000);
      });
    }
  }
});
.container {
  position: relative;
}

.floating {
  border: thin solid black;
  padding: 3em;
  position: absolute;
}

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s
}
.fade-enter, .fade-leave-to /* .fade-leave-active in <2.1.8 */ {
  opacity: 0
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div class="container">
  <button @click="showFloater">Could go here</button>
  <button @click="showFloater">Or here</button>
  <transition name="fade">
    <div v-show="show" class="floating" v-bind:style="{
      top: top + 'px',
      left: left + 'px'
    }">
      This window is {{width}}px wide.
    </div>
  </transition>
</div>