DIY overlay

You’ve wanted to show something on your screen on several occasions, preferably something dynamic. But every time you wanted to, you had to rely on someone else making it for you.

The guide will show you how making your own overlay isn’t as daunting as the lack of tutorials would have you think.

All the BrowserSources you will ever make are basically a webpage with a transparent background that you refresh every x seconds. That’s really it.

We’ll use a countdown as our example overlay.

The basic idea of our countdown is that it will be in the shown in the top right corner of our stream as measured in days, hours, and minutes. Showing the seconds will be way too annoying.

Getting started

Your overlay will be a local .html file somewhere on your computer. Create one now; name it whatever.

Create a new simple scene in OBS. Then create a new BrowsersSource.

In the BrowserSource config, tick Local file and point it to the .html file you just created. Use your monitor width and height—not your stream resolution—and set the FPS to the framerate you stream at. Be careful that scrolling with your mouse can change the values in the fields if your cursor is on them.

Check Refresh browser when scene becomes active. Otherwise your overlay will disappear when you change scenes.

BrowserSources come with this default CSS:

body {
    background-color: rgba(0,0,0,0);
    margin: 0px auto;
    overflow: hidden;
}

We want some version of this code to go in our own HTML file, so delete this entire field.

With this out of the way, finish the BrowserSource so we can move on to our HTML file. Leave OBS alone and don’t look at it until I tell you to.

Overlay HTML

I’m not going to teach you HTML, so I’ll assume a basic level of familiarity with HTML, CSS, and JS. After all, the idea is that you want to code your own overlays, right?

This is our starting HTML template:

<!-- overlay.html -->
<html>
    <head>
        <link rel="stylesheet" href="./overlay.css" />
    </head>
    <body>
        <main>

        </main>
    </head>
</head>

Create the overlay.css file in the same folder as your HTML file.

Let’s review the default CSS OBS uses for BrowserSources:

/** overlay.css */
body {
    background-color: rgba(0,0,0,0);
    margin: 0px auto;
    overflow: hidden;
}

The only thing we need here is the transparent background from the first line. Let’s add this to our CSS file along with some other basic stuff:

/** overlay.css */
body {
    background-color: rgba(0,0,0,0);
    width: 1920px;
    max-height: 1080px;
}

(This assumes your monitor size is 1920×1080, of course.)

We want our HTML container to be as big as our screen, but we don’t need it to be as high, so we’re just going to use max-height.

Let’s add a placeholder text to emulate what we want:

<!-- overlay.html -->
        <main>
            <section id="countdown" class="countdown">
                06d 05h 04m 03s
            </section>
        </main>

We want it to be in the top right:

/** overlay.css */
#countdown {
    width: 100%; /** To match container and align all the way right */
    text-align: right;
    font-size: 32px;
    color: purple; /** Viewable on both white and black background */
}

Let’s take a moment to see what this looks like in OBS:

Version 0.1

Screenshot of the BrowserSource showing what's described below

First you’ll notice our overlay is clearly larger than 1080px, since we get a scroll bar and our countdown is cut off.

Now try changing the font size in your CSS and see what happens.

(Nothing happens.)

http-equiv="refresh"

We need to refresh your overlay automatically. To achieve this, we use http-equiv="refresh":

<!-- overlay.html -->
<html>
    <head>
        <meta http-equiv="refresh" content="1">

        <link rel="stylesheet" href="./overlay.css" />
    </head>
    <body>
        <main>
            <section id="countdown" class="countdown">
                06d 05h 04m 03s
            </section>
        </main>
    </head>
</head>

<meta http-equiv="refresh" content="1"> means our page is refreshed every second. This might make it hard for you to inspect source code in your browser, so you can always set it to something higher temporarily.

This also means our page is drawn every second. The implication of this when using 1 isn’t significant, but if your page is only drawn every minute, you’ll need to remember that your browser source will take a minute to show up after you open OBS; after that, it will be refreshed every minute. Don’t assume your overlay is broken because it’s not showing up; it might just need to be drawn first.

I use 1, because I might as well, and because constant updates to our overlay makes it easier to work with until it’s done. We’re showing seconds in the placeholder for the same reason, because I’m not really interested in showing them in a real use case.

Reset CSS

Next, we’ll have to deal with that scroll bar which in CSS terms is called “overflow”. If we had kept the overflow CSS rule from OBS’s template, we would just have hidden the issue instead of fixing it.

CSS has a billion quirks by default, and the simplest way to fix (most of) them is with a reset CSS. Download normalize.css and put it in css/:

<!-- overlay.html -->
<html>
    <head>
        <meta http-equiv="refresh" content="1">

        <link rel="stylesheet" href="./css/normalize.css" />
        <link rel="stylesheet" href="./overlay.css" />
    </head>

Remember to put it before your general CSS.

Version 0.2

Now go to the settings for your BrowserSource and hit Refresh cache of current page:

Updated BrowserSource screenshot with none of the issues

That’s those two issues fixed.

You also shouldn’t have to ever use the refresh button again after this.

Basic countdown styling

First of all, our font is a mess and hard to read, so let’s fix that:

/** overlay.css */
    font-family: Calibri;
    font-weight: bold;

Next, we want to move the countdown just a little bit from the corner to let it breathe.

/** overlay.css */
    padding: 10px 10px;

Let’s see what this looks like:

Screenshot v0.3 of the BrowserSource showing what's described below

If you’re familiar with CSS and webdesign, you already know why we’re seeing the scroll bar; it’s using the wrong box sizing, which means the size of the container box increases as we add padding when it should just maintain its size while adding the padding from within.

Add this CSS to fix the issue:

/** overlay.css */
*, *:before, *:after {
    margin: 0;
    padding: 0;

    -webkit-box-sizing: border-box !important;
       -moz-box-sizing: border-box !important;
        -ms-box-sizing: border-box !important;
            box-sizing: border-box !important;
}

Screenshot v0.4 of the BrowserSource: issue fixed

Boom, sorted.

Well, except that the vertical distance is larger than the horizontal one. This is because line-height is added to the vertical padding, so let’s change our padding slightly by eyeballing:

/** overlay.css */
    padding: 10px 20px;

You can perfect your padding yourself after you’ve picked your own preferred font family and size. Try line-height: 0; for #countdown.

The JavaScript part

Uh-oh.

I’m going to just put the JavaScript in-line instead of loading it externally, because it’s a pain to jump back and forth between the files.

<!-- overlay.html -->
<html>
    <head>
        <meta http-equiv="refresh" content="1">

        <link rel="stylesheet" href="./css/normalize.css" />
        <link rel="stylesheet" href="./overlay.css" />
    </head>
    <body>
        <main>
            <section id="countdown" class="countdown"></section>
        </main>

        <script>

        </script>
    </body>
</html>

The bad news is that there are no good JavaScript countdown libraries. The bad news is it’s really easy to do yourself.

If you’re well familiar with JavaScript, you should already know this about its Date objects:

The JavaScript date is based on a time value that is milliseconds since midnight 01 January, 1970 UTC. A day holds 86,400,000 milliseconds. The JavaScript Date object range is -100,000,000 days to 100,000,000 days relative to 01 January, 1970 UTC.

Mozilla web docs

Since a Date object is a measure of milliseconds, we can count down to an event by subtracting the Date of our current time from the Date object of our future event. This will give us the number of milliseconds until the event from now.

Timezones

There is one issue, however: friggin timezones. For this, we will need a library called Moment.js and its timezone library. Download them and load them in your HTML. Make sure you download either moment-timezone-with-data-2012-2022.js or moment-timezone-with-data.js, as moment-timezone-with-data.js only has the JavaScript logic but none of the actual timezone data.

Here is what it should look like:

<!-- overlay.html -->
<html>
    <head>
        <meta http-equiv="refresh" content="1">

        <link rel="stylesheet" href="./css/normalize.css" />
        <link rel="stylesheet" href="./overlay.css" />
        <script src="./js/moment.js"></script>
        <script src="./js/moment-timezone-with-data.js"></script>
    </head>

You can also download the minified .min versions of the files instead to save space, but for now you can inspect the code of them to see how they work. Same with normalize.css.

This is how I use the libraries to create a Date object:

var event = moment(
    "2017-10-28 23:00",
    "YYYY-MM-DD HH:mm")

To create an object for now(), you leave out the arguments:

var now = moment();

If you don’t specify a timezone, moment.js will just use your local time which is just fine for us.

Represent

As discussed, we get our countdown in milliseconds by subtracting the latter from the former:

var diff = event - now;

All we have to do now is figure out how to represent these milliseconds as days, hours, and minutes instead.

Here’s the cheatsheet to accomplish that:

"d" = diff/1000/60/60/24
"h" = diff/1000/60/60 % 24
"m" = diff/1000/60 % 60
"s" = diff/1000 % 60

Don’t copy-paste this anywhere.

I’m using %, modulus, here, but if this is too confusing for you, you can get to the numbers you want yourself.

Let’s turn this into a function that takes a number in milliseconds as argument:

function formatTime(ms) {
    return {
        "d": ms/1000/60/60/24,
        "h": ms/1000/60/60 % 24,
        "m": ms/1000/60 % 60,
        "s": ms/1000 % 60
    };
}

var countdown = formatTime(diff);

I’m just leaving the seconds there in case you want to show them.

By now, this is all our code:

<!-- overlay.html -->
<html>
    <head>
        <meta http-equiv="refresh" content="1">

        <link rel="stylesheet" href="./css/normalize.css" />
        <link rel="stylesheet" href="./overlay.css" />
        <script src="./js/moment.js"></script>
        <script src="./js/moment-timezone-with-data.js"></script>
    </head>
    <body>
        <main>
            <section id="countdown" class="countdown"></section>
        </main>

        <script>
            var event = moment(
                "2017-10-28 23:00",
                "YYYY-MM-DD HH:mm");

            var now = moment();
            var diff = event - now;

            function formatTime(ms) {
                return {
                    "d": ms/1000/60/60/24,
                    "h": ms/1000/60/60 % 24,
                    "m": ms/1000/60 % 60,
                    "s": ms/1000 % 60
                };
            }

            var countdown = formatTime(diff);
        </script>
    </body>
</html>
/** overlay.css */
*, *:before, *:after {
    margin: 0;
    padding: 0;

    -webkit-box-sizing: border-box !important;
       -moz-box-sizing: border-box !important;
        -ms-box-sizing: border-box !important;
            box-sizing: border-box !important;
}

body {
    background-color: rgba(0,0,0,0);
    width: 1920px;
    max-height: 1080px;
}

#countdown {
    width: 100%; /** To match container and align all the way right */
    text-align: right;
    font-family: Calibri;
    font-weight: bold;
    font-size: 32px;
    color: purple; /** Viewable on both white and black background */
    padding: 10px 20px;
}

Right now, there’s no countdown going on so we can’t check in on OBS to see how it looks yet.

Inserting the countdown

What follows next is taking the days, hours, and minutes we’ve calculated and insert it into our HTML DOM so we literally have something to show for it.

For this, we’ll use document.getElementById("countdown").innerHTML.

My preferred countdown has the format 01d 02h 03m, so this is how to represent that in HTML with <span> elements for each counter:

document.getElementById("countdown").innerHTML =
    "<span>" + countdown.d + "d" + "</span>" + " " +
    "<span>" + countdown.h + "h" + "</span>" + " " +
    "<span>" + countdown.m + "m" + "</span>" + " " +
    "<span>" + countdown.s + "s" + "</span>";

We’ll include the seconds for now to make sure the countdown and overlay keep updating.

Also be sure you deleted the placeholder text:

<section id="countdown" class="countdown"></section>

Version 0.5

So what have we got now:

Screenshot of the BrowserSource showing what's described below

Our countdown now is shown and updates dynamically, but each value has like a billion decimals.

Did you notice how their position keeps shifting all the time?

Screenshot of the BrowserSource showing what's described above and below Another screenshot of the BrowserSource taken slightly later showing what's described below

That’s because the font we chose isn’t monospaced. I told you to keep the seconds for now to expose issues like this.

Formatting the countdown

To keep this from happening, we need to do one of two things:

  1. Use a monospace font
  2. Create a fixed width and spacing for each <span> element in CSS

I’m gonna stick to the first (lazy) approach:

<!-- overlay.css -->
font-family: Consolas, monospace;

This should fix the issue. Right?

Nope. Two other things are responsible for the numbers shifting positions:

  1. The number of decimals keeps changing
  2. The integers change between single and double digits

The first issue is fixed by rounding the values down.

The second issue is fixed by using what are called leading zeroes, as I used in my placeholder values.

Unlike our days and minutes (and seconds), our days can go beyond two digits, but because it’s the left-most value in a right-aligned line of text, it won’t shift the position of anything to the right of it with more or fewer digits. Besides, it’s going to update so rarely that it wouldn’t have been a big deal anyway.

In order to round our values and add a leading zero, I recommend you turn to d3-format.

<!-- overlay.html -->
        <script src="./js/moment.js"></script>
        <script src="./js/moment-timezone-with-data.js"></script>
        <script src="./js/d3-format.v1.min.js"></script>

Without going into to much detail about how d3-format works, this is how we’ll be using it:

d3.format("02d")(1.234); // => "01"
d3.format("d")(1.234);   // =>  "1"

"02d" formats to leading zero, total of two integers.

d formats integer.

Applied to our formatting function:

// overlay.html
            function formatTime(ms) {
                return {
                    "d": d3.format("d")(ms/1000/60/60/24),
                    "h": d3.format("02d")(ms/1000/60/60 % 24),
                    "m": d3.format("02d")(ms/1000/60 % 60),
                    "s": d3.format("02d")(ms/1000 % 60)
                };
            }

Version 0.6

Let’s see where we are:

Screenshot of the overlay finally working as intended.

We did it!

Here is the final code, with the seconds removed:

<!-- overlay.html -->
<html>
    <head>
        <meta http-equiv="refresh" content="1">

        <link rel="stylesheet" href="./css/normalize.css" />
        <script src="./js/moment.js"></script>
        <script src="./js/moment-timezone-with-data.js"></script>
        <script src="./js/d3-format.v1.min.js"></script>
    </head>
    <body>
        <main>
            <section id="countdown" class="countdown"></section>
        </main>

        <script>
            var event = moment(
                "2017-10-28 23:00",
                "YYYY-MM-DD HH:mm");

            var now = moment();
            var diff = event - now;

            function formatTime(ms) {
                return {
                    "d": d3.format("d")(ms/1000/60/60/24),
                    "h": d3.format("02d")(ms/1000/60/60 % 24),
                    "m": d3.format("02d")(ms/1000/60 % 60),
                    "s": d3.format("02d")(ms/1000 % 60)
                };
            }

            var countdown = formatTime(diff);

            document.getElementById("countdown").innerHTML =
                "<span>" + countdown.d + "d" + "</span>" + " " +
                "<span>" + countdown.h + "h" + "</span>" + " " +
                "<span>" + countdown.m + "m" + "</span>";
        </script>
    </body>
</html>
/** overlay.css */
*, *:before, *:after {
    margin: 0;
    padding: 0;

    -webkit-box-sizing: border-box !important;
       -moz-box-sizing: border-box !important;
        -ms-box-sizing: border-box !important;
            box-sizing: border-box !important;
}

body {
    background-color: rgba(0,0,0,0);
    width: 1920px;
    max-height: 1080px;
}

#countdown {
    width: 100%; /** To match container and align all the way right */
    text-align: right;
    font-family: Consolas, monospace;
    font-weight: bold;
    font-size: 32px;
    color: purple; /** Viewable on both white and black background */
    padding: 10px 20px;
}

This is the part where you create your own overlay template so you’ll be able to make another one at a later time when you’ve long forgotten 99% of this guide.

Anyway, that’s it, hope this has whetted your appetite to dig in and do your own overlays. I’d love to see your work, so please share it with me on Twitter if you don’t mind. You could also try to do something when the countdown hits zero; you probably don’t want to see something like -00d 00h 05m.

A simple way to see if you understand the code behind this is to create a timer that counts up instead of down like this one does.

This is actually pretty important.

Do. Not. Use. Overlays. Whose. License. You. Aren’t. Sure. Of.

You do not want your stream and VODs to end up violating someone’s rights. Just because they aren’t kicking your door down with DMCAs doesn’t mean you aren’t infringing on their intelectual property.

All the more reason to make as many of the overlays on your stream as possible.

In theory, you might not even be within your rights to use this overlay. I am probably not going to issue you DMCAs, but how do you know that?

Many of you probably already use profile photos and such that are already infringing, but it’s a lot harder with overlays that you can’t remove from your VODs.

This assumes you don’t just stream ephemerally; if you don’t archive and upload your VODs, any copyright infringement is not as great a concern as any permanent VODs and uploads would be.

Here is a clip from a great panel at Twitchcon 2017 with the low-down:

Finally, another reason it’s awesome to own your own stuff is that you don’t run into problems when your overlay service decides to relaunch and shut down all overlays with a whole nine days of advance warning.