Работая над моим последним проектом, мне пришлось придумать серию из четырех анимированных счетчиков, и я просто не мог решиться импортировать еще один *.js в свой проект для такой простой функции.
В этой статье подробно рассказывается, как я создал компонент, и объясняются проблемы, которые я преодолел.
Использование запросаAnimationFrame()
Поскольку я уже создавал что-то подобное в прошлом, я уже знал, что синтаксис setTimeout() и setInterval() может быть запутанным и сложным в обслуживании. Результат также мерцает и пропускает некоторые кадры. Узнай почему.
Немного зная о фрейме метода requestAnimationFrame(), я решил узнать о нем больше. Должен сказать, что я очень доволен результатом, а также чистотой кода.
«Метод window.requestAnimationFrame() сообщает браузеру, что вы хотите выполнить анимацию, и запрашивает, чтобы браузер вызвал указанную функцию для обновления анимации перед следующей перерисовкой. […]
Этот метод следует вызывать всякий раз, когда вы готовы обновить анимацию на экране. Это потребует, чтобы ваша функция анимации была вызвана до того, как браузер выполнит следующую перерисовку. Количество обратных вызовов обычно составляет 60 раз в секунду, но обычно соответствует частоте обновления экрана в большинстве веб-браузеров в соответствии с рекомендацией W3C».
Создание многократно используемой функции
Моя задача — создать четыре анимированных счетчика, поэтому моя первая задача — создать повторно используемую функцию. Мне нужны четыре параметра: элемент HTML, начальное значение, конечное значение и продолжительность.
Используя requestAnimationFrame, мне приходится вызывать метод каждый раз, когда мне нужен новый кадр: вот почему для этого я объявляю «шаг» как функцию. Внутри него я устанавливаю метку времени, сравниваю ее с прогрессом, достигнутым до сих пор, и устанавливаю значение в HTML. Наконец, если прогресс не достиг 100%, я использую параметр обратного вызова для повторного запуска анимации.
Как только эта функция создана, тяжелая работа сделана. Его довольно легко вызвать и использовать по мере необходимости. Все, что мне нужно сделать, это вызвать каждый счетчик с помощью @ViewChild. Итак, вот полный файл app.component.ts:
export class AppComponent implements AfterViewInit { title = 'Counter animation with angular'; cAnimated: boolean = false; dAnimated: boolean = false; // Accessing DOM elements with ViewChild @ViewChild('a') a: any; @ViewChild('b') b: any; @ViewChild('c') c: any; @ViewChild('d') d: any; constructor(private render: Renderer2) {} // Counter animation function animateValue(obj, start, end, duration) { let startTimestamp = null; const step = timestamp => { // Set the actual time if (!startTimestamp) startTimestamp = timestamp; // Calculate progress (the time versus the set duration) const progress = Math.min((timestamp - startTimestamp) / duration, 1); // Calculate the value compared to the progress and set the value in the HTML obj.nativeElement.innerHTML = Math.floor(progress * (end - start) + start); // If progress is not 100%, an call a new animation of step if (progress < 1) window.requestAnimationFrame(step) }; // Call a last animation of step window.requestAnimationFrame(step); } }
Создание события прокрутки
Поскольку анимация не отображается в верхней части страницы, мне пришлось создать событие прокрутки, которое позволяло бы запускать анимацию только один раз, когда элемент полностью находится в области просмотра, независимо от его размера.
Чтобы проверить, полностью ли элемент находится в области просмотра, я использовал метод getBoundingClientRect() и сравнил его со свойством innerHeight объекта окна. "Знать больше".
Чтобы убедиться, что анимация выполняется только один раз, я объявил логическое свойство elementAnimated и установил для него значение true после запуска анимации, чтобы она не запускалась каждый раз при запуске события.
// ngAfterViewInit is called after the view is initially rendered. @ViewChild() depends on it. You can't access view members before they are rendered. See the paragraph below. ngAfterViewInit() { // Calling the first two animations this.animateValue(this.a, 0, 2021, 1500); this.animateValue(this.b, 0, 16, 1500); // Create a scrolling event using Renderer2 this.render.listen('window', 'scroll', () => { // Get element c position let cPosition = this.c.nativeElement.getBoundingClientRect(); // Compare it with the height of the window if (cPosition.top >= 0 && cPosition.bottom <= window.innerHeight) { // if it has not been animated yet, animate c if (this.cAnimated == false) { this.animateValue(this.c, 0, 2300, 1500); // prevent animation from running again this.cAnimated = true; } } // Get element d position let dPosition = this.d.nativeElement.getBoundingClientRect(); // Compare it with the height of the window if (dPosition.top >= 0 && dPosition.bottom <= window.innerHeight) { // if it has not been animated yet, animate d if (this.dAnimated == false) { this.animateValue(this.d, 0, 3, 1500); // prevent animation from running again this.dAnimated = true; } }
Надеюсь, это помогло вам создать быстро анимированный счетчик с плавным освещением. Ваше здоровье!