使用过滤数组时的过渡组意外行为

时间:2019-05-15 10:14:49

标签: vue.js vuejs2 vuejs-transition-group

我的过渡小组有问题。 问题是我正在尝试渲染按某种条件过滤的列表。在我的示例中-取决于最小/最大范围。

如何解决?

最初,我尝试使用转换组内部渲染的Array的计算值。但是很明显,每次更新范围滑块值时,它都会返回新数组。因此,我尝试使用@input事件处理程序来更新仅在需要更新时才应呈现的列表。 最让我感到困惑的是,我尝试为@enter @enter-to @leave @leave-to事件设置过渡组挂钩,并且正确触发了它们。就一次。我也尝试使其变为JS驱动,但即使我设置了:css="false" :duration="500",过渡组也设置了CSS类。

我做了一个简单的例子来说明这个问题。 codesandbox.io

我希望列表(数组)得到更新,然后触发转换组的内容重新呈现。并执行FLIP动画。 但是问题在于,如果用作过滤条件的值快速更改(例如,用户使用鼠标拖动范围滑块),则根本不会执行动画,或者会导致滞后。从左向右拖动范围滑块(动画效果不佳)时,可以准确观看行为,这与从右向左拖动范围滑块时看到的行为有所不同(第一个元素不执行动画)。 另请注意,如果单击滑块以设置值-动画将按预期执行。

1 个答案:

答案 0 :(得分:0)

Ref:“并且执行了FLIP动画” ...

考虑到您想翻转项目,我根本不会使用<transition-group>
我将使用3d旋转效果(我猜这是您想要的),并根据项目道具和范围值之间的关系,使用计算属性来控制旋转角度。

这里是:

Vue.config.devtools = false;
Vue.config.productionTip = false;

Vue.component('rotatingButton', {
  template: `
    <div>
      <div class="flipper">
        <div
          class="items"
          :style="{ transform: \`rotateX(\${rotation}deg)\` }">
          <div v-for="item in items"
            :key="item.ID"
            :class="[item.value, 'item']"
            v-text="item.value" />
        </div>
      </div>
      <input type="range" v-model="range" min="0" max="1500">
      <div class="color-range">
        <div v-for="item in items"
          :style="{width: \`\${(item.max - item.min)/15}%\`, 
                   left: \`\${item.min/15}%\`,
                   backgroundColor: item.barColor }"
        />
      </div>
    </div>`,
  data: () => ({
    range: 100,
    items: [{
          ID: 0,
          min: 0,
          max: 500,
          value: "first",
          barColor: '#369'
        }, {
          ID: 1,
          min: 500,
          max: 700,
          value: "second",
          barColor: "#900"         
        }, {
          ID: 2,
          min: 700,
          max: 1500,
          value: "third",
          barColor: "#393"
        }
      ],
  }),
  computed: {
    rotation() {
      return this.range < this.items[0].max ? 0 : (this.range < this.items[1].max ? 90 : 180);
    }
  }
})

new Vue({
  el: '#app'
});
#app {
  text-align: center;
  margin-top: 60px;
}
.flipper {
  perspective: 210px;
  height: 30px;
  width: 150px;
  line-height: 30px;
  margin: 15px auto;
}
.items {
  height: 100%;
  perspective-origin: 150% 150%;
  transform-style: preserve-3d;
  transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.item {
  display: block;
  position: absolute;
  width: 100%;
  height: 100%;
  border: 1px solid rgba(33,66,99,.33);
  font: 25px monospace;
  color: rgba(33,66,99,.33);
  background-color: white;
}

.first { transform: translateZ(15px); }
.second { transform: rotateX(-90deg) translateZ(15px); }
.third { transform: rotateX(180deg) translateZ(15px); }
.fourth { transform: rotateX(90deg) translateZ(15px); }
.color-range {
  padding: 0 1rem;
  position: relative;
  height: 4px;
}
input[type="range"] {width: 100%; margin: 0;}
.color-range > div {
  position: absolute;
  top: 0;
  height: 100%;
  opacity: .65;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <rotating-button />
</div>


如果需要,该示例可以扩展为处理大量项目:

Vue.config.devtools = false;
Vue.config.productionTip = false;

Vue.component('rotatingButton', {
  template: `
    <div>
      <div class="flipper">
        <div
          class="items"
          :style="{ transform: \`rotateX(\${rotation}deg)\` }">
          <div v-for="(item, key) in items"
          	v-show="range > key -1 && range < key + 3"
            :key="key"
            :class="['item', \`key-\${key}\`]"
            v-text="item + 1" />
        </div>
      </div>
      <div class="inputs">
        <input type="range" v-model="range" min="1" :max="max" step=".01">
        <input type="number" v-model="max">
      </div>
      <pre>{{logger}}</pre>
    </div>`,
  data: () => ({
    range: 10,
    max: 15,
  }),
  computed: {
    items() {
      return _.times(this.max);
    },
    rotation() {
      return Math.floor(((Number(this.range) * 9) + 36) * 1e3) / 1e2;
    },
    logger() {
      return JSON.stringify({
        range: Number(this.range),
        items: Number(this.max),
        rotation: this.rotation
      }, true, 2);
    }
  },
})

new Vue({
  el: '#app'
});
#app {
  text-align: center;
}

.flipper {
  perspective: 210px;
  height: 30px;
  width: 150px;
  line-height: 30px;
  margin: 15px auto;
}

.items {
  height: 100%;
  perspective-origin: 150% 150%;
  transform-style: preserve-3d;
  transition: transform 0.1s ease-in-out;
}

.item {
  display: block;
  position: absolute;
  width: 100%;
  height: 100%;
  border: 1px solid rgba(33, 66, 99, .42);
  font: 25px monospace;
  color: rgba(33, 66, 99, .42);
  background-color: white;
  transform-style: preserve-3d;
}

.item {
  transform: translateZ(15px);
}
.inputs {
  display: flex;
  padding: 0 1rem;
}
input[type="range"] {
  flex-grow: 1;
  margin: 0 1rem 0 0;
}
input[type="number"] {
  font-size: 1.5rem;
  width: 100px;
}
.item:nth-child(4n + 1) {
  transform: rotateX(-90deg) translateZ(15px);
}

.item:nth-child(4n + 2) {
  transform: rotateX(180deg) translateZ(15px);
}

.item:nth-child(4n + 3) {
  transform: rotateX(90deg) translateZ(15px);
}


.color-range>div {
  position: absolute;
  top: 0;
  height: 100%;
  opacity: .65;
}

pre {
  text-align: left;
  background-color: #def;
  border: 1px solid #ccc;
  border-radius: .25rem;
  padding: 1rem;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <rotating-button />
</div>