A Beginner-friendly and practical Vanilla JavaScript tutorial. The big takeaway: how to write High-Performance Window Events that won’t kill our users’ hardware.
For: Complete Beginners to Intermediates
Today we’re building a glorious little app called “Viewport Dimensions”. This feature does one thing really well: it displays your browser viewport’s width and height values when you resize your browser window.
This an invaluable tool when you’re doing responsive design. CodePen has a similar feature. But I needed something bigger and some extra features, so I decided to build my own. I learned a ton in the process, especially about how to handle performance issues. Now I’m sharing that with you!
Viewport Dimensions
I don’t want to make assumptions about your existing knowledge. As a result, this is a highly detailed tutorial.
Technical topics:
- How to create elements with the
createElement()
method. - How to style elements with the
Style object
- How to setup and use the Window Object’s
resize
event. - How to get the width and height from your browser viewport, with the
innerWidth
andinnerHeight
properties. - How to call functions outside of functions (callbacks).
- How conditional (true/false)
if
statements work. - How to delay code execution with a timer, using
setTimeout()
- How to reset a timer with
clearTimeout()
. - How to use an event throttler function to dramatically improve the performance of window events.
- The relationship between MS & FPS (Milliseconds & Frames Per Second).
- The important difference between Perceived Performance and Hardware Performance.
The approach
We’re building two versions of our Viewport Dimensions app.
- Version 1 is about making the app work. It’s the draft, but not the final product.
- Version 2 is about improving version 1. We’ll learn how to make our code more performant, energy efficient, and easier to read.
To keep things JavaScript-centric, we will do everything in JavaScript code. By keeping everything in one JavaScript file, our app will also be easy to add to any existing project of yours.
Are you a complete beginner? Then I suggest you follow along using CodePen. You don’t even have to sign up to use it. I’ve published a pretty popular CodePen video tutorial that you can watch here.
How to learn
If you’re confused, go back a few paragraphs and make sure you didn’t skip anything important. You’ll learn more by trying to solve problems on your own first and then look for reference code second.
If you get lost you can always reference the finished code:
Let’s get started!
Viewport Dimensions [Version 1.0]
Before we start coding, let’s take a look at the project requirements.
Requirements
- It should have a container to display the window width & height.
- It should be positioned at the top right corner of our window.
- It should be hidden by default.
- It should show up as soon as the window gets resized.
- It should hide 3 seconds after the latest window resize.
That’s the essence of the feature requirements for version 1.
Let’s get started coding!
Creating the Display container and styling it
First, let’s create a div element for our Viewport Dimensions app, using the createElement
method and assign it to a variable:
var viewportDimensions = document.createElement("div");
We now have an empty <div></div>
element stored inside a variable.
In the browser, all HTML elements are represented by Element objects. These Element objects have properties and methods that we can access with JavaScript.
We want our viewport width and height displayed at the top right corner of our browser window. Let’s take care of that and give our div element some styling by accessing the HTML DOM Style object properties:
viewportDimensions.style.position = "fixed";
viewportDimensions.style.right = "0";
viewportDimensions.style.top = "0";
viewportDimensions.style.padding = "16px";
viewportDimensions.style.zIndex = "3";
viewportDimensions.style.fontSize = "22px";
The syntax above is dot notation. In simple terms, it means that we use .
to access an object’s properties or methods. So in the example above, we’re saying:
- Hey
viewportDimensions
- Give me access to the
Style Object
- So I can access its styling properties
Styling with the Style object is like styling with CSS. But the Style object uses inline style. The styling properties gets added directly to the HTML element, not in an external CSS stylesheet.
Let’s confirm that our code works, by appending our Viewport Dimensions <div>
element to the <body>
element of our HTML document.
First, let’s declare a variable to store a reference to our document’s body element. Add the following code at the top of your JavaScript file.
var body = document.querySelector("body");
Now let’s add our Viewport Dimension’s div element inside the body element. Put the following code at the bottom of your JavaScript file (below the last style:
body.appendChild(viewportDimensions);
appendChild()
is a method that appends (adds) a node inside another node. Everything in the DOM is a node, and there are different types of nodes. In this case, we’re adding a div element node, inside the body element node.
Since our div element doesn’t have any content yet, we can’t see if our current code even works. We can quickly find out by adding a temporary border to the element:
viewportDimensions.style.border = "10px solid";
If it works you should now a see a small box with a thick black border in the top right corner of your window:
You can also check by right-clicking in your browser window. Then click on Inspect to access your browser DevTools.
Now under the elements tab, locate your body
element, and you should see an empty div element, with some inline style properties.
Here’s an overview of all our code so far:
// Select body element
var body = document.querySelector("body");
// Create div element
var viewportDimensions = document.createElement("div");
// Style element
viewportDimensions.style.position = "fixed";
viewportDimensions.style.right = "0";
viewportDimensions.style.top = "0";
viewportDimensions.style.padding = "16px";
viewportDimensions.style.zIndex = "3";
viewportDimensions.style.fontSize = "22px";
// Add div element inside body element
body.appendChild(viewportDimensions);
Great, our div element has been created, styled, and appended inside our body element. Next up is the Window Resize Event.
Setting up the Window Resize Event
The Window Object represents your browser window. It has several methods, including addEventListener
. This method can listen to many types of window events, such as scrolling, clicking, typing, and resizing.
We’re going to use the 'resize'
event. Below body.appendChild
add the following:
window.addEventListener("resize", function() {
// Code to execute when window resizes
});
Note: comments //
are not interpreted by the browser, they’re only there for us humans
Earlier we used dot notation to grab the properties of the div element. Now we use it to access a method on the window object: addEventListener
.
We’re passing two arguments to the event listener: 'resize'
and function()
. We already discussed the resize event, but what’s the function for?
The function()
sets up the code block that we want to execute every time the window resize event is active (as soon as you resize your window). The code block is what’s inside the curly braces { }
(also referred to as the function body) which so far only has a // comment
.
Every time we call the window event, it tells function()
to run the code inside the curly braces { }
.
To confirm that it’s working, let’s test it using console.log()
:
window.addEventListener("resize", function() {
// Code to execute when window resizes
console.log("Working!");
});
Now resize your window. If your console prints 'Working!' a bunch of times, it means that your window resize event is working.
Accessing the Console
To access the console in CodePen, click the Console button at the bottom left corner of your window. To access the console in Chrome, here are the keyboard shortcuts:
- Cmd + Opt + J (Mac).
- Ctrl + Shift + J (Windows / Linux)
Alright, let’s put our Window Event to work!
Returning browser width & height in pixels
The next move is to grab the numeric values of the viewport width and height. We want the values to update instantly, as we resize our window. The Window Object represents your browser window. It has a couple of glorious properties called innerWidth
and innerHeight
.
Guess what they do?
Yup, they return your browser window’s width and height in pixels (including scrollbars). Let’s confirm this, and use console.log()
to return those width and height property values.
Since we want it to grab the current value dynamically we need to put our console.log()
’s inside the resize event’s function block:
window.addEventListener("resize", function() {
// Code to execute when window resizes
console.log("Width:", window.innerWidth);
console.log("Height:", window.innerHeight);
});
The 'Width:'
and 'Height:'
text in the console.log()
’s are just there to make the console output clear.
We actually don’t have to write window.
to grab the width & height values, just innerWidth
and innerHeight
will work. I do it to avoid confusion.
If it works your console will now constantly print your current viewport’s dimensions as long as you resize your browser window.
It should look like this:
Now we’re getting somewhere!
Let’s get rid of the console.log()
and instead assign the width and height properties to their own variable so we can store a reference to them:
window.addEventListener("resize", function() {
// Code to execute when window resizes
var width = window.innerWidth;
var height = window.innerHeight;
});
Now width
and height
will always contain the current values of the window width and height property. You don’t always have to assign returned values to a variable, but it’s a good practice since it allows you to use or change that information later.
To confirm that your new variables work as they should, try to print those new variables to the console:
window.addEventListener("resize", function() {
// Code to execute when window resizes
var width = window.innerWidth;
var height = window.innerHeight;
console.log(width);
console.log(height);
});
If everything works, you can remove your console.log’s, and move on.
Adding width and height values to the div element
We want to add the returned width and height values to our div element. More specifically we want to embed the values inside our div elements opening and closing tags:
<div> We want width & height values here </div>
To do that we need to grab the variable that stores our div element: viewportDimensions
. Then we need to access a property on our element object, called textContent
. We use the same dot notation from earlier:
viewportDimensions.textContent;
Now we just need to assign our width and height variables:
viewportDimensions.textContent = width + "px" + " x " + height + "px";
Try resizing your browser window now. If you did everything right it should now display the current width and height values, at the top right corner of your window.
I know the code syntax we used above can look a bit confusing.
Let’s break it down:
textContent
is a property of the element object. Any element in a document has it, including our Viewport Dimensions div element.
textContent
returns a node, specifically a text node. For example: the text inside a paragraph element: <p>Some text...</p>
, is a text node.
Our viewportDimensions
variable contained an empty <div>
element. Now it returns a text node with numeric pixel values between the opening and closing div element tag:
width + "px" + " x " + height + "px";
width
and height
both contain a pixel number value. We only add the 'px'
and ' x '
(strings) to separate the width and height variables, and to make it clear to everyone that the values we return are in pixels.
Adding a countdown timer with setTimeout()
We don’t want to see the width and height displayed constantly when we’re not resizing our window. Let’s make the Viewport Dimensions’ div element go away exactly three seconds after we stop resizing our browser window.
To do that we need to use another method of the Window Object, called setTimeout
. This method is designed to execute a function()
once after a specified number of milliseconds.
It works like this:
setTimeout(functionToExecute, 3000);
1000 ms = 1 second. So the 3000 above means 3 seconds.
What function do we want to execute? One that removes our Viewport Dimensions’ div element.
We don’t have such a function yet, let’s create one and call it removeViewportDimensions()
and inside its code block { }
let’s tell it to remove our div element.
Add the following code above your window event function:
function removeViewportDimensions() {
viewportDimensions.style.display = "none";
}
The display
property specifies how or if an element is displayed. By setting it to 'none'
, we’re hiding it.
Now we need to call our new removeViewportDimensions()
function, from inside our window event code block. Using the setTimeout()
format from earlier, add the following:
setTimeout(removeViewportDimensions, 3000);
Now everytime the window resize event is active, setTimeout
sets a countdown of 3 seconds. In other words: we delay the call to removeViewportDimensions()
by 3 seconds.
When the 3 seconds are up, the removeViewportDimensions()
function is called, and executes its code:
Now our div element’s display property is set to 'none'
, and is hidden.
But we have a problem
If you try to resize your browser window a second time, right after the div element has been removed, then the div element won’t show up again. That’s because the last thing that happens in our program is that our div element gets its display
property set to 'none'
.
There’s nowhere in our program where we state that we want our div element to be visible again.
But then why was it visible the first time? Because by default a <div>
element is a so-called block-level element. This means that its default display
property value is 'block'
— which is a visible display type.
The very last thing we do to our div element is setting its display value to 'none'
. So that value will still be on the element when we run the resize event the second time. Unless you refresh your page.
This is easy to fix. Add the following inside your window resize event’s code block (above setTimeout
!):
viewportDimensions.style.display = "block";
Now our div element’s display
property is set to 'block'
every time the resize event runs. Try to resize your window, wait for it to hide the div element, and then resize again.
Awesome, our app works!
Well.. sort of. We have another problem. If you try resizing your browser window for an extended period (more than 3 seconds) then the div element will start flickering. It’s as if it was a light getting turned on and off constantly.
How unworthy of our app!It happens, because we’re not resetting our countdown timer. Every time we resize our viewport, the window event executes its function code block as many times as it can. So our removeViewportDimensions()
function is also called many times.
This causes our app to have a bunch of overlapping code execution. Our div element is getting hammered with the display = 'none'
property and the display = 'block'
property at the same time.
This makes our div element show and hide constantly, and causes the flickering. This sucks. To solve this problem, we need to learn how clearing timers work in JavaScript.
Resetting the countdown timer
We can clear existing timers by using the clearTimeout()
method. This method is designed to clear a timer that has been set with the setTimeout
method. They are like partner methods.
Clearing existing timers is exactly what we want. We only want to run a setTimeout()
timer once per resize event.
You might think that we could run clearTimeout()
on setTimeout()
like this:
clearTimeout(setTimeout);
But that won’t work. To use the clearTimeout() method, you have to use a global variable when creating the timeout method.
JavaScript has two scopes: global & local:
- A global variable is a variable declared outside a function (in the global scope).
- A local variable is a variable declared inside a function (local scope).
This means that if we want to clear setTimeout()
we need to assign it to a global variable first, and then we can call clearTimeout()
on that variable to reset it.
Let’s create our global variable, and call it resizeTimeout
:
var resizeTimeout;
Put this variable at the very top of your JavaScript file.
Right now it’s just an empty variable. It will only be empty until the first time our resize event has started.
If you try using console.log(resizeTimeout)
you will get undefined returned. undefined
is a false value type, which is important. You’ll understand why, soon.
Now, take your setTimeout()
function from earlier and assign it to the resizeTimeout
variable:
resizeTimeout = setTimeout(removeViewportDimensions, 3000);
Now the global resizeTimeout
variable will receive information about our setTimeout
function, as soon as the resize event is active.
Now we can use setTimeout()
every time our window resize event is running. Set clearTimeout()
on the global resizeTimeout
variable. Like this:
clearTimeout(resizeTimeout);
Put the code right above your resizeTimeout = setTimeout()
so it looks like this:
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(removeViewportDimensions, 3000);
Okay, now run your resize event for 5-6 seconds, and you’ll see that the flickering is gone!
Confused? Let’s talk about what happens in the code.
The JavaScript interpreter (an engine that runs in your browser) will read your code and:
- execute it line for line
- one line at a time
- from top to bottom
Our code will continue to be executed, from top to bottom, as long as we keep resizing our window. Every tiny amount of resizing will fire a new window event. So the JavaScript interpreter reads through and executes our code many times per second.
Every time our window event’s function executes its code block, clearTimeout()
will reset any existing countdown in the queue, on the resizeTimeout
variable.
On the last line of code, we assign setTimeout()
.
So the last thing that happens will always be one countdown of 3 seconds that get’s assigned to resizeTimeout
. Any initiated countdowns before that will be wiped out by clearTimeout()
.
Awesome! Version 1 is done. You can view the complete code here. Let’s move on to Version 2, where we’re specifically going to focus on performance.
Viewport Dimensions [Version 2]
Our app works, but is it good enough? To answer that question we need to take two important things into consideration:
- Perceived performance: the user experience (UX).
- Hardware performance (usage & efficiency).
From a UX point of view, I think our app performs great. It has a blazing fast response time, and it renders very well, even on old screens (I tested on a couple of decade-old veterans). We can always argue about optimizing the visuals (UI Design). But based on pure functionality, our app works well.
But from a hardware performance perspective, our app is really bad. Our app is using a ton of CPU and GPU resources, compared to how modest its technical requirements are.
Can you figure out why? It’s not something you instinctively think about when you’re not familiar with the gluttonous nature of the Window Event.
Here’s the problem:
Every time we resize our browser window, our app’s window event is executing its function hundreds of times. In just a few seconds!
I put up a function on CodePen that visualizes the problem. Notice how many functions are executed per second. Also watch the total count blow through the roof, fast:
This is horrible and totally unnecessary! Our code is using way more resources than it should.
To see how much your CPU and GPU are working during browser events, you can launch Chrome’s Task Manager:
Try resizing your browser window. Inside Task Manager, you will get a CPU & GPU % usage returned next to your active browser tab.
Absolute vs. Relative Hardware Usage
It’s important to differentiate between absolute and relative hardware usage. From an absolute (total) hardware usage viewpoint, our tiny app won’t do much harm. Most computers are way too powerful to get shaken by this.
But from a relative usage perspective, our app is horribly inefficient. And that’s the problem. We never want to get into the habit of neglecting performance, as it will haunt us, and come back to bite us in the ass.
As soon as you start working with applications that execute heavy functions, hardware usage becomes a serious issue. It won’t be a pretty sight if we execute code like we do right now.
How the Window Event executes
By default, a standard window event will execute functions as many times as it can, as long as the event is active. Every tiny change will trigger a new window event. This is what causes the ridiculously high execution rate that our app has right now.
How many times do functions execute per second?
It could be anything from every 4 to every 40 milliseconds. It depends on a lot of factors, including:
- How powerful is your CPU and GPU (hardware)?
- Which browser are you using?
- How complex is the software (code) are you running? Does it involve animations?
- What else is your hardware doing at the moment? Got a YouTube video running in another tab?
Your hardware uses whatever resources it has available at the given time that you ask it to do something.
Sometimes it won’t have enough free resources to immediately respond to your request because a ton of apps is running at the same time.
This can cause slow response times, screen glitches, freezing, and other issues.
Hardware usage adds up. This is why we need to fix the current state of our application. Because we care about performance!
Note: in modern browsers, the minimum execution rate is 4ms which in terms of frame rate is around 250fps. This is according to Mozilla’s Web API docs.
But there’s conflicting information on this topic, more info here.
How often “should” a function execute?
The ideal function execution rate depends on the context. Are we working with a detailed animation? Or something simple, like returning a couple of numeric values (like we do with our app)?
Regardless of the context, we generally don’t want window events to execute functions as many times as possible. That’s absurd. It’s also not free. We must stop it!
We can control execution frequency, by using a process called throttling. That’s coming up next.
Event Throttling to the rescue!
We can use a throttling function to restrict how many times another function executes over time. E.g.: “only call this function once per 100 ms”.
Let’s take a look at a basic event throttler function, which get’s triggered by the window resize event. The throttler then calls an outside function every 100 ms.
Basic Throttler Function:
var throttled;
var delay = 100;
function functionToExecute() {
console.log("Executed!");
}
function throttler() {
if (!throttled) {
functionToExecute();
throttled = true;
setTimeout(function() {
throttled = false;
}, delay);
}
}
window.addEventListener("resize", throttler);
I’ve added some extra white space to the code formatting to make it easier to read. Try to read through the code a couple of times, and see if you understand it.
Use this CodePen to test it. You’ll see that we call functionToExecute()
up to 10 times per second (10 x 100 = 1000 ms = 1 second).
I say up to because a function won’t always fire exactly at the rate you set it to. It depends on how busy your hardware is at the time your function is running. Are you running other energy taxing apps on your computer?.
What’s happening in the throttler code:
Don’t worry if you don’t understand this the first time. I’ve spent a lot of time messing with this stuff until it finally clicked.
At the very top of our JavaScript file, we start by declaring two global variables:
var throttled; // Empty variable. Type: undefined (falsy value)
var delay = 100; // Number
Note: global variables, means that we can access these variables from anywhere.
We use throttled
as a reference point for our throttler()
function, to check if there’s a function in the queue.
Before our throttler()
function gets triggered the first time (by the resize event), there won’t be any functions to execute in the queue. So throttled
starts out as an empty variable.
By default, empty variables have a value type of undefined
— which is a falsy value type. You’ll see why this is important in a moment.
We then assign a numeric value (100) to a variable called delay
. This is the value we use to regulate the ms (millisecond) execution rate of our throttler()
function.
We assign 100ms to the delay
variable. Then we pass that variable to our throttler’s setTimeout()
. Our throttler will then run every 100ms or 10 times per second — at most.
I say at most because 100ms puts a cap on our execution rate. We may actually get less than 10 function calls per second, depending on our user’s hardware and software. But most computers will be able to fire 10 times per second.
Inside the throttler function, we set up a conditional if
statement:
function throttler() {
if (!throttled) {
// code to execute
}
}
Conditional statements check if a specified condition has a truthy value.
The !
symbol means not
. So if (!throttled)
translates to:
“If throttled does not have a truthy value, then run if
’s code”.
“But if throttled
does have a truthy value, then stop right there!”
What are True & False values in JavaScript?
In JavaScript, there are truthy and falsy values. Yep, that’s what they’re called. Truthy is a value that is true. Falsy is a value that is considered false (surprise!).
In JavaScript there are six falsy values:
undefined
null
0
''
(empty string)NaN
false
In JavaScript, anything that is not one of the above values is considered a truthy value. That’s pretty simple, right?
Back to the throttler
With that quick lesson on true vs. false, go ahead and check if our throttled
variable is indeed undefined, and a falsy value type.
We can quickly find out with console.log()
by using !!
to check our variable:
var throttled;
console.log(throttled); // undefined
console.log(!!throttled); // false value
!!
is a double not
operation. It’s a quick way to check for truthy and falsy values.
Great, now we have confirmed that throttled
is undefined, and a falsy value type. This means that the very first time our throttler()
runs, if
’s condition is indeed met.
// I start out as a falsy value
var throttled;
// I’m triggered by the window resize event
function throttler() {
// I will only run my { code } if throttled is falsy (it is!)
if (!throttled) {
// code to execute
}
}
Which means that we can move onto what happens inside if’s
code block.
Inside if’s code block
Once inside the if
statement code block, we immediately start the callback to functionToExecute
.
We also set our global throttled variable to true, so it’s no longer undefined
(no longer a false value). This stops any future calls to functionToExecute()
.
if (!throttled) {
// Run callback
functionToExecute();
// Set global throttled variable to true
throttled = true;
// ...
}
But then right below, we use setTimeout
to reset the the throttled
value back to a falsy value, after the 100ms delay:
function throttler() {
if (!throttled) {
// Run callback
functionToExecute();
// Set global throttled variable to true
throttled = true;
setTimeout(function() {
// Set throttled back to false after delay
throttled = false;
}, delay);
}
}
So every time the window resize event is running, it triggers the throttler()
, which will call functionToExecute
every 100ms!
Throttling is power
It’s easier to understand how powerful throttling is if you look at two opposite extremes. Try the following in the throttler function on CodePen:
- Swap 100 out with 500 and resize your window. Notice how infrequent the
functionToExecute()
is called (twice per second). - Then try to swap out 500 with 16 and resize the window. Now
functionToExecute()
is bombarded with calls (up to 60 calls per second).
That’s how throttling works. We use it to limit the number of times a function can be called over time. It’s a very easy way to avoid wasting hardware resources. The hard part is finding the “optimal” execution rate for a given function, as no project is the same. More about that later.
Adding the throttler to our project
To apply the throttler function to our own project, we’ll need to do a bit of refactoring to the old code first. Refactoring is when we improve the structure of a program but don’t change the overall functionality.
In other words: we take something that already works, and makes it easier to use and understand.
We no longer want the window resize event to directly execute the function that returns width and height. Instead, we want our throttler function to regulate when and how often the code executes.
Fortunately, the change is simple. We just need to declare a named function and then put all code from the anonymous function inside that named function.
Let’s call the new function getViewportDimensions()
:
function getViewportDimensions() {
var width = window.innerWidth;
var height = window.innerHeight;
viewportDimensions.textContent = width + "px" + " x " + height + "px";
viewportDimensions.style.display = "block";
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(removeViewportDimensions, 3000);
}
Now let’s take the same throttler function from earlier, and replace the functionToExecute()
with getViewportDimensions()
:
function throttler() {
if (!throttled) {
// Callback: the function we want to throttle
getViewportDimensions();
throttled = true;
setTimeout(function() {
throttled = false;
}, delay);
}
}
And finally we add the window resize event listener:
window.addEventListener("resize", throttler);
That’s it, all the hard work is done! Before we wrap up, let’s check our app’s performance, and see if we need to tweak anything.
Hardware Performance vs. Perceived Performance
I don’t know about you, but when I resize my browser window, the width and height numbers are rendering a bit laggy to my screen. It’s not as smooth as it was before we added the throttler.
But that’s not surprising. Before we added the throttler, our code’s execution rate was absurdly high. Now it’s firing around 10 times per second. That’s why it’s a bit laggy now.
There’s no doubt that our app is very performant, from a hardware usage perspective. Our hardware hardly does any work, so it absolutely loves us right now. Which is good.
But what about the perceived performance, from the User Experience point of view? Do our users love us too?
Our rendering should be smoother. Our function execution rate is currently capped at 10 times per second. Let’s increase it. But how much?
We can make it easier to make that decision if we first take a look at the relationship between time and frame rate.
Time vs. Frame Rate
Frame rate is also known as FPS (Frames Per Second). It refers to the number of individual frames displayed per second.
In general, the higher fps the smoother the rendering will be. Video games often need 60fps or more to render smoothly. Most movies and tv series run smoothly at 24-30fps. The more fast-paced action a video or graphic has, the more FPS it needs to render smooth.
For some types of graphics, you can go as low as 15fps and still get a smooth render. But you should rarely go below 15fps because then the perceived performance will start to look noticeably slower.
To find your desired FPS in ms, you divide your target fps with 1000, e.g. 1000/15(fps) = 66ms.
Take a look at this FPS to millisecond table:
var 2fps = 1000/2; // 500ms
var 10fps = 1000/10; // 100ms
var 15fps = 1000/15; // 66ms
var 24fps = 1000/24; // 42ms
var 30fps = 1000/30; // 33ms
var 60fps = 1000/60; // 16ms
Do you recognize something? Our app is currently executing every 100ms, or in frame rate terms: 10 frames per second.
Remember, I just said that anything below 15fps will usually appear slow? That could explain why current app’s rendering feels a bit off. There are not enough frames per second to render it smooth. It doesn’t feel instant.
Let’s do some testing
I’ll guarantee that if we crank our Viewport Dimensions feature up to run at 15fps (66ms), it will look significantly smoother than at the current 10fps (100ms).
And it won’t cost too much extra CPU power.
We only need it to be smooth enough.
Like I said earlier, testing opposite extremes is a good way to find out what not to do. Then the question becomes where between the two extremes does your project fit?
Try testing the following frame rates: 5fps (500ms), 15fps (66ms), and 60fps (16ms) on your Viewport Dimensions app and see what happens.
What do you think?
5fps looks horrible. It looks broken. That would never work. Our hardware would love it, but our users will hate it!
15fps looks good to me. I wouldn't say that this frame rate is 100% smooth, but it’s clearly better than 10fps.
It’s also important to remember that if your eyes are trained to look for small details, it’s not comparable to the average user’s experience. Most people will consider 15fps smooth, for this type of graphic.
60fps is super smooth — not surprisingly. But honestly, it’s not worth massively extra hardware usage.
I’m going to settle at 15fps for my app.
Why 15 FPS?
The reason I decided on 15fps / 66ms, is because that frame rate is high enough to be perceived as smooth (for most people).
Most software, computers, and displays (screens) can render at 15fps without problems. Even most small, handheld devices can handle 15fps without issue.
The irony of high frame rates is that while logic tells us that more frames per second = smoother, it can also have the opposite effect.
If you try to force a frame rate or refresh rate that your user’s hardware and software can’t handle, the result is often: lagging.
Refresh rate: is how many times your display can redraw your screen. A refresh rate of 45Hz means that your display can redraw it’s entire screen 45 times in 1 second.
Now if your CPU & GPU can generate a frame rate of 60fps, but your display’s refresh rate is only 45Hz, then you have a mismatch. This often causes unpredictable results.
We developers and designers can easily get out of touch with reality. Our development machines are usually more powerful than the average person’s computer.
This is why we need to do solid research and test our assumptions before we push products on the market.
So 15fps is a happy medium that will also work for people who don’t have powerful machines.
Using 15fps will also save us at least 75% of the energy our hardware used before we added the throttler.
Anything below 15fps will usually start to look slow and laggy. It becomes really obvious once you get to around 10fps, which is why our initial frame rate wasn’t good enough.
Here’s an insightful video on the topic of response times which supports my argument for not going below 15fps (on most things).
Context & the Minimum Effective Dose
How much of a perceived difference is there from 15 to 30fps? If you test it out on the app we just built, you’ll be surprised.
You probably won’t be to notice any significant difference. Not a deal-breaking difference. But the price you pay for hardware usage is double up!
High fps costs a lot of hardware power. So why use more than necessary? There’s always a point of diminishing returns. That’s where the Minimum Effective Dose (MED) comes in. You only need enough, to get the desired effect, no more, no less.
Of course, it always depends on the context. Are we talking about an explosive first-person shooter video game or a simple pixel counter like the one we’ve built? Context is key!
A Finishing Touch
All the hard work is done. For good measure, let’s wrap all our code inside an IIFE ((function () {...})();
) and initialize use strict
mode inside:
(function() {
"use strict";
// Put all your code below
})();
IIFE (Immediately-Invoked Function Expression), keeps all your code inside a local scope.
If we don’t wrap our code like this, we risk “polluting the global namespace”. This is a fancy way of saying that we risk causing conflicts with other code, outside of our app.
use strict;
is also used to prevent conflicts with 3rd party software that don’t follow strict coding rules.
We’re done!
Here is all the code we’ve written. I've added some comments for clarification and some extra white space for readability:
Finished project code:
(function() {
"use strict";
// throttled value before window event starts
var throttled;
// Delay function calls to 66ms (frame rate: 15fps)
var delay = 66;
// Declare empty variable for later use
var resizeTimeout;
// Grab body element
var body = document.querySelector("body");
// Create element
var viewportDimensions = document.createElement("div");
// Style element
viewportDimensions.style.position = "fixed";
viewportDimensions.style.right = 0;
viewportDimensions.style.top = 0;
viewportDimensions.style.padding = "16px";
viewportDimensions.style.zIndex = 3;
viewportDimensions.style.fontSize = "22px";
// Add div element inside body element
body.appendChild(viewportDimensions);
// window.resize callback function
function getViewportDimensions() {
var width = window.innerWidth;
var height = window.innerHeight;
viewportDimensions.textContent = width + "px" + " x " + height + "px";
viewportDimensions.style.display = "block";
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(removeViewportDimensions, 3000);
}
// Called from getViewportDimensions()
function removeViewportDimensions() {
viewportDimensions.style.display = "none";
}
function throttler() {
// Only run code if’s block if we’re not throttled
if (!throttled) {
// Callback: the function we want to throttle
getViewportDimensions();
// We're currently throttled!
throttled = true;
// Reset throttled variable back to false after delay
setTimeout(function() {
throttled = false;
}, delay);
}
}
// Listen to resize event, and run throttler()
window.addEventListener("resize", throttler);
// End of IFFE wrapper
})();
Here’s a CodePen you can play with.
How to use your app on any project
Include a script tag at the bottom of your HTML document, just above your closing body tag (</body>
) and add the path to your js file, like this:
<script src="/js/get-viewport-dimensions.js"></script>
Browser Compatibility
Our app is compatible with all modern browsers, and from Internet Explorer 9 and up.
Advanced throttling
What we learned in this tutorial is a great precursor for some the more powerful performance tools on the market.
If you need to boost performance on more complex projects than the one we built, check out the Lodash JavaScript library. Lodash has a lot of performance features and is very popular among developers. You can also learn a lot from digging through the library’s code.