yaquawa
yaquawa

Reputation: 7338

How to make transition work with "visibility" but not "display"?

The transition element of vue only works with display:none but not visibility:hidden, is there any way to make it work with visibility? I want to get the clientWidth of the element before it shows up, with display:none I can't get that value.

By the way I'm using vue3.

Here is the reproduction demo: https://codesandbox.io/s/competent-hermann-b1s5q

Upvotes: 1

Views: 5448

Answers (2)

skirtle
skirtle

Reputation: 29102

I'm going to assume, for the sake of argument, that you genuinely do need to use visibility for hiding and that other potential solutions (such as opacity) won't work in your real use case, possibly because they don't prevent user interactions with the element.

However, the assertion in the question is slightly misleading. It isn't really a difference between display and visibility. The real difference here is that the display case is using v-show, which includes special handling for transitions.

The current source code for v-show can be seen here:

https://github.com/vuejs/vue-next/blob/d7beea015bdb208d89a2352a5d43cc1913f87337/packages/runtime-dom/src/directives/vShow.ts

A similar approach can be used to construct a directive that uses visibility. Below is an example. It is based on the code for v-show but I've cut it back to just the code required for this particular use case:

const visible = {
  updated(el, { value, oldValue }, { transition }) {
    if (!value === !oldValue) {
      return
    }

    if (value) {
      transition.beforeEnter(el)
      el.style.visibility = ''
      transition.enter(el)
    } else {
      transition.leave(el, () => {
        el.style.visibility = 'hidden'
      })
    }
  }
}

Vue.createApp({
  data() {
    return {
      show: true
    };
  },
  methods: {
    toggle() {
      this.show = !this.show;
    }
  },
  directives: {
    visible
  }
}).mount('#app')
#app {
  text-align: center;
}

.tooltip-enter-active {
  transition: transform 0.4s ease-out, opacity 0.3s ease-out;
}

.tooltip-leave-active {
  transition: transform 0.35s ease-in, opacity 0.28s ease-out;
}

.tooltip-enter-from {
  transition: none;
}

.tooltip-enter-from,
.tooltip-leave-to {
  transform: translateY(-30px) scale(0.96);
  opacity: 0;
}
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
<div id="app">
  <transition name="tooltip">
    <div v-visible="show">
      Using visibility
    </div>
  </transition>
  <button @click="toggle">toggle message</button>
</div>

I did also have to make a small CSS change to give the enter transition a kick:

.tooltip-enter-from {
  transition: none;
}

Upvotes: 3

Dan Knights
Dan Knights

Reputation: 8368

You'd probably be better off without <transition> in this case:

const app = Vue.createApp({
  data() {
    return {
      show: true,
    };
  },
  methods: {
    toggle() {
      const tooltip = this.$refs.tooltip;

      this.show = !this.show;
      tooltip.classList.toggle("tooltip-show");
    },
  },
  mounted() {
    console.log('Tooltip-width: ', this.$refs.tooltip.clientWidth);
  },
});

app.mount('#app')
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

.tooltip {
  opacity: 0;
  transform: translateY(-30px) scale(0.96);
  transition: transform 0.35s, opacity 0.25s;
}

.tooltip-show {
  opacity: 1;
  transform: translateY(0) scale(1);
}
<script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>

<div id="app">
  <div class="tooltip" ref="tooltip">This will work!</div>
  <button @click="toggle">toggle tooltip</button>
</div>

Upvotes: 0

Related Questions