Back in the days I thought it was re­quired to reren­der 20-60 times per sec­ond on any graph­ics ap­pli­ca­tion. That may be true for most games, but many ap­pli­ca­tions do not!

Vladimír showed me dif­fer­ent­ly: the Mag­num Prim­i­tives Ex­am­ple on­ly ren­ders af­ter mouse in­ter­ac­tion changed the ro­ta­tion of the ob­ject.

In the linked We­bGL de­mo of this ex­am­ple, this will have saved some of your bat­tery ca­pac­i­ty, if you looked at it on a phone.

Some­times, ren­der­ing con­ti­nous­ly is not avoidi­ble, though. On the new Vhite Rab­bit Web­site, we have an in­ter­ac­tive crys­tal mesh, which grad­u­al­ly ro­tates to sig­nal it’s in­ter­actible. What can we do to save your bat­tery, so that you can look at our web­site longer?

The big­gest is­sue is that the el­e­ment ren­ders even if off­screen. The el­e­ment is placed in the low­er half of the page, which means you may not see it, but it still wast­ed your bat­tery life. How about not ren­der­ing when off­screen then?

Render WebGL Only If Visible

We aim for some way to de­tect whether an el­e­ment be­came vis­i­ble or hid­den af­ter a scroll event. To check if the el­e­ment is cur­rent­ly in­side the view­port, we can use the fol­low­ing javascript snip­pet:

function isElementInViewport(el) {
   var rect = el.getBoundingClientRect();
   return rect.bottom >= 0 && <= (window.innerHeight || document.documentElement.clientHeight);

This on­ly checks the ver­ti­cal ax­is on­ly. As hor­i­zon­tal­ly scrolling web­sites are not pop­u­lar at time of writ­ing. You’ll be able to adapt the code if need­ed.

We now use this func­tion to cre­ate an event for vis­i­bil­i­ty changed 2.

function onVisibilityChange(el, callback) {
   var oldVisible = null;
   return function () {
      var visible = isElementInViewport(el);
      if (visible != oldVisible) {
         oldVisible = visible;
         if (typeof callback == 'function') {

All set up for adapt­ing our ren­der­ing code, here is the op­ti­miza­tion that will turn off ren­der­ing if our We­bGL can­vas is off­screen:

function render() {

   /* ... */

 var animationFrame = null;
 function redraw() {
     /* Assuming your render function is called "render()" */
     animationFrame = requestAnimationFrame(render);

 /* Add visibility listener to the WebGL canvas */
 var visibilityChangedHandler = onVisibilityChange(canvas, function(visible) {
     if(visible) {
     } else if(animationFrame != null) {
         /* Stop rendering */

 addEventListener('DOMContentLoaded', visibilityChangedHandler, false);
 addEventListener('load', visibilityChangedHandler, false);
 addEventListener('scroll', visibilityChangedHandler, false);
 addEventListener('resize', visibilityChangedHandler, false);

This will re­quest a next frame ev­ery frame. Hence, con­ti­nous­ly ren­ders un­til the visibilityChanged call­back is called with false, which will can­cel the last re­quest­ed an­i­ma­tion frame. To avoid a race con­di­tion, we re­quest the next frame be­fore ren­der­ing. This avoids can­ce­la­tion of the frame which is be­ing ren­dered (which does noth­ing) and then a new frame be­ing re­quest­ed af­ter all.

May this have saved some of your us­er’s bat­ter­ies!

Adapt­ed from this snip­pet on stack­over­flow.