Неожиданное поведение группы переходов при использовании отфильтрованного массива

У меня проблема с переходной группой. Проблема в том, что я пытаюсь сделать список отфильтрованным по какому-то условию. В моем примере - в зависимости от мин / макс.

Как это можно исправить?

Первоначально я пытался использовать вычисленное значение для массива, отображаемого внутри переходной группы. Но очевидно, что он возвращал новый массив каждый раз, когда значение ползунка диапазона обновлялось. Итак, я пытался использовать @input обработчик событий, который обновляет список, который должен отображаться только тогда, когда его нужно обновить. Что меня больше всего смущает, так это то, что я пытался настроить хуки переходной группы для @enter@enter-to@leave@leave-to события, и они запускаются правильно. Только раз. Я также пытался сделать его управляемым JS, но группа переходов устанавливает классы CSS, даже если я установил :css="false" :duration="500",

Я сделал упрощенный пример, который иллюстрирует проблему. codesandbox.io

Я ожидаю, что список (массив) будет обновлен, а затем инициирует повторное рендеринг содержимого группы переходов. И анимация FLIP выполняется. Но проблема в том, что если значение, которое используется в качестве условия фильтра, быстро изменяется (например, пользователь перетаскивает бегунок диапазона с помощью мыши), анимация либо вообще не выполняется, либо вызывает лаги. Внимательно следите за поведением, когда вы перетаскиваете ползунок диапазона слева направо (анимации запаздывают), и он отличается от того, что вы видите, когда перетаскиваете его справа налево (анимации для первого элемента не выполняются). Также обратите внимание, что если вы нажмете на ползунок, чтобы установить значение - анимация будет выполняться так, как ожидалось.

1 ответ

Ссылка: "И анимация FLIP выполняется"...

Учитывая, что вы хотите перевернуть предмет, я бы не стал использовать <transition-group> совсем.
Я бы использовал эффект трехмерного вращения (который, как я предполагаю, вам нужен) и управлял бы углом поворота, используя вычисляемое свойство, основанное на соотношении между подпорками элемента и значением диапазона.

Вот:

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>

Другие вопросы по тегам