Этот вопрос проверяет понимание оптимизации анимаций и визуальных обновлений в браузере, а также знание отличий requestAnimationFrame от других методов тайминга.
requestAnimationFrame используют для плавной анимации и визуальных обновлений, синхронизированных с частотой обновления экрана. Он обеспечивает оптимальную производительность, автоматически приостанавливая выполнение когда страница не видна, и вызывая колбэк перед перерисовкой браузера.
requestAnimationFrame - это браузерный API, который позволяет выполнять код синхронно с обновлением экрана, обычно 60 раз в секунду.
javascript
function animateElement(element) {
let startTime = null;
const duration = 2000; // 2 секунды
function step(timestamp) {
if (!startTime) startTime = timestamp;
const progress = timestamp - startTime;
// Вычисление текущей позиции
const translateX = (progress / duration) * 200;
element.style.transform = `translateX(${translateX}px)`;
// Продолжение анимации
if (progress < duration) {
requestAnimationFrame(step);
}
}
requestAnimationFrame(step);
}javascript
class SmoothScroller {
constructor() {
this.isScrolling = false;
this.targetScroll = 0;
}
scrollTo(target) {
this.targetScroll = target;
if (!this.isScrolling) {
this.animateScroll();
}
}
animateScroll() {
this.isScrolling = true;
const currentScroll = window.pageYOffset;
const distance = this.targetScroll - currentScroll;
if (Math.abs(distance) < 1) {
this.isScrolling = false;
return;
}
// Плавное замедление
const newScroll = currentScroll + distance * 0.1;
window.scrollTo(0, newScroll);
requestAnimationFrame(() => this.animateScroll());
}
}Автоматическая пауза когда страница неактивна или скрыта
Согласование с частотой обновления экрана (обычно 60fps)
Выполнение перед рендерингом - гарантия актуальности данных
javascript
// Плохо - с setTimeout
function animateWithTimeout() {
element.style.left = (parseInt(element.style.left) + 1) + 'px';
setTimeout(animateWithTimeout, 16); // ~60fps
}
// Хорошо - с requestAnimationFrame
function animateWithRAF() {
element.style.left = (parseInt(element.style.left) + 1) + 'px';
requestAnimationFrame(animateWithRAF);
}javascript
class GameEngine {
constructor() {
this.lastTime = 0;
this.isRunning = false;
}
start() {
this.isRunning = true;
this.gameLoop(0);
}
gameLoop(timestamp) {
if (!this.isRunning) return;
// Вычисление delta time для независимости от FPS
const deltaTime = timestamp - this.lastTime;
this.lastTime = timestamp;
// Обновление игрового состояния
this.update(deltaTime);
// Отрисовка кадра
this.render();
// Следующий кадр
requestAnimationFrame((time) => this.gameLoop(time));
}
update(deltaTime) {
// Обновление позиций объектов, физика и т.д.
}
render() {
// Отрисовка игровых объектов
}
stop() {
this.isRunning = false;
}
}javascript
function measureFPS() {
let frameCount = 0;
let lastTime = performance.now();
let fps = 0;
function countFrame() {
frameCount++;
const currentTime = performance.now();
if (currentTime - lastTime >= 1000) {
fps = frameCount;
frameCount = 0;
lastTime = currentTime;
console.log(`FPS: ${fps}`);
}
requestAnimationFrame(countFrame);
}
requestAnimationFrame(countFrame);
}javascript
requestAnimationFrame((timestamp) => {
// timestamp - время с момента загрузки страницы в миллисекундах
// Точное время когда будет выполнен рендеринг
console.log(`Кадр будет отрисован в: ${timestamp}`);
});javascript
let animationId = null;
function startAnimation() {
function animate() {
// Логика анимации
animationId = requestAnimationFrame(animate);
}
animationId = requestAnimationFrame(animate);
}
function stopAnimation() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
}javascript
function efficientAnimation() {
let rafId = null;
function animationStep() {
// Выполняем только необходимые вычисления
performCalculations();
// Проверяем нужно ли продолжать
if (shouldContinueAnimation()) {
rafId = requestAnimationFrame(animationStep);
} else {
rafId = null;
}
}
function start() {
if (!rafId) {
rafId = requestAnimationFrame(animationStep);
}
}
function stop() {
if (rafId) {
cancelAnimationFrame(rafId);
rafId = null;
}
}
return { start, stop };
}javascript
class BatchedAnimator {
constructor() {
this.callbacks = new Set();
this.rafId = null;
}
addCallback(callback) {
this.callbacks.add(callback);
this.scheduleFrame();
}
removeCallback(callback) {
this.callbacks.delete(callback);
if (this.callbacks.size === 0 && this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
}
scheduleFrame() {
if (!this.rafId) {
this.rafId = requestAnimationFrame(() => {
this.rafId = null;
this.executeCallbacks();
});
}
}
executeCallbacks() {
this.callbacks.forEach(callback => {
try {
callback();
} catch (error) {
console.error('Animation error:', error);
}
});
}
}Вывод: requestAnimationFrame следует использовать для любых визуальных обновлений и анимаций, где важна плавность и производительность. Он обеспечивает оптимальную синхронизацию с браузерным рендерингом и автоматически адаптируется к системным ресурсам.