I built a 404 page which involves parallax scrolling text. The program updates the position of 100-200 text nodes at each animation frame called with window.requestAnimationFrame
. It runs decently well on my MacBook (although it occupies significant CPU), but on weaker computers like my phone, it runs very sluggishly.
How can I optimize my code to achieve better performance?
var scene;
function randint(low, high) { // Return a random integer between low and high
return Math.floor(( Math.random()*(high-low) ))+low;
}
// Add a layer to the scene
function addLayer() {
// Make a "layer" in the parallax animation
var layer = document.createElement("div");
// Give it a random Z value (0 to 1)
layer.setAttribute("class", "layer");
layer.setAttribute("data-z", Math.random().toFixed(2));
// Make a div inside that says "404"
layer.appendChild(document.createTextNode("404"));
// Random X position
layer.style.left = randint(-10, window.innerWidth) + "px";
// Below bottom of screen
layer.style.top = window.innerHeight + "px";
// Font size and weight based on Z value
var z = parseFloat(layer.getAttribute("data-z"));
layer.style.fontSize = Math.floor(z * 150) + "px";
layer.style.fontWeight = Math.ceil(z * 4) * 100;
// Random opacity
layer.style.opacity = (Math.random() / 10).toFixed(2);
// Add nodes to appropriate parents
scene.appendChild(layer);
}
function updatePositions() {
var layers = scene.getElementsByTagName("div");
var layer, div, z;
for (var i = 0; i < layers.length; i++) {
layer = layers[i];
z = parseFloat(layer.getAttribute("data-z"));
y = parseInt(layer.style.top.slice(0, layer.style.top.length - 2));
layer.style.top = (y - Math.ceil(z * 10)).toFixed(2) + "px";
if (y < (-150)) { // Max fontsize is 150px
scene.removeChild(layer);
}
}
}
function updateColor() {
var hue = (parseFloat(document.body.getAttribute("data-hue")) + 0.25) % 360;
document.body.setAttribute("data-hue", hue.toString());
var hex = chroma(hue, 1, 1, "hsv").brighten().hex();
document.body.style.color = hex;
document.getElementsByTagName("a")[0].style.backgroundColor = hex;
}
function update() {
addLayer();
updatePositions();
updateColor();
window.requestAnimationFrame(update); // Recur
}
scene = document.getElementById("scene");
window.requestAnimationFrame(update);
body {
background-color: #222;
margin: 0;
width: 100vw;
height: 100vh;
/* Global font settings */
color: #fff;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
#scene {
position: fixed;
top: 0;
left: 0;
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.layer {
position: absolute;
}
.main {
position: fixed;
top: 0;
left: 0;
width: 100%;
text-align: center;
opacity: 0.5;
}
h1 {
font-size: 200px;
margin-bottom: 0;
}
.main p {
font-size: 150%;
margin-top: 0;
}
.main a {
color: #222;
background-color: #fff;
padding: 10px;
text-decoration: none;
font-size: 150%;
font-weight: 600;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/1.1.1/chroma.min.js"></script>
<body data-hue="0">
<!-- Background -->
<div id="scene"></div>
<!-- Foreground -->
<div class="main">
<h1>404</h1>
<p>That page couldn't be found.</p>
<a href="http://luke.deentaylor.com/">Home</a>
</div>
</body>
Running this snippet in full window will more accurately emulate how it will appear on my website.
Here's a JSFiddle if it's preferred.
1 Answer 1
According to my experience, web animations don't use CPU as such, but rather prefer the GPU at times for rendering. On phones, custom animations like yours will run without any moderation of fps, and so a lag is kinda expected. On top of it, js doesn't use parallel threads of course, so it won't even use the remaining core if they are available.
What I'd suggest is use some good library to run these. I remember using Three.js and it ran on my Lumia 520 at 8FPS but still without lag, as the animation was optimized for low FPS. The same ran on my PC at 60FPS smoothly just the same.
Hope this helps.
removeChild
is slow -> re-use a container when 404 is out of view, do the same foraddLayer
. Also that coloring is slow, write your own HSL withoutsqrt
using formulas in easyrgb.com (PS. not a JS dev). \$\endgroup\$