use requestAnimationFrame for better performance