Building The Haunting of Hill House Door Knob Effect in Javascript

Using Anime.js and Howler.js

Image for post
Barely visible door knob

This past week I suffered through the terrifying Netflix original series, The Haunting of Hill House. It’s an incredible piece of horror that you simply have to check out. Yes, it will make you feel uneasy. Yes, you probably won’t sleep very well. Yes, you’re going to start seeing things in your home... However, you will also get to watch an incredible (child and adult) cast tackle family grief and trauma which haunts them for years. There’s a lot more to this series than jump scares. But, you better fucking believe they have those too. 😩🍿

Visually, the show is incredibly inspiring. Like any good haunted house story, the house itself is very much a character and every door, vanity, staircase, and dumbwaiter is put to terrifying use. Without giving anything a way, our characters find themselves behind a door whose knob begins to turn itself. I knew I wanted to do a Halloween themed programming tutorial and thought it might be fun to recreate this effect.

Check out the Codepen and click the knob if you dare. Then read on to learn how I pulled this off.


The first thing I did was go to Netflix and screenshot the best frame of the door and knob. I took this screenshot into Photoshop and cut the knob out into a transparent PNG. I also noted that the knob was about 48% the height of the composition. I then used the clone stamp and content aware tool to remove the knob from the door. I also used a gradient mask to fade out the door in every direction so the image would work more responsively without any sharp visual edges. That gave us a simple background with a subtle texture.

I also recorded each of the three knob turning sounds from the actual scene. This recording was then brought into Fission and trimmed into three files for each turn. I made note of the length of each sound in milliseconds: 5380, 5750, and 8880. Finally, I combined all three turn sounds consecutively into one audio file. More on that later.


The layout and styling for this experience is incredibly simple as all of the logic of the effect is created with Javascript.

First, let’s look at the HTML:

<img id="knob" src="knob.png">

Yeh… I think you know what’s going on here. 😅 Let’s go ahead and add our background image, center the knob, and then resize the knob accordingly using CSS:

html, body{
height: 100%;
width: 100%;
align-items: center;
background: url(door.jpg);
background-position: center center;
background-repeat: none;
background-size: cover;
display: flex;
justify-content: center;
cursor: pointer;
height: 48vh;

If you want to spook yourself out, simply add a CSS animation to rotate that knob infinitely at an uncomfortable speed. We’re going to use Javascript to develop something a bit more random.


Now that we have a creepy knob placed in the center of our page, it’s time to create a spirit that will randomly turn it. 👻🚪 To script the animation, I decided to use Anime.js because I thought it’s callbacks would come in handy for this. The audio is powered by Howler.js because we can take advantage of it’s audio sprites functionality. But first, let’s establish a few variables:

let turning = false
let rotate = 0
let turns = [5380, 5750, 8800]

Later on we’ll require that the user clicks the knob to begin the experience and we’ll use turning to keep track of that initial interaction. This is necessary because mobile devices require a user interaction before playing any sound. The rotate variable will be used to keep track of where our knob is currently rotated to. turns is simply an array of the durations of our turning audio clips. In addition we’ll want to establish the sound file with Howler:

let sound = new Howl({
src: ['knob.mp3'],
sprite: {
turn0: [0, 5380],
turn1: [5380, 5750],
turn2: [11130, 8800]

Pay special attention to the sprite property. Since we combined all of our turn sounds into a single audio file, we can use Howler’s audio sprites functionality to establish where each clip begins and how long it is. This is done by supplying an array with two numbers, the starting point and the length of the clip in milliseconds. I went ahead and just worked this out manually.

With our variables and sound initialized, we can build out the actual turn function. Since I was going to be dealing with a lot of random numbers, I brought in Underscore to make use of it’s excellent _.random utility. First, I wanted to randomly select which turn length I would be animating. This would power both the duration of animation and which accompanying audio clip should be played. Next, I wanted to randomize the actual rotation amount but still keep it eerily believable like on the show. I also randomized the direction of the rotation. Finally, if you watch the show, there is a slight pause between turns which can be established with the Anime.js delay property.

Now that we established all our random variables, we simply need to build out the anime call. Each of our random variables get associated with the relevant property. I chose linear for easing but play around with this if you’re trying to get real exorcist with it. The sound should not begin until the animation begins so let’s use the begin callback to trigger that. Finally, when the complete callback is called, turn the knob again.

function turn() {
let i = _.random(0,2)

let rotation = _.random(45, 100)
if (Math.random() < 0.5) {
rotate -= rotation
} else {
rotate += rotation
let delay = _.random(1000, 3000) anime({
targets: '#knob',
rotate: rotate,
duration: turns[i],
delay: delay,
elasticity: 0,
easing: 'linear',
begin: function() {`turn${i}`)
complete: function() {

Finally, as mentioned earlier, we’ll want to start this whole eerie experience with a user clicking the knob so let’s add that event.

document.getElementById('knob').onclick = function(){
if (!turning) {
turning = true

I hope you enjoyed this simple tutorial. Make sure to check out the show and have a safe Halloween! 🎃

Written by

I develop websites for rock 'n' roll bands and get paid in sex and drugs. Previously Silva Artist Management, SoundCloud, and Songkick. Currently: Available

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store