I made a website to rickroll my friends

Cover Image for I made a website to rickroll my friends

For a long time, I've wanted to use my skills for the greater good, like trolling my friends. But then I had a genius idea pop up in my head.

What if there was a website that rickrolled you when you opened it?

But there in fact were websites that rickrolled you when you opened them.

But nahh, I didn't want it to be that straightforward. I wanted it to be a little engaging like with a puzzle or something that when solved would rickroll you instead of appreciating your hard work.

And I ended up making...

🚀 The Implementation

The idea behind it was simple.

You click on circles and the number of circles doubles. This keeps happening until there are 2500 circles on your screen.

Why 2500 circles specifically?

Well, I just didn't want to show the users a gif. I wanted to somehow incorporate the circles into the mix. And so the 2500 circles, arranged in a 50x50 graph became my canvas.

The rest is easy. Well...not really.

I had to select the individual circles so I could use them as pixels and render a very basic monotone animation of Rick Astley in action.

I started with this basic HTML code with one circle and a little instruction for the user to keep clicking on and styled it with some CSS.

  <div class="box clickable" id="1">
    <div class="circle" id="0"></div>
  <h3 class="rainbow" id="banner">KEEP TAPPING<br>ON THE CIRCLE</h3>
  <script src="script.js"></script>

Then a little javascript to just double the number of circles.

var box = document.getElementsByClassName("clickable")[0];
var count = parseInt(box.id);

// setting the max num of circles
const max = 2500;

box.onclick = function () {
  var ok = true;

  // i dont want to add any more circles than the exact number of 2500
  // hence this code check while determining num of circles to add to the box
  if (ok === true && count <= max) {
    var num = count == 1 ? 1 : count * 2 > max ? (max - count) : count * 2;
    for (var i = 0; i < num ; i++) { addCircle() }
    box.id = num;
    if (count != 1) {
      box.style.gridTemplateColumns = `repeat(${Math.ceil(Math.sqrt(count))}, 1fr)`;
    ok = false;

  if (count >= max) {
    // doing this makes it a 50x50 grid with 2500 circles
    // now it makes sense to set max to 2500 amirite?
    box.style.gridTemplateColumns = "repeat(50, 1fr)";
    box.onclick = null;

Then a helper function to actually create and add the circle divs

function addCircle() {
  var circle = document.createElement('div');
  circle.className = 'circle';
  circle.id = count;
  box.id = count++;

The result:

Now came the actual problem of rendering the animation.

After a long process of trial and error, the best way I found to render the animation was to use a sprite sheet.

A sprite sheet is basically a long image that contains all the frames of a gif. Here's what mine looked like:

Now, we go through each frame (each part of this long sprite sheet) and then check out which pixels are dark, select the equivalent circle and change its color to a darker color.

Now for convenience, I made sure each frame was a 50 pixels x 50 pixels image. So each circle represented a single pixel on the image.

Here's the code:

box.onclick = function () {
    /* Previously Written Code Goes Here 👇

    // defining my spritesheet image object
    var img_obj = {
      'source': null,
      'current': 0,
      'total_frames': 52,
      'width': 50,
      'height': 50

    const img = new Image();
    img.onload = function () { // Triggered when image has finished loading.
      img_obj.source = img;  // we set the image source for our object.

    img.src = "./data/ricksprite.png";

    const canvas = document.createElement('canvas');
    canvas.width = 50;
    canvas.height = 50;
    var context = canvas.getContext("2d");
    setInterval(function () { rickRoll(canvas, context, 0, 0, img_obj) }, 100);

    var banner = document.getElementById('banner');
    banner.innerHTML = "YOU'VE BEEN RICKROLLED!"; // another message to remind that you've been rickrolled
    box.onclick = null; 

Then we define the actual function that goes through the sprite sheet and updates the frame that we are currently showing to the user.

// this function takes care of the animation by moving the spritesheet and taking each frame to draw
// once the frame is determined, it checks for each dark pixel and then colors the appropriate circle with black
function rickRoll(canvas, context, x, y, iobj) {
  // coloring all black pixels to normal before animation to reset the box
  // seems like a glitch where if i dont run it twice, it creates a weird effect

  if (iobj.source != null){
    context.drawImage(iobj.source, iobj.current * iobj.width, 0, iobj.width, iobj.height, x, y, iobj.width, iobj.height);

  iobj.current = (iobj.current + 1) % iobj.total_frames;

  for (let y = 0; y < canvas.height; y++) {
    for (let x = 0; x < canvas.width; x++) {
      let data = context.getImageData(x, y, 1, 1).data;
      if (data && data[0] <= 20 && document.getElementById(((y+1) * 50 + x + 1).toString())) {
        document.getElementById(((y+1) * 50 + x + 1).toString()).classList.add("dark");

  iobj.current = (iobj.current + 1) % iobj.total_frames;

Finally, another function that colors all the circles gray. So when we animate another frame, the already black pixels don't just stay on the canvas.

function clearBox() {
  let elems = document.getElementsByClassName("dark");
  for (let i = 0; i < elems.length; i++) {

And after writing so many functions, we can finally see rickroll in action!

Here's the link: https://the-circle-illusion.netlify.app/

GitHub Repo links for OG devs:

Thank you for making it to the end of the post. Here's a pizza slice

{\ __ /}
( • . •)
/ > 🍕

See you in the next one!