Back in the day, a certain internet webcomic achieved a level of infamy for it's startling twist.
However, the shocking twist that powered that comic was written with Adobe Flash Player, which will be completely deprecated from the internet on December 31, 2020.
And thus, a part of internet history was lost to the ages...
Or was it? (BE WARNED, the below link contains spooky jump scares)
https://thatspookycomic.github.io/
Were you brave enough to try it out? I decided to recreate the infamous effect using nothing but vanilla JS, HTML, and CSS along with some new-school web APIs. You can compare the results of the page above with a recording of the original effect.
So how was it done?
The Jump Scare sound
Let's start from the top of the page. You probably noticed the pumpkin emoji that the page tells you to click on, right?
Seems like a cute little innocent Halloween fun, right? Well, it actually hides a sinister purpose.
In order to play the jump scare noises on the page, I use a web API called Audio.
Now, the Audio tool is designed to prevent the annoyance of auto-playing media that starts when a webpage is opened. If the user has not interacted with the document, when you try to play a sound you get this error:
So in order for us to do something like:
const spookySound = new Audio('audio/spooky-sound.mp3');
spookySound.play();
We first have to incentive the user to click somewhere on the page! So I dropped in a little JS to update a pumpkin emoji onclick:
<span id="clickEmoji" onclick="emojiClickChange()">🎃</span>
function emojiClickChange() {
document.getElementById('clickEmoji').innerText = "👻";
}
We are now ready to fire off some scary sounds!
Triggering the Jump Scare
Next up, we need to detect when the user has scrolled to a certain point on the page, where we need to fire the jump scare effect.
To do that, we use the Intersection Observer API.
I dropped an id on the two images where I wanted the jump scare to occur:
<img src="images/7.jpg" id="firstTarget" />
...
<img src="images/17.jpg" id="secondTarget" />
and then I create an observer and observe those images:
let observer = new IntersectionObserver(handler);
observer.observe(document.getElementById('firstTarget'));
observer.observe(document.getElementById('secondTarget'));
Now, we can create a function that will be called whenever the state of those observed items changes!
function handler(entries) {
for (entry of entries) {
if (
entry.target.id === 'firstTarget'
&& entry.isIntersecting
&& !firstJumpScareActivated
) {
playFirstJumpScare();
}
}
}
Whenever any of the observed items changes state the handler
function is called. We can read entry.target.id
to know what item we are dealing with, isIntersecting
to know if the user has scrolled the item into view, and I use a boolean XJumpScareActivated
to make sure the effect only happens once.
One last thing, if your images load in slow, the effect might be triggered if the id is briefly in frame when the page loads. The right way to fix this is to wait for all images to be loaded before starting the intersection observer, but for the sake of simplicity, a setTimeout did the job for me:
setTimeout(() => {
observer.observe(document.getElementById('firstTarget'));
observer.observe(document.getElementById('secondTarget'));
}, 3000)
Great, we can now tell when the user has scrolled our target item into view, and we are ready to play sound. Last up, we need to create the rapid scrolling jump effect!
Auto scrolling the user around
In order for this jump scare to work properly, we want to rapidly scroll the user through the comic frames while playing a sound. The scariest part of this effect comes from the fact that the user thinks they have control of the pace of the comic, and we want to rapidly tear that control away from them.
The way I built this was by scrolling to each part of the page I wanted to scroll the user to, and in the dev tools console, typing window.scrollY
:
Now that I had the Y coordinate I wanted, I just trigger the scroll effect at setTimeout intervals on each Y coordinate. The code looks like this:
function playFirstJumpScare() {
setTimeout(() => {
clickSound.play();
window.scrollTo(0, 8441);
}, 800)
setTimeout(() => {
window.scrollTo(0, 9090);
}, 900)
setTimeout(() => {
window.scrollTo(0, 9660);
}, 1000)
...
}
Future improvements could involve putting all the times and scroll positions into an array of objects and mapping through them:
[
{
time: 800,
scrollPos: 8441
},
...
]
But today is Halloween already and I needed to release this thing, stat! So for now, the code stays ugly.
Once I activate the jump scare, I set a boolean to true so that it doesn't happen again:
firstJumpScareActivated = true;
Finally, to get the website online, I created a new GitHub account and used GitHub Pages, you can see the full code of the website here.
I hope this was informative, interesting, and most importantly, terrifying. Feel free to share the comic with all your friends this spooky last night of October! Happy Halloween!
Top comments (2)
Got a few good laughs out of this! Thanks for sharing
Enjoyed the scare 😂😂
Its a nice project 🤘🏽