Search code examples
vue.jsvuejs2vuejs3css-animationsvue-transitions

Transition animations not working when migrating project from Vue2 to Vue3


I have a project that is on Vue2.7 and am porting that over to Vue3 and vite. I pretty much copied over the exact code because of compositionAPI support in 2.7. Everything renders normally, but now all of the sudden my transition animations are not longer the smooth dropdown they were before and even the hover sizing is very strange.

I've been trying to find what is going wrong here, but I havent really found any discrepencies between what I wrote and breaks caused by Vue3.

If anyone has any ideas as to why these animations and a majority of my styling doesnt work please let me know!

Cheers

I've made a CodeSandbox to edit everything.

Dropdown.vue

<template>
  <div>
    <div
      v-if="kind === 'products'"
      class="_custom-select"
      @mouseenter="open = true"
      @mouseleave="open = false"
    >
      <div
        style="display: flex"
        :class="[open ? open : '', '_dropdown-title']"
        @click="open = !open"
      >
        <p class="_title">Products</p>
        <svg
          :class="[open ? '_rotate-180' : '']"
          style="width: 13px; margin-left: 6px"
          xmlns="http://www.w3.org/2000/svg"
          width="11.677"
          height="6.676"
          viewBox="0 0 11.677 6.676"
        >
          <path
            d="M4.663,5.837.245,1.422A.835.835,0,0,1,1.427.244l5.006,5A.833.833,0,0,1,6.457,6.4L1.43,11.434A.835.835,0,0,1,.249,10.255Z"
            transform="translate(11.677) rotate(90)"
            fill="#7897fd"
          />
        </svg>
      </div>
      <Transition name="slide">
        <div v-if="open" :class="[sidebar ? '_transparent' : '', '_items']">
          <a href="#" class="_product-icon"> Option2 </a>
          <a href="#"> Option1 </a>
        </div>
      </Transition>
    </div>
    <div
      v-if="kind === 'resources'"
      class="_custom-select"
      @mouseenter="open = true"
      @mouseleave="open = false"
    >
      <div
        style="display: flex"
        :class="[open ? open : '', '_dropdown-title']"
        @click="open = !open"
      >
        <p class="_title">Resources</p>
        <svg
          :class="[open ? '_rotate-180' : '']"
          style="width: 13px; margin-left: 6px"
          xmlns="http://www.w3.org/2000/svg"
          width="11.677"
          height="6.676"
          viewBox="0 0 11.677 6.676"
        >
          <path
            d="M4.663,5.837.245,1.422A.835.835,0,0,1,1.427.244l5.006,5A.833.833,0,0,1,6.457,6.4L1.43,11.434A.835.835,0,0,1,.249,10.255Z"
            transform="translate(11.677) rotate(90)"
            fill="#7897fd"
          />
        </svg>
      </div>
      <Transition name="slide">
        <div v-if="open" :class="[sidebar ? '_transparent' : '', '_items']">
          <a href="https://google.com/" target="_blank">E-Learning</a>
          <a href="#">FAQ</a>
        </div>
      </Transition>
    </div>
  </div>
</template>

<script>
import { defineComponent, ref } from "vue";
export default defineComponent({
  props: {
    /**
     * @example
     * "products" | "resources"
     */
    kind: { type: String, default: "products" },
    sidebar: { type: Boolean, default: false },
  },
  setup(props) {
    const open = ref(false);
    return {
      open,
    };
  },
});
</script>

<style lang="sass" scoped>
p
  all: unset
._custom-select
  position: relative
  text-align: left
  outline: none
  font-size: 1rem
  text-decoration: none

._dropdown-title
  display: flex
  justify-content: center
  align-items: center
  // font-size: 20px
  font-weight: bold
  color: #fff
  padding: 0.25em 2em
  cursor: pointer
  user-select: none

._items
  position: absolute
  background-color: #0D1F5D
  left: 0
  right: 0
  top: 100%
  display: flex
  flex-direction: column
  justify-content: center
  align-items: center
  color: #fff
  z-index: 1

  & a
    color: #fff
    text-decoration: none
    width: 100%
    // font-size: 1.25rem
    font-weight: bold
    text-align: center
    padding: 0.75em 1em
    cursor: pointer
    user-select: none
    &:hover
      background-color: #2A3A72
._transparent
  background-color: transparent
._product-icon
  width: 100%
  // height: 27px

._rotate-180
  transform: rotate(180deg)
  transition: 0.3s linear

.slide-enter-active
  -moz-transition-duration: 0.3s
  -webkit-transition-duration: 0.3s
  -o-transition-duration: 0.3s
  transition-duration: 0.3s
  -moz-transition-timing-function: ease-in-out
  -webkit-transition-timing-function: ease-in-out
  -o-transition-timing-function: ease-in-out
  transition-timing-function: ease-in-out

.slide-leave-active
  -moz-transition-duration: 0.3s
  -webkit-transition-duration: 0.3s
  -o-transition-duration: 0.3s
  transition-duration: 0.3s
  -moz-transition-timing-function: cubic-bezier(0, 1, 0.5, 1)
  -webkit-transition-timing-function: cubic-bezier(0, 1, 0.5, 1)
  -o-transition-timing-function: cubic-bezier(0, 1, 0.5, 1)
  transition-timing-function: cubic-bezier(0, 1, 0.5, 1)

.slide-enter-to,
.slide-leave
  max-height: 100px
  overflow: hidden

.slide-enter,
.slide-leave-to
  overflow: hidden
  max-height: 0
</style>

Navbar.vue

<script>
import { defineComponent, ref, onMounted } from "vue";
import DropdownMenu from "./DropdownMenu.vue";
import HamburgerMenu from "./HamburgerMenu.vue";

export default defineComponent({
  components: { DropdownMenu, HamburgerMenu },
  setup() {
    // const scrolledNav = ref(null)
    const mobile = ref(false);
    const mobileNav = ref(false);
    const windowWidth = ref(null);
    function toggleMobileNav() {
      mobileNav.value = !mobileNav.value;
    }
    function checkScreen() {
      windowWidth.value = window.innerWidth;
      if (windowWidth.value <= 850) {
        mobile.value = true;
        return;
      }
      mobile.value = false;
      mobileNav.value = false;
      return;
    }
    onMounted(() => {
      window.addEventListener("resize", checkScreen);
      checkScreen();
    });
    function contactClicked() {
      console.log("contact clicked");
    }
    return {
      mobile,
      mobileNav,
      windowWidth,
      toggleMobileNav,
      checkScreen,
      contactClicked,
    };
  },
});
</script>

<template>
  <div class="_nav">
    <nav>
      <div class="_nav-header">
        <a href="/"> IMAGE </a>
        <div class="_icon">
          <HamburgerMenu
            :open="mobileNav"
            @click="toggleMobileNav"
            v-show="mobile"
          />
        </div>
        <div v-show="!mobile" class="_nav-inner">
          <DropdownMenu kind="products" />
          <DropdownMenu kind="resources" />
          <a href="about" class="_about">About Us</a>
        </div>
        <div v-show="!mobile" class="_buttons">
          <a href="/contact" class="_contact-button" @click="contactClicked">
            Contact Us</a
          >
          <a href="signIn" class="_sign-in-button"> Sign In</a>
        </div>
      </div>
      <div class="_sidebar">
        <div
          class="_sidebar-backdrop"
          @click="mobileNav = false"
          v-if="mobileNav"
        ></div>
        <transition name="slide">
          <div v-if="mobileNav" class="_sidebar-panel">
            <DropdownMenu :sidebar="true" kind="products" />
            <DropdownMenu :sidebar="true" kind="resources" />
            <a href="about" class="_about">About Us</a>
            <div class="_buttons-sidebar">
              <a href="/contact" class="_contact-button"> Contact Us</a>
              <a href="signIn" class="_sign-in-button"> Sign In</a>
            </div>
          </div>
        </transition>
      </div>
    </nav>
  </div>
</template>
<style lang="sass" scoped>
._nav
  position: fixed
  top: 0
  left: 0
  width: 100%
  color: #fff
  background-color: #0D1F5D
  transition: 500ms ease all
  z-index: 99
  &-header
    display: flex
    justify-content: space-between
    align-items: center
    width: 100%
    margin: 1em 0
nav
  position: relative
  display: flex
  height: 5.5vh
  min-height: 64px
  transition: 500ms ease all
  margin: 0 auto
  @media(min-width: 1648px)
    max-width: 1648px
._nav-inner
  display: flex
  justify-content: center
  align-items: center
  flex: 1

._logo
  display: flex
  justify-content: center
  align-items: center
  height: 100%
  min-height: 26px
._about
  text-decoration: none
  font-weight: bold
  color: #fff
._sign-in-button
  text-decoration: none
  display: flex
  justify-content: center
  align-items: center
  width: max-content
  // height: 100%
  text-align: center
  /* font-size: 1rem */
  font-weight: bold
  color: #fff
  background-color: #962326
  border: none
  border-radius: 8px
  box-shadow: none
  padding: 6px 26px
  transition: 0.3s ease
  &:hover
    background-color: #b32a2d
._contact-button
  text-decoration: none
  display: flex
  justify-content: center
  align-items: center
  box-sizing: border-box
  width: max-content
  height: 100%
  text-align: center
  /* font-size: 1rem */
  font-weight: bold
  color: #fff
  background-color: transparent
  outline: 2px #fff solid
  border-radius: 8px
  padding: 4px 16px
  transition: 0.3s ease
  &:hover
    background-color: #fff
    color: #0D1F5D

._icon
  display: flex
  align-items: center
  height: 100%
  z-index: 99

._sidebar
  &-backdrop
    background-color: rgba(19, 15, 64, .4)
    width: 100vw
    height: 100vh
    position: fixed
    top: 0
    left: 0
    cursor: pointer
  &-panel
    overflow-y: auto
    position: fixed
    left: 0
    top: 64px
    bottom: 0
    display: flex
    flex-direction: column
    justify-content: space-around
    align-items: center
    background-color: #130f40
    width: 50%
    // padding: 3em 2em
    z-index: 999
._buttons
  display: flex
  align-items: center
  gap: 2em

._buttons-sidebar
  display: flex
  flex-direction: column
  align-items: center
  gap: 1em

.slide-enter-active,
.slide-leave-active
  transition: transform 0.2s ease

.slide-enter,
.slide-leave-to
  transform: translateX(-100%)
  transition: all 150ms ease-in 0s

@media only screen and (max-width: 1648px)
  nav
    padding: 0 4em
</style>

Solution

  • Try with .slide-enter-form instead of .slide-enter and .slide-leave-from instead of .slide-leave . More here