javascript - Vuejs - 一次一个元素的过渡组轮播动画

标签 javascript css vue.js vuejs-transition

我正在尝试使用 Vue 过渡组来实现这种轮播/幻灯片效果:

Example GIF

正如您所看到的,它们会在前一步骤上升且当前步骤及其内容跟随时间轴上升的同时对一个列表项(步骤)进行动画处理。

我不确定使用 transition-group 是否可以实现我想要完成的任务,因为整个父 block 都会动画,而不是子节点。如果是这样的话,如果我至少可以为父 block 设置动画,我会很高兴。

另一个需要注意的是,由于我在没有 v-if 或没有过滤列表的情况下使用 transition-group ,所以默认情况下会呈现所有步骤,这不是'不好。

这是我的 HTML 结构:

<transition-group class="steps-viewport" name="steps" tag="div">
  <div v-for="step in currentStep" :key="step.order" class="step-wrapper">
    <h3 class="is-size-5 mb-6 has-text-grey-light">
      Passo {{ step.order }}
    </h3>
    <h1 class="is-size-3">{{ step.title }}</h1>
    <h2 class="is-size-4 mt-2 has-text-grey">{{ step.headline }}</h2>
    <component
      class="mt-5"
      v-bind:is="step.component"
      @status-changed="handleStatusChange($event)"
    ></component>
  </div>
</transition-group>

这是我的 CSS:

.component-wrapper {
  width: 100%;

  .steps-viewport {
    height: calc(100vh - 10rem);
    overflow: hidden;
    display: flex;
    flex-direction: column;

    .step-wrapper {
      flex: 0 0 calc(100vh - 10rem);
      display: flex;
      justify-content: center;
      flex-direction: column;
    }
  }
}

最后但并非最不重要的一点是我的组件的脚本:

import ProductInfo from "./ProductInfo";

export default {
  components: {
    ProductInfo
  },

  props: {
    defaultActiveStep: {
      type: Number,
      default: 1
    }
  },

  watch: {
    activeStep() {
      this.$emit("step-changed", this.activeStep);
    }
  },

  computed: {
    currentStep() {
      return this.steps.filter(s => s.order === this.activeStep);
    }
  },

  data: () => {
    return {
      activeStep: 1,
      steps: [
        {
          order: 1,
          title: "Title 1?",
          headline:
            "Headline 1",
          component: "product-info"
        },
        {
          order: 2,
          title: "Title 2",
          headline:
            "Headline 2.",
          component: "product-info"
        },
        {
          order: 3,
          title: "Title 3",
          headline:
            "Headline 3.",
          component: "product-info"
        },
        {
          order: 4,
          title: "Title 4!",
          headline:
            "Headline 4",
          component: "product-info"
        }
      ]
    };
  },

  methods: {
    handleStatusChange(status) {
      
      const first = this.steps.shift();
      this.steps = this.steps.concat(first);
    }
  }
};

最佳答案

您需要定义特殊的类来针对转换的不同阶段,在本例中为 .steps-enter-active (中间转换状态)和 .steps-enter-to(最终状态)。

要使其在页面加载时发生,您还需要传递 appear 属性。


如果您希望整个 order block 进行转换,您可以这样做:

new Vue({
  el: '#app',

  computed: {
    currentStep() {
      return this.steps.filter(s => s.order === this.activeStep);
    }
  },

  data: () => {
    return {
      activeStep: 1,
      steps: [{
          order: 1,
          title: "Title 1?",
          headline: "Headline 1",
          component: "product-info"
        },
        {
          order: 2,
          title: "Title 2",
          headline: "Headline 2.",
          component: "product-info"
        },
        {
          order: 3,
          title: "Title 3",
          headline: "Headline 3.",
          component: "product-info"
        },
        {
          order: 4,
          title: "Title 4!",
          headline: "Headline 4",
          component: "product-info"
        }
      ]
    };
  },
});

Vue.config.productionTip = false;
Vue.config.devtools = false;
.component-wrapper {
  width: 100%;
}

.steps-viewport {
  height: calc(100vh - 10rem);
  /* overflow: hidden */
  display: flex;
  flex-direction: column;
}

.step-wrapper {
  flex: 0 0 calc(100vh - 10rem);
  display: flex;
  justify-content: center;
  flex-direction: column;
}

.steps-enter-active {
  opacity: 0;
  transform: translateY(100%);
  transition: all 0.4s;
}

.steps-enter-to {
  opacity: 1;
  transform: translateY(0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <transition-group class="steps-viewport" name="steps" tag="div" appear>
    <div v-for="step in currentStep" :key="step.order" class="step-wrapper">
      <h3 class="is-size-5 mb-6 has-text-grey-light">
        Passo {{ step.order }}
      </h3>
      <h1 class="is-size-3">{{ step.title }}</h1>
      <h2 class="is-size-4 mt-2 has-text-grey">{{ step.headline }}</h2>
    </div>
  </transition-group>
</div>


如果您希望其中的每个元素进行转换,您可以这样做,添加一个 transition-delay:

new Vue({
  el: '#app',

  computed: {
    currentStep() {
      return this.steps.filter(s => s.order === this.activeStep);
    }
  },

  data: () => {
    return {
      activeStep: 1,
      steps: [{
          order: 1,
          title: "Title 1?",
          headline: "Headline 1",
          component: "product-info"
        },
        {
          order: 2,
          title: "Title 2",
          headline: "Headline 2.",
          component: "product-info"
        },
        {
          order: 3,
          title: "Title 3",
          headline: "Headline 3.",
          component: "product-info"
        },
        {
          order: 4,
          title: "Title 4!",
          headline: "Headline 4",
          component: "product-info"
        }
      ]
    };
  },
});

Vue.config.productionTip = false;
Vue.config.devtools = false;
.component-wrapper {
  width: 100%;
}

.steps-viewport {
  height: calc(100vh - 10rem);
  /* overflow: hidden */
  display: flex;
  flex-direction: column;
}

.step-wrapper {
  flex: 0 0 calc(100vh - 10rem);
  display: flex;
  justify-content: center;
  flex-direction: column;
}

.steps-enter-active {
  opacity: 0;
  transform: translateY(100%);
  transition: all 0.4s;
}

.steps-enter-to {
  opacity: 1;
  transform: translateY(0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <div v-for="step in currentStep" :key="step.order" class="step-wrapper">
    <transition-group class="steps-viewport" name="steps" tag="div" appear>
      <h3 class="is-size-5 mb-6 has-text-grey-light" key="1">
        Passo {{ step.order }}
      </h3>
      <h1 class="is-size-3" style="transition-delay: 0.1s" key="2">{{ step.title }}</h1>
      <h2 class="is-size-4 mt-2 has-text-grey" style="transition-delay: 0.2s" key="3">{{ step.headline }}</h2>
    </transition-group>
  </div>
</div>


要同时转出,您需要使用 transition 来代替,这样您就可以使用 mode="out-in" 允许元素首先转出,在下一个进入之前。

您还需要使用 .steps-enter-active > * 来定位 CSS 中过渡元素的子元素。然后,只需添加一个 .steps-leave-to 类,它定义要离开的状态:

new Vue({
  el: '#app',

  computed: {
    currentStep() {
      return this.steps.filter(s => s.order === this.activeStep);
    }
  },

  methods: {
    nextStep() {
      if (this.activeStep !== this.steps.length) {
        this.activeStep++;
      } else {
        this.activeStep = 1;
      }
    }
  },

  data: () => {
    return {
      activeStep: 1,
      steps: [{
          order: 1,
          title: "Title 1?",
          headline: "Headline 1",
          component: "product-info"
        },
        {
          order: 2,
          title: "Title 2",
          headline: "Headline 2.",
          component: "product-info"
        },
        {
          order: 3,
          title: "Title 3",
          headline: "Headline 3.",
          component: "product-info"
        },
        {
          order: 4,
          title: "Title 4!",
          headline: "Headline 4",
          component: "product-info"
        }
      ]
    };
  },
});

Vue.config.productionTip = false;
Vue.config.devtools = false;
.component-wrapper {
  width: 100%;
}

.steps-viewport {
  height: calc(100vh - 10rem);
  /* overflow: hidden */
  display: flex;
  flex-direction: column;
}

.step-wrapper {
  flex: 0 0 calc(100vh - 10rem);
  display: flex;
  justify-content: center;
  flex-direction: column;
}

.step-wrapper,
.step-wrapper>* {
  transition: all 0.4s;
}

.step-wrapper>h1 {
  transition-delay: 0.1s;
}

.step-wrapper>h2 {
  transition-delay: 0.2s;
}

.steps-enter-active>* {
  opacity: 0;
  transform: translateY(100%);
}

.steps-leave-to>* {
  opacity: 0;
  transform: translateY(-100%);
}

.steps-enter-to>* {
  opacity: 1;
  transform: translateY(0);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <transition name="steps" mode="out-in" duration="600" appear>
    <div v-for="step in currentStep" :key="step.order" class="step-wrapper">
      <h3 class="is-size-5 mb-6 has-text-grey-light">
        Passo {{ step.order }}
      </h3>
      <h1 class="is-size-3">{{ step.title }}</h1>
      <h2 class="is-size-4 mt-2 has-text-grey">{{ step.headline }}</h2>
    </div>
  </transition>
  <button @click="nextStep()">Next</button>
</div>


最后,为了在添加新元素后让所有内容顺利向上移动,您可以将初始字段包装在 div 中,将新元素包装在过渡中,并将第一个 div 的高度减少新元素的高度.

您还需要转换高度并配置计时(转换延迟和持续时间属性)以正确匹配:

new Vue({
  el: '#app',

  computed: {
    currentStep() {
      return this.steps.filter(s => s.order === this.activeStep);
    }
  },

  methods: {
    nextStep() {
      this.$refs.addStep.disabled = false;
      this.extraStep = false;
      this.$refs.addAnotherStep.disabled = false;
      this.anotherExtraStep = false;

      if (this.activeStep !== this.steps.length) {
        this.activeStep++;
      } else {
        this.activeStep = 1;
      }
    },
    addStep() {
      const initial = document.querySelector('.step-initial');
      const input = document.querySelector('.step-input');

      // 52px = input height + margin + border
      initial.style.maxHeight = initial.offsetHeight - 52 + 'px';

      if (!this.extraStep) {
        this.$refs.addStep.disabled = true;
        this.extraStep = true;
      } else {
        this.$refs.addAnotherStep.disabled = true;
        this.anotherExtraStep = true;
      }
    }
  },

  data: () => {
    return {
      extraStep: false,
      anotherExtraStep: false,
      activeStep: 1,
      steps: [{
          order: 1,
          title: "Title 1?",
          headline: "Headline 1",
          component: "product-info"
        },
        {
          order: 2,
          title: "Title 2",
          headline: "Headline 2.",
          component: "product-info"
        },
        {
          order: 3,
          title: "Title 3",
          headline: "Headline 3.",
          component: "product-info"
        },
        {
          order: 4,
          title: "Title 4!",
          headline: "Headline 4",
          component: "product-info"
        }
      ]
    };
  },
});

Vue.config.productionTip = false;
Vue.config.devtools = false;
#app {
  position: relative;
  height: calc(300px + 52px);
}

.component-wrapper {
  width: 100%;
}

.steps-viewport {
  /* height: calc(100vh - 10rem); */
  /* overflow: hidden */
  display: flex;
  flex-direction: column;
}

.step-wrapper,
.step-wrapper * {
  transition: all 0.2s;
}

.step-wrapper * {
  margin: 0;
}

.step-initial {
  display: flex;
  justify-content: space-evenly;
  flex-direction: column;
  height: 300px;
  max-height: 300px;
}

.step-initial *:nth-child(2) {
  transition-delay: 0.05s;
}

.step-initial *:nth-child(3) {
  transition-delay: 0.1s;
}

.steps-enter-active .step-initial * {
  opacity: 0;
  transform: translateY(100%);
}

.steps-leave-to .step-initial *,
.steps-leave-to .step-input {
  opacity: 0;
  transform: translateY(-100%);
}

.steps-leave-to .step-input:nth-of-type(2) {
  transition-delay: 0.2s;
}

.steps-leave-to .step-input:nth-of-type(3) {
  transition-delay: 0.3s;
}

.steps-enter-to .step-initial * {
  opacity: 1;
  transform: translateY(0);
}

.step-input {
  margin: 20px 0;
  height: 30px;
}

.steps-input-enter-active {
  opacity: 0;
  transform: translateY(100%);
}

.steps-input-leave-to {
  opacity: 0;
  transform: translateY(-100%);
}

.steps-input-enter-to {
  opacity: 1;
  transform: translateY(0);
}

.step-btns {
  position: absolute;
  bottom: 10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <transition name="steps" mode="out-in" duration="350" appear>
    <div v-for="step in currentStep" :key="step.order" class="step-wrapper">
      <div class="step-initial">
        <h3 class="is-size-5 mb-6 has-text-grey-light">
          Passo {{ step.order }}
        </h3>
        <h1 class="is-size-3">{{ step.title }}</h1>
        <h2 class="is-size-4 mt-2 has-text-grey">{{ step.headline }}</h2>
      </div>
      <transition name="steps-input">
        <div v-if="extraStep" class="step-input">
          <input />
        </div>
      </transition>
      <transition name="steps-input">
        <div v-if="anotherExtraStep" class="step-input">
          <input />
        </div>
      </transition>
    </div>
  </transition>
  <div class="step-btns">
    <button @click="nextStep()">Next</button>
    <button @click="addStep()" ref="addStep">Add Step</button>
    <button @click="addStep()" ref="addAnotherStep">Add Another Step</button>
  </div>
</div>

关于javascript - Vuejs - 一次一个元素的过渡组轮播动画,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65152286/

相关文章:

可以容纳 div 的未对齐网格的 CSS 框架

html - Bootstrap 3.0.3 form-group 类它不采用任何边距底部

javascript - 使用 async/await 时如何检查特定的后端错误键?

javascript - react 问题 : I tried file data set in state when upload file, 但它不起作用

javascript - 图片、音乐、视频、youtube、flash 和 vimeo x 浏览器媒体播放器插件?

css - 文本对齐文本 45 度

vue.js - .vue 文件未按预期编译

javascript - 如何在 Vuex 的 Action 中调用 Action

javascript - Simple-todos 应用程序教程,尝试创建一个从 Mongo 集合中删除已检查任务的函数

javascript - 设置innerHTML时DIV宽度和高度不会调整大小?