Search code examples
cssvue.jsvuejs3css-transitionsvue-transitions

Vue slide up and down transition not working at all, elements just pop in and out


I have a footer section that has some hidden content that can be toggled. For whatever reason the transition animation isn't working/ is super stuttery and I'm not too sure what I am doing wrong here.

I've read about making max-height the attribute you target with the animations, but this is still not working properly.

I've made a CodeSandbox to show the issue, so please take a look!

Any advise or tips would be greatly appreciated!

Cheers!

Footer.vue

<template>
  <div class="_real-time-footer">
    <div class="_real-time-footer-top">
      <div class="_real-time-footer-top-left-side">
        <p class="_real-time-footer-top-left-side-inferences-text">Something</p>
        <div>button</div>
        <div>button</div>
      </div>
      <div class="_video-play-and-time">
        <button
          v-if="!videoIsPlaying"
          key="1"
          style="cursor: pointer"
          @click.prevent="() => playVideo()"
          class="_video-play-and-time-play-button"
        >
          <svg
            class="_video-play-and-time-play-button-icon"
            id="Play_Btn"
            data-name="Play Btn"
            xmlns="http://www.w3.org/2000/svg"
            width="100%"
            height="100%"
            viewBox="0 0 32 32"
          >
            <rect
              id="Rectangle_3351"
              data-name="Rectangle 3351"
              width="32"
              height="32"
              rx="4"
              fill="#4972fa"
              opacity="0.254"
            />
            <path
              id="Play"
              d="M7.649,1.382a1,1,0,0,1,1.7,0l6.71,10.893A1,1,0,0,1,15.21,13.8H1.79a1,1,0,0,1-.851-1.524Z"
              transform="translate(25.16 7.5) rotate(90)"
              fill="#466ff4"
              opacity="0.998"
            />
          </svg>
        </button>
        <button
          v-else
          key="2"
          style="cursor: pointer"
          @click.prevent="() => stopVideo()"
          class="_video-play-and-time-play-button"
        >
          <svg
            class="_video-play-and-time-play-button-icon"
            id="Stop_Btn"
            data-name="Stop Btn"
            xmlns="http://www.w3.org/2000/svg"
            width="100%"
            height="100%"
            viewBox="0 0 32 32"
          >
            <rect
              id="Rectangle_2865"
              data-name="Rectangle 2865"
              width="32"
              height="32"
              rx="4"
              fill="rgba(71,113,250,0.25)"
            />
            <rect
              id="Rectangle_3155"
              data-name="Rectangle 3155"
              width="15"
              height="15"
              rx="1"
              transform="translate(8.5 8.5)"
              fill="#4771fa"
            />
          </svg>
        </button>
        <p class="_video-play-and-time-time">{{ videoTime }}</p>
      </div>
      <div class="_current-time">
        <p class="_current-time-time">{{ time }}</p>
        <p class="_current-time-timezone">{{ timezone }}</p>
      </div>
    </div>
    <Transition name="slide" mode="out-in">
      <div
        v-if="liveInferenceManagementPanelIsVisible"
        style="
          display: flex;
          justify-content: center;
          align-items: center;
          max-height: 0;
          background-color: #202634;
          padding: 3em 0;
        "
      >
        <div>something here</div>
      </div>
    </Transition>
  </div>
</template>
<script>
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const videoIsPlaying = ref(false);
    const liveInferenceManagementPanelIsVisible = ref(false);
    const videoTime = ref("00:00:00");
    const time = new Date().toLocaleTimeString([], {
      hour: "2-digit",
      minute: "2-digit",
      // hour12: false,
    });
    const timezone = new Date()
      .toLocaleDateString(undefined, { day: "2-digit", timeZoneName: "long" })
      .substring(4)
      .match(/\b(\w)/g)
      .join("");
    function playVideo() {
      videoIsPlaying.value = true;
      liveInferenceManagementPanelIsVisible.value = false;
      console.log("Video starts");
    }
    function stopVideo() {
      videoIsPlaying.value = false;
      liveInferenceManagementPanelIsVisible.value = true;
      console.log("Video ends");
    }
    return {
      videoIsPlaying,
      liveInferenceManagementPanelIsVisible,
      videoTime,
      time,
      timezone,
      playVideo,
      stopVideo,
    };
  },
});
</script>
<style lang="sass" scoped>
._real-time-footer
  position: absolute
  bottom: 0
  left: 0
  right: 0
  &-top
    display: flex
    justify-content: center
    align-items: center
    width: 100%
    padding: 1rem 2rem
    color: #A5B0CB
    background: #283044
    &-left-side
      flex: 1 1 0
      display: flex
      align-items: center
      gap: 2em
      // flex-wrap: wrap
      &-inferences-text
        margin-right: 2em
      &-live-feed
        position: absolute
        top: 1.5em
        left: 1.5em
        color: #fff
._video-play-and-time
  flex: 1 1 0
  display: flex
  align-items: center
  background: none
  gap: 1.25em
  &-play-button
    &:focus
      outline: none
    &-icon
      width: 2em
  &-time
    opacity: 50%
._current-time
  justify-self: right
  display: flex
  align-items: center
  gap: 0.75em
  border-radius: 12px
  padding: 0.35em 1.25em
  background-color: #202634
  &-time
    font-size: 14px
  &-timezone
    font-size: 8px
    opacity: 66%
._dsp-wrapper
  position: absolute
  top: 10px
  left: 10px
  height: 70%
  width: 20%
._fsp-wrapper
  position: absolute
  top: 8px
  right: 8px
  height: 43%
  width: 20%
._ccp-wrapper
  position: absolute
  bottom: 8px
  right: 10px
  left: 10px
  // width: 100%
  height: 23%
  background: red
  opacity: 70%

.fade-move,
.fade-enter-active,
.fade-leave-active
  height: inherit
  transition: opacity 0.3s

.fade-enter,
.fade-leave-to
  opacity: 0

.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: 200px
  overflow: hidden

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



Solution

  • A couple of remarks.

    • The class naming syntax has changed from vue2 to vue3 .slide-enter should be now .slide-enter-from. Check out the css based transitions.
    • I put your inline styling in a class above the transition classes to get the css specificity in order.
    • I used the padding property instead of the max-height. Since the padding property is the one deciding the container height.

    With all this in place your sass code should look something like:

    .slide-container
      display: flex
      justify-content: center
      align-items: center
      max-height: 0
      background-color: #202634
      padding: 3em 0
    
    .slide-enter-active
      ...
    
    .slide-leave-active
      ...
    
    .slide-enter-to,
    .slide-leave-from
      padding: 3em 0
      overflow: hidden
    
    .slide-enter-from,
    .slide-leave-to
      overflow: hidden
      padding: 0em 0
    
    

    I forked your example and made the changes for you to play around.