Крутая загрузка кольца и цифровые эффекты прокрутки (Часть 1)

Vue.js

В реальной разработке проекта необходимо реализовать эффект кольцевой загрузки и цифровой прокрутки.Далее я поделюсь собственными идеями и практиками реализации.

Реализация идеи цифровой прокрутки

Vue сам управляет отображением слоя просмотра в соответствии с данными.Суть реализации прокрутки чисел заключается в том, чтобы установить функцию задержки для изменения данных, и отображение слоя просмотра также будет иметь постепенный эффект с изменением.

составной

Атрибуты, которые необходимо использовать для рассмотрения нескольких сценариев использования и разделения прокрутки на компоненты.

параметр иллюстрировать Типы По умолчанию
tag название ярлыка String 'span'
start стоит ли начинать Boolean true
startVal начальное значение Number / String 0
endVal конечное значение Number /String -
decimals сколько десятичных знаков Number 2
duration время перехода Number 2 (s)
isRestart Можно ли сделать паузу Boolean false

Итак, проверка типа нашего реквизита выглядит следующим образом

// index.vue
<script>
import CountUp from './countup.js'
export default {
  name: 'countup',
  mounted() {
    this.$nextTick(() => {
      this._countup = new CountUp(
        this.$el,
        this.startVal,
        this.endVal,
        this.decimals,
        this.duration
      )
      if (this.start) {
        this._countup.init()
      }
    })
  },
  props: {
    tag: {
      type: String,
      default: 'span'
    },
    start: {
      type: Boolean,
      default: true
    },
    startVal: {
      type: Number | String,
      default: 0
    },
    endVal: {
      type: Number | String,
      required: true
    },
    decimals: {
      type: Number,
      default: 2
    },
    duration: {
      type: Number,
      default: 2
    },
    isRestart: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      times: 0
    }
  },
  methods: {
    onPauseResumeClick() {
      if (this.isRestart) {
        if (this.times === 0) {
          this._countup.pauseResume()
          this.times++
        } else {
          this._countup.restart()
          this.times = 0
        }
      }
    }
  },
  render(h) {
    return h(
      this.tag,
      {
        on: {
          click: this.onPauseResumeClick
        }
      },
      [this.startVal]
    )
  },
  watch: {
    start(val) {
      if (val) {
        this._countup.init()
      }
    },
    endVal(val) {
      this._countup.updateNew(this.endVal)
    }
  }
}
</script>

逻辑部分抽离出来放在 countup.js文件中. Во-первых, давайте взглянем на файл index.vue, создадим экземпляр класса CountUp в смонтированном состоянии и向这个类中传递了我们props接收到的参数. а также在初始化和start值发生改变的时候в классе триггеровinit函номер, вendVal改变когда класс срабатываетupdateNew函数. Наконец, значение визуализируется в слое представления с помощью функции рендеринга. Проанализировав файл index.vue, мне интересно, какие функции определяет countup.js, а затем посмотреть на реализацию цифрового перехода.

Класс обратного отсчета

Прежде всего, давайте посмотрим на структуру кода, подробности пока не интересны, конструктор构造函数Получает переданные извне значения и добавляет эти значения в объект экземпляра. Такой метод в классе (也就是类的prototype原型上的方法) Все будет хорошо通过thisДоступ к значению объекта экземпляра.

class CountUp {
  constructor(target, startVal, endVal, decimals, duration) {
    this.target = target
    this.startVal = startVal
    this.endVal = endVal
    this.decimals = decimals
    this.duration = Number(this.duration) * 1000 || 2000
  }
  // 初始化
  init() {
    // 拿到DOM
    this.label =
      typeof this.target === 'string'
        ? document.getElementById(this.target)
        : this.target
    this.startVal = Number(this.startVal)
    this.endVal = Number(this.endVal)
    this.frameVal = this.startVal
    this.startTime = new Date()
    this.progress = this.endVal - this.frameVal
    this.update()
  }
  // 更新
  update() {
    this.rAF = setInterval(() => {
      const time = new Date() - this.startTime
      const speed =
        ((new Date() - this.startTime) / this.duration) * this.progress
      if (time >= this.duration) {
        clearInterval(this.rAF)
        this.frameVal = this.endVal
        this.startVal = this.frameVal
      } else {
        this.frameVal = this.startVal + speed
      }
      this.printValue(this.frameVal)
    })
  }
  // 打印值
  printValue(value) {
    this.label.innerHTML = value.toFixed(this.decimals)
  }
  // 有新的结束值
  updateNew(newEndVal) {
    this.pauseResume()
    this.endVal = newEndVal
    this.init()
  }
  // 暂停
  pauseResume() {
    clearInterval(this.rAF)
    this.startVal = this.frameVal
  }
  // 重新开始
  restart() {
    this.init()
  }
}
export default CountUp

constructorПолучите данные в конструкторе, а затем передайте каждыйprototypeметод на如:printValue(打印值)、updateNew(更新)......Реализовать логику кода. Вооружившись пониманием структуры этого класса, давайте посмотрим, что делает каждый модуль.
В навесной крючок проходимthis._countup.init()Инициализация, в процессе инициализации в основном выполняются некоторые преобразования безопасности.Если входящий $el не является строкой, будет получен DOM соответствующего id.В противном случае целью будет сама DOM, а начальное и конечное значения будут преобразованы в числовые типы.开启计时Установите startTime, мы будем использовать время, чтобы определить, достигнуто ли целевое значение, чтобы решить, следует ли остановить переход,计算出总的路程的绝对值. По окончании инициализации начните выполнение следующегоexecuteфункция.

переход

Самое главное в функции init — установить время начала перехода и рассчитать общее расстояние от начального значения до конечного значения. Далее идет процесс перехода цифровой прокрутки.

 update() {
    this.rAF = setInterval(() => {
      const time = new Date() - this.startTime
      const speed =
        ((new Date() - this.startTime) / this.duration) * this.progress
      if (time >= this.duration) {
        clearInterval(this.rAF)
        this.frameVal = this.endVal
        this.startVal = this.frameVal
      } else {
        this.frameVal = this.startVal + speed
      }
      this.printValue(this.frameVal)
    })
  }

существуетupdate更新函数В мы устанавливаем setInterval для повторения выполнения числа累计过程,пройти через单位时间/总时间*路程=速度Формула для накопления, следует отметить, чтоspeed本身是有正负Так что не нужно рассматривать, является ли это дополнительной или минусовой проблемой. И мы проходимprintValueФункция обновляет значение каждого обновления в узле DOM. И управлять отображением DOM в этой функции, например toFixed, чтобы контролировать количество десятичных знаков, отображаемых числом, конечно, вы также можете контролировать整数部分每三位加一个,отображение如:10,200

  // 打印值
  printValue(value) {
    this.label.innerHTML = value.toFixed(this.decimals)
  }

Пока мы завершили функцию перехода цифровой прокрутки, давайте посмотрим на эффект производства.

Хотя это было сделано, оказалось, что когда мы обновили конечное значение 5000 и изменили его на 500 до того, как оно было достигнуто, оно мгновенно изменилось обратно.

  // 有新的结束值
  updateNew(newEndVal) {
    this.pauseResume()
    this.endVal = newEndVal
    this.init()
  }
    // 暂停
  pauseResume() {
    clearInterval(this.rAF)
    this.startVal = this.frameVal
  }

мы должны更新endVal之前将上一个的定时器清除掉, иначе он всегда будет использовать setInterval. Так в середине 500 -> 5000 меняем значение на 500, значит и startVal и endVal равны 500. Естественно эффекта перехода не будет, и значение 500 будет возвращено сразу. После добавления функции pauseResume посмотрите на эффект перехода.

Конечно, функция pauseResume также может быть настроена на запуск вручную.Мы определяем предварительную функцию в методах и определяем, установлены ли реквизитыisRestart为true是否开启可暂停模式, судя по тому, правда ли это点击次数timesПауза на 0, перезапустите прокрутку на 1.

 methods: {
    onPauseResumeClick() {
      if (this.isRestart) {
        if (this.times === 0) {
          this._countup.pauseResume()
          this.times++
        } else {
          this._countup.restart()
          this.times = 0
        }
      }
    }
  },
 render(h) {
    return h(
      this.tag,
      {
        on: {
          click: this.onPauseResumeClick
        }
      },
      [this.startVal]
    )
  },

Умело используйте v-if v-show, чтобы завершить преобразование карты в килокалорию.

// number1
  <span v-if="isComplate">
    <count :start-val="1"
      :end-val="formatConsume"></count>千卡
  </span>
// number2
  <span v-show="!isComplate">
    <count :start-val="0"
      :end-val="1000"></count>卡
  </span>

пройти черезv-if重新渲染和v-show显示隐藏的机制, isComplate используется для оценки того, достигло ли оно 1000, здесь v-if используется для управления номером 1 для повторного рендеринга, если здесь用v-show则页面进入的时候就会开始加载过渡效果. Не тот эффект, который нам нужен. Причина, по которой number2 использует v-show, состоит в том, чтобы скрыть его, если онv-if直接消失在DOM会再触发transition的过渡效果,переход将变成500->5000->500的效果, нам просто нужно скрыть его и показать эффект перехода number1.

Эпилог

Мы завершили компоненты цифрового перехода.Во-первых, мы принимаем параметры через реквизит index.vue, помещаем логическую часть в countup.js и создаем экземпляр этого класса после введения. Вызывайте методы в классе для обновления DOM при инициализации и обновлении значений.下节将分享圆环加载的过渡效果.