我该如何解决这个跳跃过渡?

How can I fix this jumping transition?

我正在尝试构建某种淡入淡出滑块,但在转换之间出现奇怪的跳跃,已经尝试了 的解决方案,但它在我的情况下不起作用。我不明白为什么会这样,为了清楚起见,请看一下这个 gif

如果可能的话,我想知道是哪个部分造成的,以及我该如何解决。任何形式的帮助将不胜感激。请看看我的脚本..提前致谢!

<template>
  <header class="hero">
    <div class="container">
      <div class="textbox">
        <transition name="fade" v-for="(slide, i) in slides" :key="slide">
          <h1 class="punchline" v-if="current == i">{{ slide }}</h1>
        </transition>
        <NuxtLink class="link" to="/">
          <div class="icon-wrapper">
            <Icon icon="chevron-right" />
          </div>
        </NuxtLink>
      </div>
      <div class="indicator">
        <span
          v-for="(slide, i) in slides"
          :key="slide"
          :class="{ active: current == i }"
          class="item"
          @click="animateTo(i)"
        />
      </div>
    </div>
    <Vector :class="color" class="stick-overlay stick-top" file="model-stick" />
    <Vector
      :class="color"
      class="stick-overlay stick-bottom"
      file="model-stick"
    />
  </header>
</template>


<script>
export default {
  data() {
    return {
      current: 0,
      slides: ['Slide 001', 'Slide 002', 'Slide 003']
    }
  },
  computed: {
    color() {
      if (this.current == 1) return 'blue'
      if (this.current == 2) return 'purple'
      return 'default'
    }
  },
  methods: {
    animateTo(index) {
      this.current = index
    }
  }
}
</script>

<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}

.hero {
  @apply flex;
  @apply justify-center;
  @apply items-center;
  @apply text-white;
  @apply relative;

  height: 810px;
  background: url('/images/img-hero.png');
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;

  .textbox {
    @apply relative;
    @apply text-center;
    @apply mb-20;

    .punchline {
      @apply text-3xl;
      @apply font-semibold;
      @apply leading-relaxed;
      @apply mb-10;
    }

    .link {
      @apply flex;
      @apply justify-center;

      .icon-wrapper {
        @apply h-16 w-16;
        @apply border;
        @apply border-white;
        @apply flex;
        @apply justify-center;
        @apply items-center;
        @apply rounded-full;
      }
    }
  }

  .indicator {
    @apply flex;
    @apply justify-between;
    @apply items-center;
    @apply px-20;

    .item {
      @apply inline-block;
      @apply h-2 w-2;
      @apply border;
      @apply border-white;
      @apply rounded-full;
      @apply transition-colors;
      @apply duration-300;

      &.active {
        @apply bg-white;
      }
    }
  }

  .stick-overlay {
    @apply absolute;
    @apply z-0;

    &.stick-top {
      top: -75%;
      left: -75%;
      transform: scale(0.6) rotate(180deg);
    }

    &.stick-bottom {
      @apply block;
      bottom: -80%;
      left: -5%;
      transform: scale(0.6);
    }

    &::v-deep svg path {
      @apply transition-all;
      @apply duration-1000;
    }

    &.default ::v-deep svg path {
      fill: rgba(0, 40, 255, 0.5);
    }

    &.blue ::v-deep svg path {
      fill: rgba(33, 167, 252, 0.3);
    }

    &.purple ::v-deep svg path {
      fill: rgba(119, 41, 251, 0.3);
    }
  }
}
</style>

如果这是带有 Vue 路由器的 Vue3,如果你想为每个页面转换,请确保将其用作主路由器视图:

  <router-view v-slot="{ Component }">
    <transition>
      <component :is="Component"></component>
    </transition>
  </router-view>

对于这些,我使用这个 CSS [编辑:我看到你已经有了这些]:

.v-enter-active {
  transition: opacity 0.8s;
}

.v-enter-from {
  opacity: 0;
}

跳跃通常发生在一个元素离开(淡出-离开-活跃)和一个进入时,所以它有助于在两个方向上隐藏它们中的每一个,像这样:

.fade-leave-active,
.fade-enter-active {
  transition: opacity 0.8s;
}

.fade-leave-to,
.fade-enter-from {
  opacity: 0;
}

它有助于使 v-for 等更繁重的逻辑远离过渡元素,它可能会导致意外的内容移动。我尝试尽可能地将事情分开,并且 运行 在过渡的外部或内部循环。它不像对页面上的所有内容都使用一个过渡那么干净,但根据我的经验,为了避免您遇到的问题类型,这在某种程度上是必要的。

<transition name="fade">
   <top-notification class="error" v-if="isError && topNotification">{{ topNotification }}</top-notification>
</transition>

<transition name="fade">
   <top-notification v-if="!isError && topNotification">{{ topNotification }}</top-notification>
</transition>

最后,如果您使用 Vuex,则必须进行一些试验以使您的状态尽可能干净,并避免在页面上发生逻辑时瞬间引入双状态。例如,如果 v-if 有多个条件,那么在其中一个变为假之前,两个条件同时为真。这可能会导致元素停留一小会儿而不是直接消失。因此,请保持您的 Vue 检查器面板打开并监视状态。

好的,我知道了!
抱歉,我找不到 Nuxt + SCSS + Tailwind 工作的工作 codesandbox,所以我几乎在我自己的 Nuxt 项目中做了一个页面,这意味着我必须更新一些样式,因为我没有所有默认的 Tailwind 属性我的配置文件。

如果您愿意,可以随意放弃所有样式,因为错误不是来自样式。这是您问题的完整代码的实际答案(减去我没有给出的组件)。

<template>
  <header class="hero">
    <div class="container">
      <div class="textbox">
        <transition name="fade">
          <h1 class="punchline">{{ slides[current] }}</h1> <!-- this is working ! -->
        </transition>
        <NuxtLink class="link" to="/">
          <div class="icon-wrapper">
            tastyicon
            <!-- <Icon icon="chevron-right" /> -->
          </div>
        </NuxtLink>
      </div>
      <div class="indicator">
        <span
          v-for="(slide, i) in slides"
          :key="slide"
          :class="{ active: current == i }"
          class="item"
          @click="animateTo(i)"
        />
      </div>
    </div>
    <!-- <Vector :class="color" class="stick-overlay stick-top" file="model-stick" /> -->
    <!-- <Vector :class="color" class="stick-overlay stick-bottom" file="model-stick" /> -->
  </header>
</template>

<script>
export default {
  data() {
    return {
      current: 0,
      slides: ['Slide 001', 'Slide 002', 'Slide 003'],
    }
  },
  computed: {
    color() {
      if (this.current === 1) return 'blue'
      if (this.current === 2) return 'purple'
      return 'default'
    },
  },
  methods: {
    animateTo(index) {
      this.current = index
    },
  },
}
</script>
<style lang="scss" scoped>
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}

.hero {
  @apply flex justify-center items-center text-white relative;

  background-position: center;
  background-repeat: no-repeat;
  background-size: cover;
  height: 810px;
  background: url('https://images.unsplash.com/photo-1612712779378-58eb8b588761?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1650&q=80');

  .textbox {
    @apply relative text-center mb-20;

    .punchline {
      @apply text-3xl font-semibold leading-relaxed mb-8;
    }

    .link {
      @apply flex justify-center;

      .icon-wrapper {
        @apply h-16 w-16 border border-white flex justify-center items-center rounded-full;
      }
    }
  }

  .indicator {
    @apply flex justify-between items-center px-20;

    .item {
      @apply inline-block h-2 w-2 border border-white rounded-full transition-colors duration-300;

      &.active {
        @apply bg-white;
      }
    }
  }

  .stick-overlay {
    @apply absolute z-0;

    &.stick-top {
      left: -75%;
      top: -75%;
      transform: scale(0.6) rotate(180deg);
    }

    &.stick-bottom {
      @apply block;

      bottom: -80%;
      left: -5%;
      transform: scale(0.6);
    }

    &::v-deep svg path {
      @apply transition-all duration-1000;
    }

    &.default ::v-deep svg path {
      fill: rgba(0, 40, 255, 0.5);
    }

    &.blue ::v-deep svg path {
      fill: rgba(33, 167, 252, 0.3);
    }

    &.purple ::v-deep svg path {
      fill: rgba(119, 41, 251, 0.3);
    }
  }
}
</style>

此处的问题是您如何处理根据其状态显示在视图上的实际选定元素。你真的不应该在 for 循环中依赖 index,因为它会产生意想不到的行为。
v-if="current == i" 让您了解了这一点。尝试使用索引以外的其他元素显示元素,这些元素实际上可以“移动”并且 DOM 将删除它们,然后以糟糕的方式将它们带回,因此您的问题中出现了丑陋的过渡。
因为你有current,所以直接使用它就像slides[current] !

PS: 不确定你的项目是哪种配置,但你完全可以像我一样写你的样式(在@apply的一行)。或者直接进入你的 template 但我猜你不喜欢这样。

我终于通过将 <h1> 元素包装在 <template> 标签内并对其进行循环而不是直接从 <transition> 标签进行循环来修复它。注意我在<transition>标签里也加了mode="out-in",不然还是跳

<transition name="fade" mode="out-in">
  <template v-for="(slide, i) in slides">
    <h1 class="punchline" :key="slide" v-if="current == i">
      {{ slide }}
    </h1>
  </template>
</transition>

跳转的原因是因为当我检查元素时,新内容已加载,而旧元素仍然存在,因此它在完全删除之前只是呈现在旧元素下方。不确定为什么会发生这种情况,但可能是因为 <transition> 不应该循环。

<transition-group> 像这样:

<transition-group name="fade" mode="out-in">
  <h1
    class="punchline"
    v-for="(slide, i) in slides"
    :key="slide"
    v-show="current == i">
    {{ slide }}
  </h1>
</transition-group>

也不适用于此条件,因为它会产生相同的行为。 (或者我用错了?请告诉我)