Back in the days I thought it was required to rerender 20-60 times per second on any graphics application. That may be true for most games, but many applications do not!
Vladimír showed me differently: the Magnum Primitives Example only renders after mouse interaction changed the rotation of the object.
In the linked WebGL demo of this example, this will have saved some of your battery capacity, if you looked at it on a phone.
Sometimes, rendering continously is not avoidible, though. On the new Vhite Rabbit Website, we have an interactive crystal mesh, which gradually rotates to signal it’s interactible. What can we do to save your battery, so that you can look at our website longer?
The biggest issue is that the element renders even if offscreen. The element is placed in the lower half of the page, which means you may not see it, but it still wasted your battery life. How about not rendering when offscreen then?
Render WebGL Only If Visible
We aim for some way to detect whether an element became visible or hidden after a scroll event. To check if the element is currently inside the viewport, we can use the following javascript snippet:
function isElementInViewport(el) { var rect = el.getBoundingClientRect(); return rect.bottom >= 0 && rect.top <= (window.innerHeight || document.documentElement.clientHeight); }
This only checks the vertical axis only. As horizontally scrolling websites are not popular at time of writing. You’ll be able to adapt the code if needed.
We now use this function to create an event for visibility changed 2.
function onVisibilityChange(el, callback) { var oldVisible = null; return function () { var visible = isElementInViewport(el); if (visible != oldVisible) { oldVisible = visible; if (typeof callback == 'function') { callback(visible); } } } }
All set up for adapting our rendering code, here is the optimization that will turn off rendering if our WebGL canvas is offscreen:
function render() { redraw(); /* ... */ } 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) { redraw(); } else if(animationFrame != null) { /* Stop rendering */ cancelAnimationFrame(animationFrame); } }); addEventListener('DOMContentLoaded', visibilityChangedHandler, false); addEventListener('load', visibilityChangedHandler, false); addEventListener('scroll', visibilityChangedHandler, false); addEventListener('resize', visibilityChangedHandler, false);
This will request a next frame every frame. Hence, continously renders until the visibilityChanged
callback is called with false
, which will cancel the last requested animation frame.
To avoid a race condition, we request the next frame before rendering. This avoids cancelation of
the frame which is being rendered (which does nothing) and then a new frame being requested after all.
May this have saved some of your user’s batteries!
- 2
- Adapted from this snippet on stackoverflow.