vueuse / motion

🤹 Vue Composables putting your components in motion
https://motion.vueuse.org
MIT License
2.38k stars 83 forks source link

Using transition @leave #83

Open plexus77 opened 2 years ago

plexus77 commented 2 years ago

I am trying to implement a leave transition using the transition element like in your demo so that I can show and hide an element with nice transitions

I'm using Nuxt 3.0.0-rc.10 and @vueuse/motion 2.0.0-beta.22

Here is a simple example below.

<template>
  <div class="p-8">
    <div class="flex">
      <button class="px-2 py-4 mr-4 bg-blue-500 text-white" @click="toggle=true">
        Show
      </button>
      <button class="px-2 py-4 bg-blue-500 text-white" @click="toggle=false">
        Hide
      </button>
      <div>{{ toggle }}</div>
    </div>

    <transition
        :css="false"
        @leave="(_, done) => motions.transition.leave(done)"
    >
      <div
          v-if="toggle"

          v-motion="'transition'"
          :initial="{ opacity: 0, y: -200 }"
          :enter="{ opacity: 1, y: 0 }"
          :leave="{ opacity: 0, y: -200 }"

          style="width: 200px; height: 200px"
          class="m-6 bg-green-900"
      >
      </div>
    </transition>
  </div>
</template>
<script setup>
import { useMotions } from '@vueuse/motion'

const toggle = useState('toggle', () => false)

definePageMeta({
  layout: 'empty'
});

const motions = useMotions()
</script>

If I remove the element then it works as expected with the div animating in when I click show and then disappearing instantly by clicking hide.

I can't see what I am doing wrong here, if there is a bug or perhaps incompatability with the version I am using.

plexus77 commented 2 years ago

So I found a better way to achieve the animation without using the transition component.

<template>
  <div class="p-8">
    <div class="flex">
      <button class="px-2 py-4 mr-4 bg-blue-500 text-white" @click="toggle">
        Toggle
      </button>
      <div>{{ show }}</div>
    </div>
    <div
        v-if="show"

        v-motion="'transition'"
        :initial="{ opacity: 0, y: -200 }"
        :enter="{ opacity: 1, y: 0, transition: { duration: 500 } }"
        :leave="{ opacity: 0, y: -200, transition: { duration: 500 } }"

        style="width: 200px; height: 200px"
        class="m-6 bg-green-900"
    >
    </div>
  </div>
</template>
<script setup>
import { useMotions } from '@vueuse/motion'

definePageMeta({
  layout: 'empty'
});

const motions = useMotions()
const show = useState('show', () => false)
const toggle = () => {
  if (show.value) {
    motions.transition.leave(() => { show.value=false })
  } else {
    show.value=true
  }
}
</script>
SkyleLai commented 2 years ago

I had same problem that motions.transition.leave doesn't work onLeave at transition component.

I create a component "Motionable" as a workaround based on your comment:

Motionable.vue

<template>
  <component :is="is" v-if="show || !leaved" v-motion="name">
    <slot />
  </component>
</template>

<script setup>
import { useMotions } from '@vueuse/motion'
const props = defineProps({
  is: { type: [String, Object], default: 'div' },
  name: { type: String, required: true },
  show: { type: Boolean, default: true },
})
const motions = useMotions()
const leaved = ref(!props.show)
watch(
  () => props.show,
  async (newShow) => {
    const motion = motions[props.name]
    if (motion && motion.isAnimating.value) {
      motion.stop('leave')
      if (newShow) {
        motion.apply('enter')
      }
    }
    if (!newShow) {
      leaved.value = false
      motion.leave(() => {
        leaved.value = true
      })
    }
  }
)
</script>

usage

<Motionable
  is="div"
  name="transition1"
  :show="show"
  :initial="{
    y: 300,
    opacity: 0,
  }"
  :enter="{
    y: 0,
    opacity: 1,
  }"
  :leave="{
    y: 300,
    opacity: 0,
  }"
>
  hello, world
</Motionable>
productdevbook commented 2 years ago

yes same problem, nuxt 3

<script setup lang="ts">
import { useMotions } from '@vueuse/motion'
import { Teleport, toRefs } from 'vue'
const props = defineProps({
  visible: { type: Boolean, default: false },
})
const emit = defineEmits(['update:visible', 'close'])
const { visible } = toRefs(props)

const leaveTransition = async () => {
  const { windowTransition, overlayTransition } = useMotions()
  await Promise.all([
    overlayTransition.apply('leave'),
    windowTransition.apply('leave'),
  ])
}
const close = async () => {
  await leaveTransition()
  emit('close')
  emit('update:visible', false)
}
</script>

<template>
  <Teleport to="#dialog-outlet">
    <div v-if="visible" class="fixed z-[99999] h-screen w-screen">
      <div
        v-motion="'overlayTransition'"
        :initial="{
          opacity: 0,
        }"
        :enter="{
          opacity: 1,
        }"
        :leave="{ opacity: 0 }"
        class="fixed inset-0 bg-gray-500 bg-opacity-10 backdrop-blur-[3px] transition-opacity"
      />

      <div class="fixed inset-0 z-10 overflow-y-auto">
        <div
          class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0"
        >
          <div
            v-motion="'windowTransition'"
            :initial="{
              opacity: 0,
              scale: 0,
            }"
            :enter="{
              opacity: 1,
              scale: 1,
              transition: {
                type: 'keyframes',
                duration: 50,
              },
            }"
            :leave="{
              scale: 0.5,
              opacity: 0,
              transition: {
                duration: 100,
                type: 'keyframes',
                ease: 'easeInOut',
              },
            }"
            :delay="100"
            class="relative w-full transform overflow-hidden rounded-lg bg-white dark:bg-dark-400 px-4 pt-5 pb-4 text-left shadow-xl transition-all sm:my-8 sm:w-full sm:max-w-sm sm:p-6"
          >
            <div>
              <div class="text-left">
                <h3 id="modal-title" class="text-lg font-medium leading-6">
                  Change Color Site
                </h3>
                <slot />
              </div>
            </div>
            <div class="mt-5 sm:mt-6">
              <button
                type="button"
                class="inline-flex w-full justify-center rounded-md border border-transparent bg-red-600 px-4 py-2 text-base font-medium text-white shadow-sm hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 sm:text-sm"
                @click="close"
              >
                Go back to
              </button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </Teleport>
</template>
bernhardberger commented 2 months ago

can confirm.. (nuxt 3.13.2)