loading...
Cover image for How to Create the Drawing Interaction on DEV's Offline Page

How to Create the Drawing Interaction on DEV's Offline Page

aspittel profile image Ali Spittel Updated on ・4 min read

Since more and more people have been noticing DEV's offline page, I thought I would do a quick tutorial on how to replicate the code for it!

Canvas is for creating graphics with JavaScript -- we can build fun, interactive tools using it. When I normally build interactive artwork like this, I use P5.js, which makes the Canvas API easier to work with. We wanted to make the offline page as self-contained and lightweight as possible, though, so the offline page doesn't use any external code.

The first thing that we need to do is to create a <canvas> tag in our HTML. You will also need to add in CSS to make the canvas take up space -- so give it a height and a width. I made a Codepen template with some starter CSS for us to work with:

Now, on to the JavaScript!

The first thing that we need to do, is to select the canvas element that exists in the HTML already so we can interact with it. We will also have to create a variable for the context of the canvas. We will use a 2D context because our drawing will only be two dimensional:

const canvas = document.querySelector('canvas')
const context = canvas.getContext('2d')

We will also want to set the size of the canvas in the JavaScript so that our images aren't distorted:

canvas.setAttribute('width', window.innerWidth)
canvas.setAttribute('height', window.innerHeight)

Now we need to add some event listeners. For the drawing app, we want to add these:

  • 'mousedown' - when a user presses their mouse we want to start drawing

  • 'touchstart' - when a user is on their phone, we again want to start drawing

  • 'mousemove' - when a user moves their mouse, we want to draw a line from the mouse's previous place to the current place

  • 'touchmove' - the same as above, but when the user is on their phone

  • 'mouseup' - when a user stops pressing down, we want to stop drawing

  • 'mouseleave'- when a user's mouse leaves the area, we also want to stop drawing

  • 'touchend' - when a user is on their phone and stops pressing down, we again want to stop drawing

So, we need three event handling functions that will respond to the above events. Let's start with the startPaint function that will run each time the person starts drawing.

We can add an event listener the same way that we can with any other element in JavaScript:


function startPaint (e) {

}

canvas.addEventListener('mousedown', startPaint)
canvas.addEventListener('touchstart', startPaint)

We want the startPaint function to do a couple things:

  • First, we need a variable that keeps track of whether or not we are currently drawing so that the mousemove handler only works when we are currently painting. We need to set that to true whenever we start drawing.

  • Then, we need to get the coordinates of where the person is clicking. We need to keep track of those coordinates so that we can move from the current point to the next one when the person then moves their mouse.


let x, y, isPainting;

function getCoordinates(event) {
  // check to see if mobile or desktop
  if (["mousedown", "mousemove"].includes(event.type)) {
    // click events
    return [event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop];
  } else {
    // touch coordinates
    return [
      event.touches[0].pageX - canvas.offsetLeft,
      event.touches[0].pageY - canvas.offsetTop
    ];
  }
}

function startPaint(e) {
  // change the old coordinates to the new ones*
  isPainting = true;
  let coordinates = getCoordinates(e);
  x = coordinates[0];
  y = coordinates[1];
}

Then, we need to handle when the person moves their mouse to draw. Here we have to:

  • Check to see if we are painting (i.e. the mouse is down)

  • We need to get the new mouse coordinates

  • We need to draw a line from the old coordinates to the new ones

  • We need to set the old coordinates to the new ones so that our next "draw" starts at the current point

function drawLine(firstX, firstY, secondX, secondY) {
  // set the attributes of the line
  context.strokeStyle = "black";
  context.lineJoin = "round";
  context.lineWidth = 5;

  context.beginPath();
  context.moveTo(secondX, secondY);
  context.lineTo(firstX, firstY);
  context.closePath();

  // actually draw the path*
  context.stroke();
}

function paint(e) {
  if (isPainting) {
    let [newX, newY] = getCoordinates(e);
    drawLine(x, y, newX, newY);

    // Set x and y to our new coordinates
    x = newX;
    y = newY;
  }
}

canvas.addEventListener("mousemove", paint);
canvas.addEventListener("touchmove", paint);

Now, we just have to stop drawing when we release our mouse!


function exit() {
  isPainting = false;
}

canvas.addEventListener("mouseup", exit);
canvas.addEventListener("mouseleave", exit);
canvas.addEventListener("touchend", exit);

Now, here's a finished version with changing colors and the ability to resize the page!

I love building art with code, especially that people can interact with. If you want to learn more, I have a few more posts on this topic if you're interested!

Posted on by:

aspittel profile

Ali Spittel

@aspittel

Passionate about education, Python, JavaScript, and code art.

Discussion

markdown guide
 

I've always loved working with the canvas element. Back when I still wanted to be a games developer, it seemed like the quickest watly to make an (almost, because it was a few years ago) browser agnostic HTML5 game without having to rely on this party adons and plugins.

I even ended up writing a super simple game (which I called "Run Away") using it and vanilla javascript. I don't think that I have it hosted anywhere right now (I might fling it up on Netlify tonight), but the source code is available at my github. I'd love to see what folks think of it.

EDIT:

And it's live. You can now play the game over at Netlify.

One thing to note: it has support for touch (on mobile and tablets), but it doesn't use it. So you'll need to use a physical keyboard to play it.

tl;dr: run away from the scary monster. The further you run, the more fatigued you'll get. The longer you last, the hungrier (and faster) the monster gets.

 

Interactive offline pages are the new funny 404 pages. 💜

 

Holy cow. This is the first tutorial I've read of yours. You really explain things systematically. Cheers :)

 
 
 

This is amazing Ali! I love the idea

 

This is so cool! When my wifi cut out and I saw it the first time, I tried to learn how to code it but couldn't quite figure it out. Thank you for this! Also, I really like that you put your style into the design with the colors and everything. When I saw that page, before I noticed your initials at the bottom, I thought, "Hey, Ali made this."

 

Will be using this for our 404 page, kthanksbye.

 
 

Found this lying around in the millions of tabs I have open on my phone - looking forward to diving in the near future. Seems pretty straightforward and is surprisingly easy to follow along to (for some reason I always figured working with the canvas would be complicated, so I never bothered).

 
 

Sweet lovely post. Thank you for the tip and wonderful examples!

 

Nice Page!

1 small feedback I have is "Should we add cursor:pointer for those color so user know they're clickable" :D

 
 
 
 
 

Excuse me but this is FLIPPING COOL

 

I once tried doing something like that but with WebSockets (or maybe WebRTC) for collaborative drawing, it was a lot of fun, I should try to finish that project one day.

 

Reminded me of an old high school project that I did one time. Makes me want to update that repo! Thanks @aspittel 😄 On a side note, You might also want to disable scroll on iOS when you try to draw.

 

Speaking of DEV offline page, I encountered this today (for the first time). Being pretty sure I was in fact connected, tried visiting in Chromium instead of Firefox and DEV operates normally.

 

How is this offline page triggered? I always have the feeling to end up there for absolutely the wrong reason, like go backwards and forwards in the browser or opening a tab in a container :(