Subscribe on changes!

Transition component does not work with “visibility”

avatar
Oct 30th 2020

Version

3.0.2

Reproduction link

https://codesandbox.io/s/competent-hermann-b1s5q

Steps to reproduce

see https://codesandbox.io/s/competent-hermann-b1s5q

What is expected?

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.

avatar
Oct 31st 2020

It's very easy to add a modifier like v-show.visibility, if it's OK to have such a modifier, I would like to send a PR.

avatar
Oct 31st 2020

For the benefit of anyone else reading this, there is a corresponding Stack Overflow question here:

https://stackoverflow.com/questions/64615373/how-to-make-transition-work-with-visibility-but-not-display

I believe this is really a feature request rather than a bug report. Currently it is possible to hide an element in a transition-safe way using v-show. However, that forces the hiding to be via display: none. In cases where you want to hide an element by other means, for example visibility, you currently need to recreate the logic in v-show. It would be nice to have a way to reuse the transition logic in v-show without having to copy/paste/maintain it in a totally separate directive.

avatar
Oct 31st 2020

Thanks @skirtles-code ! Really helpful solution on the Stack Overflow.

It would be nice to have a way to reuse the transition logic in v-show without having to copy/paste/maintain it in a totally separate directive.

Yeah, absolutely right. Or may be it could be a strategy function something like this.

<template>
    <div v-show:strategy="setDisplay">foobar</div>
</template>

<script lang="ts">
  interface ElementDisplay {
    new(el: HTMLElement): void
    show()
    hide()
  }

  class VisibilityStrategy implements ElementDisplay {
    private el: HTMLElement;

    constructor(el: HTMLElement) {
      this.el = el
    }

    show() {
      this.el.style.visibility = 'visible'
      this.el.style.opacity = '1'
      this.el.classList.remove('is-hidden')
    }

    hide() {
      this.el.style.visibility = 'hidden'
      this.el.style.opacity = '0'
      this.el.classList.add('is-hidden')
    }
  }


  export default {
    data() {
      return {
        setDisplay: VisibilityStrategy
      }
    }
  }
</script>

then this function can be replaced by the ElementDisplay instance.

But a simple v-show.visibility modifier is enough to handle most of the use cases I think.

avatar
Nov 11th 2020

I would like to implement it, make transition component can effect on visibility.

avatar
Nov 11th 2020

I find that this issue should be added a bug label @LinusBorg , here is my analysis:

Suppose the user-defined direcitve looks like following(similar to v-show), pay attention to the comments(step 1 & step 2) and now let's look at the classes change.

const visibility = {
  beforeMount(el, { value }, { transition }) {
    el._vod = el.style.visibility === '' ? 'hidden' : el.style.visibility
    if (transition && value) {
      transition.beforeEnter(el)
    } else {
      el.style.visibility = value || 'hidden'
    }
  },
  updated(el, { value, oldValue }, { transition }) {
    if (!value === !oldValue) {
      return
    }

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

The following code is part of Transition.ts.

// step 1: transition.beforeEnter
onBeforeEnter(el) {
  onBeforeEnter && onBeforeEnter(el)
  // add *-enter-active
  addTransitionClass(el, enterActiveClass)
  // add *-enter-from
  addTransitionClass(el, enterFromClass)
}
// step 2: transition.enter
const makeEnterHook = (isAppear: boolean) => {
  return (el: Element, done: () => void) => {
    const hook = isAppear ? onAppear : onEnter
    const resolve = () => finishEnter(el, isAppear, done)
    hook && hook(el, resolve)
    nextFrame(() => {
      // remove *-enter-from
      removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass)
      // add *-enter-to
      addTransitionClass(el, isAppear ? appearToClass : enterToClass)
      if (!(hook && hook.length > 1)) {
        if (enterDuration) {
          setTimeout(resolve, enterDuration)
        } else {
          whenTransitionEnds(el, type, resolve)
        }
      }
    })
  }
}
// step 3: transition end
const finishEnter = (el: Element, isAppear: boolean, done?: () => void) => {
  // remove *-enter-to
  removeTransitionClass(el, isAppear ? appearToClass : enterToClass)
  // remove *-enter-active
  removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
  done && done()
}

We can see classes change processes:

  1. When we use v-show it's ok, because the DOM was inserted after add *-enter-active & *-enter-from, so that the DOM initial status is *-enter-from image
  2. When we use v-visibility , the initial status is certain (status a) and status was changed when we add *-enter-active & *-enter-from (status b), but now DOM initial status is not *-enter-from . image

Wait for confirmation and i can fix it.

avatar
Nov 11th 2020

Disclaimer: I currently don't have a complete understanding of the transition mechanics in play here.

That being said, I don't see why I should label this a bug when so far, we clearly only support v-if and v-show.

The fact that you were not able to implement a custom directive with the existing internal APIs does not make an unsupported feature a bug.

This still is, essentially, a feature request.

avatar
Nov 12th 2020

OK, i see your point and it makes sense.