Performance smooth with animate() enabled, jumpy when animate() disabled

I’ve got a Radeon RX 560 (reasonably modern) and am rendering the simple green cube from the main example docs along with stats.js and OrbitControls in up-to-date Chrome on Linux. The very simple full JS is at the bottom.

I’ve noticed something strange: When animate() is enabled, I can use the controls (rotate, zoom) and everything is silky smooth (triple-digit FPS). When I turn animate() off, my controls become very jumpy and the frame rate drops to single digits for a second or two when I do the same thing.

Why would this be the case? Any ideas?

(More generally, I’m investigating performance bottlenecks with a larger application and seeking to understand, more thoroughly, how all of this works and what I should reasonably expect.)

var scene;
var camera;
var renderer;
var controls;
var geometry;
var material;
var cube;
var stats;
var mouse;

function createStats() {
	stats = new Stats();
	stats.setMode(0); = 'absolute'; = '0'; = '0';

	return stats;

function render() {
	renderer.render(scene, camera);

const animate = function() {

	cube.rotation.x += 0.01;
	cube.rotation.y += 0.01;


function onMouseMove(event) {

	mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
	mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;


function onWindowResize() {

	camera.aspect = window.innerWidth / window.innerHeight;

	renderer.setSize(window.innerWidth, window.innerHeight);



$(document).ready(function() {

	mouse = new THREE.Vector2();

	renderer = new THREE.WebGLRenderer();
	renderer.setSize(window.innerWidth, window.innerHeight);

	scene = new THREE.Scene();

	camera = new THREE.PerspectiveCamera(75, window.innerWidth
		/ window.innerHeight, 0.1, 1000);

	controls = new THREE.OrbitControls(camera, renderer.domElement);

	geometry = new THREE.BoxGeometry();
	material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
	cube = new THREE.Mesh(geometry, material);

	camera.position.z = 5;

	stats = createStats();

	window.addEventListener('resize', onWindowResize, false);
	window.addEventListener('mousemove', onMouseMove, false);

	//	animate();

In the code snippet above, you only render once before animate() … framerate will not be limited by WebGL if you aren’t rendering anything after that. :wink: Two common ways to control rendering:

  1. use requestAnimationFrame to render on every frame, and optimize your scene’s content so that you can maintain 60fps.
  2. use controls.addEventListener( 'change', render ); to render only when the controls have changed. if the scene itself doesn’t animate, other than controls, this is more resource efficient.

In neither case should framerate be in the triple digits, most browsers clamp this to 60fps unless you have a VR display device or something, in which case 120fps may be possible.

Thanks. I swapped in

controls.addEventListener( ‘change’, render );


window.addEventListener(‘mousemove’, onMouseMove, false);

And it seemed to help. Not nearly as many jitters on this simple cube. Any technical insight into why this would be the case?

I see, I’d missed the mousemove render. My understanding is that mouse events can happen more often than the browser will actually re-render the page, and aren’t necessarily timed to align with the browser’s render loop. With requestAnimationFrame you avoid those issues, so another way to set this up would be to store the mouse.x/y data in the mousemove handler but wait to re-render until rAF happens.

Where would I put requestAnimationFrame in my code?

If controls are the only thing that cause you to need to re-render, then just using controls.addEventListener( ‘change’, render ); is best.

If you wanted the scene to continuously animate without interaction, then just call animate() (as you have it set up now) and don’t re-render when mouse events happen.

1 Like