Using JavaScript to create temporary drawings on a webpage that automatically erase themselves

I am curious about how to achieve a scribble effect that self-erases, similar to what is seen on this website:

Example:

Thank you!

I have come across some similar scripts, but I am struggling to understand how to make the scribble disappear after a few lines.

Here is an example of what I mean: https://jsfiddle.net/loktar/dQppK/23/

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d"),
    painting = false,
    lastX = 0,
    lastY = 0,
    lineThickness = 1;

canvas.width = canvas.height = 600;
ctx.fillRect(0, 0, 600, 600);

canvas.onmousedown = function(e) {
    painting = true;
    ctx.fillStyle = "#ffffff";
    lastX = e.pageX - this.offsetLeft;
    lastY = e.pageY - this.offsetTop;
};

canvas.onmouseup = function(e){
    painting = false;
}

canvas.onmousemove = function(e) {
    if (painting) {
        mouseX = e.pageX - this.offsetLeft;
        mouseY = e.pageY - this.offsetTop;

        // find all points between        
        var x1 = mouseX,
            x2 = lastX,
            y1 = mouseY,
            y2 = lastY;


        var steep = (Math.abs(y2 - y1) > Math.abs(x2 - x1));
        if (steep){
            var x = x1;
            x1 = y1;
            y1 = x;

            var y = y2;
            y2 = x2;
            x2 = y;
        }
        if (x1 > x2) {
            var x = x1;
            x1 = x2;
            x2 = x;

            var y = y1;
            y1 = y2;
            y2 = y;
        }

        var dx = x2 - x1,
            dy = Math.abs(y2 - y1),
            error = 0,
            de = dy / dx,
            yStep = -1,
            y = y1;
        
        if (y1 < y2) {
            yStep = 1;
        }
       
        lineThickness = 5 - Math.sqrt((x2 - x1) *(x2-x1) + (y2 - y1) * (y2-y1))/10;
        if(lineThickness < 1){
            lineThickness = 1;   
        }

        for (var x = x1; x < x2; x++) {
            if (steep) {
                ctx.fillRect(y, x, lineThickness , lineThickness );
            } else {
                ctx.fillRect(x, y, lineThickness , lineThickness );
            }
            
            error += de;
            if (error >= 0.5) {
                y += yStep;
                error -= 1.0;
            }
        }



        lastX = mouseX;
        lastY = mouseY;

    }
}
<canvas id="canvas"></canvas>

Answer №1

To master this technique, it's crucial to keep track of a list of points. The event handlers will constantly update this list, while a render function takes care of displaying everything on the screen.

Take a look at the code snippet below.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

const TOTAL_ALLOWED_POINTS = 10;
let CANVAS_WIDTH;
let CANVAS_HEIGHT;
let context;

const pointList = [];

function setupCanvas() {
  const canvas = document.getElementById('canvas');
  context = canvas.getContext('2d');

  CANVAS_WIDTH = canvas.width;
  CANVAS_HEIGHT = canvas.height;

  canvas.addEventListener('mousemove', (e) => {
    const cursorPosition = new Point(e.offsetX, e.offsetY);

    // Include this point in the list for rendering.
    pointList.push(cursorPosition);

    // Since the list of points should remain finite,
    // remove the oldest point (FIFO).
    if (pointList.length > TOTAL_ALLOWED_POINTS - 1) {
      pointList.shift();
    }
  });

  // Start rendering.
  requestAnimationFrame(renderPoints);
}

function renderPoints() {
  if (!context) return;

  context.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

  context.beginPath();
  pointList.forEach((point, index) => {
    if (index === 0) return;

    const previousPoint = pointList[index - 1];

    context.moveTo(previousPoint.x, previousPoint.y);
    context.lineTo(point.x, point.y);
  });
  context.closePath();
  context.stroke();

  requestAnimationFrame(renderPoints);
}

setupCanvas();
HTML5 Canvas

The variable TOTAL_ALLOWED_POINTS determines how many points are visible on the screen simultaneously. Meanwhile, pointList holds the actual points for rendering.

In the mousemove event handler, we capture mouse coordinates and manage the number of points to maintain visibility. If the limit is reached, the earliest point is removed, creating the trailing effect you observe.

Within the render function renderPoints, we clear the canvas before drawing lines between the points from the updated list.

Note that I utilize requestAnimationFrame to continuously call renderPoints, ensuring continual updates regardless of point list changes. Alternatively, you can invoke renderPoints directly within the event handler.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the method for inserting a clickable link into a data-image line of code using CSS3?

Recently, I delved into the world of CSS and am still getting the hang of it, Below is a snippet of code that I have been working on, <div id='ninja-slider'> <ul> <li> <div data-image="images/md/1.j ...

Does 1 CSS point amount to the same as 75% of a CSS Pixel?

Can anyone help clarify the relationship between CSS pixels and CSS points in modern web design? I've come across information stating that one CSS point is equivalent to 3/4 of a pixel. Is this accurate? Also, is it true that we can disregard the old ...

Using AngularJS's ng-repeat directive to convert expressions into JavaScript code

I am successfully retrieving data from my Database using AngularJS, however, I am struggling to find a way to extract the data from ng-repeat and store it in a JavaScript Object. Is the data treated as an expression after ng-repeat is applied? I have hear ...

Dynamic line breaks within an unordered list

I have a requirement where I need to display the last two li elements from an ul list on a new line if the screen width is less than 625px. The code snippet below achieves this functionality: ... ... ... ... However, this code fails valida ...

Modify the name of the document

I have a piece of code that retrieves a file from the clipboard and I need to change its name if it is named image.png. Below is the code snippet where I attempt to achieve this: @HostListener('paste', ['$event']) onPaste(e: ClipboardE ...

unorthodox method of transmitting parameters within express.js source code

While searching for the source code of express router, I came across this interesting piece: var debug = require('debug')('express:router:route'); I am curious to know more about this unusual way of passing arguments. Can someone ...

Using a foreach loop to showcase data in a table

My goal is to utilize the GitHub API to showcase all the issues present in my repository in a neat table format on my website. Despite my best efforts, I am facing difficulties in generating the table using a foreach loop. Below is the PHP code I am curr ...

Create a loop in JavaScript to iterate through elements using both a while loop and the

I have a JSON object (returned by the server) and I am looking to iterate through it with either a while loop or forEach method in order to display the products in a shopping cart. The name of my object is response2, which contains the cart products. I w ...

Here are some steps for generating a non-integer random number that is not in the format of 1.2321312312

I am looking to create random numbers that are not integers, for example 2.45, 2.69, 4.52, with a maximum of two decimal places. Currently, the generated number includes many decimal places like 2.213123123123 but I would like it to be displayed as 2.21. ...

Unable to activate IndexedDb persistence with Firebase v9 in a Next.js PWA

I'm having trouble enabling IndexedDb persistence in Firebase v9 for a Next.js PWA. These errors keep popping up: index.js // main Firebase file import { initializeApp } from 'firebase/app' import { getAuth } from 'firebase/auth' ...

Vue: Load page content only after the route change is complete

Every time I visit a new route within the current session, I notice that the page loads and then takes about 2 seconds for the styles to be applied. This delay causes what is commonly known as CSS flashing or FOUC (Flash of Unstyled Content), which ultimat ...

Expand the spectrum of the input range

Is there a way to make the cursor and bar of a range input bigger without changing their length? <input id="speed" type="range" min="10" max="80" /> Any suggestions on how to achieve this? Appreciate your assistance. ...

What is the best approach for retrieving a User from my Angular front-end service?

INQUIRY: I'm attempting to retrieve the details of the currently logged in user in my Angular 2 frontend service with the following code: UserModel.findById(userID, function (err, user) { It appears that this behavior is achievable from the browse ...

The collapsed form-control is adjusting its attributes while resizing

Having trouble with a website design featuring a collapsed window and menu items, but the search bar is not displaying correctly? In image 3, you can see that the style of the search bar changes when the width exceeds a certain limit. I've tried vario ...

Troubleshooting Problem: Difficulty with Uploading High-Resolution Images in Angular using

Currently, I am working on implementing file uploads using the combination of express.js, node, and angular. Everything seems to be functioning well when dealing with small image files. However, I encountered a 404 error when attempting to upload larger im ...

Ways to align content in the center using Vuetify3

I have a component positioned at the top of the page, but I would like it to be centered instead. https://i.sstatic.net/wSFBP.png Something like this: https://i.sstatic.net/rfqAB.png Here is my current code: <template> <v-container class=" ...

Using ThreeJS to Apply Dual Materials to a Mesh Entity

With ThreeJS, it's possible to incorporate more than one material into an Object3D/Mesh as stated in the documentation. You can either utilize a single Material or an array of Material: Class declaration and constructor for Mesh TypeScript file (exce ...

Ensuring elements are properly centered within a Bootstrap grid while adjusting the window size

I've been facing a challenge with making my web page resizable. Even though I managed to center the elements using margin properties, whenever I try resizing the browser window, the logo and ship images lose their positions. They end up falling out of ...

What exactly is the significance of "utilizing a universal class name"?

In my current project, I am utilizing Material-UI version 3.1.2 to create the interface. Previously, when I was using Bootstrap4, I included style="overflow-y: scroll; height: 100%" in my head tag to ensure a scrollbar is always present, preventing the ap ...

Unable to reference the namespace 'ThemeDefinition' as a valid type within Vuetify

Looking to develop a unique theme for Vuetify v3.0.0-alpha.10 and I'm working with my vuetify.ts plugin file. import "@mdi/font/css/materialdesignicons.css"; import "vuetify/lib/styles/main.sass"; import { createVuetify, ThemeDefinition } from "v ...