Kinga
Kinga

Reputation: 3

Cursor visual effect

I need help, I just saw this main page on a great website www.snapplespiked.ca. I really like so much this kind of cursor-waving effect, when you moving the cursor all over the page. I was trying this web on Atom/VsCode/Codepen and CAN NOT get these effects on the whole page. It appears only in first place on the main page when you just entering on website. In the moment when you scrolling a page down to see the rest of the parts on the page the effect disappears, not working.
I use the same HTML and CSS and all js scripts and there is not the same effect as on the original website. What I did wrong? Is anyone know what I did wrong ?

This effect come from this part of the code in HTML:

<div class="c-background js-app-background">
  <div class="c-background__texture js-app-backgroundTexture">
    <div data-product-id="795" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/mango.png);" data-color="#fc9a44" class=""></div>
    <div data-product-id="804" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/lemon_peel.png);" data-color="#ffe24a" class=""></div>
    <div data-product-id="808" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/watermelon.png);" data-color="#4ca463" class="is-active"></div>
    <div data-product-id="798" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/raspberry.png);" data-color="#ff3443" class=""></div>
    <div data-product-id="806" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/strawberry.png);" data-color="#ff3443" class=""></div>
    <div data-product-id="802" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/lemon.png);" data-color="#ffe24a" class=""></div>
    <div data-product-id="800" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/peach.png);" data-color="#fc9a44" class=""></div>
  </div>
  <canvas class="c-background__canvas js-app-backgroundCanvas" style="width: 784px; height: 779px;" width="1176" height="1168"></canvas>
</div>
<div class="c-background c-background--foreground js-app-background">
  <div class="c-background__texture js-app-backgroundTexture">
    <div data-product-id="795" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/mango.png);" data-color="#fc9a44" class="is-active"></div>
    <div data-product-id="804" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/lemon_peel.png);" data-color="#fc9a44" class=""></div>
    <div data-product-id="808" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/watermelon.png);" data-color="#fc9a44" class=""></div>
    <div data-product-id="798" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/raspberry.png);" data-color="#fc9a44" class=""></div>
    <div data-product-id="806" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/strawberry.png);" data-color="#fc9a44" class=""></div>
    <div data-product-id="802" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/lemon.png);" data-color="#fc9a44" class=""></div>
    <div data-product-id="800" style="--url: url(https://www.snapplespiked.ca/wp-content/uploads/2021/02/peach.png);" data-color="#fc9a44" class=""></div>
  </div>
</div>

Upvotes: 0

Views: 177

Answers (1)

toastrackengima
toastrackengima

Reputation: 8762

You shouldn't directly copy code snippets from other websites, as you don't have the license to use them legally, and they're only really guarrenteed to be fit for their original purpose --- as you've found, it doesn't really work for your use case, and often it'll also result in a lot of redundant code.

In fact, this effect isn't particularlly hard, so lets try to re-create it ourselves!


Getting Started

The HTML5 canvas element lets us draw pixels onto it in various shapes. It's really useful for creating complex graphical effects like this one. The background of the canvas is transparent by default, and you can clear pixels off of it, so one way we could do this would be to fill the entire canvas white every frame, and then erase away the white where the cursor trails are to reveal the background underneath.

This means that we need to do a few things in our JavaScript:

  1. Keep track of the user's mouse movements over the canvas, and an initial size property of the blob, which we'll reduce over time.
  2. Every tick...
    1. Fill the canvas with white
    2. Loop over our list of blobs that we're keeping track of
      1. For each one, erase a circle from the canvas centered from the mouse's position at that time and with a radius equal to the size that we're also keeping track of.
      2. Decrement the size by some number so that next tick we draw the blob slightly smaller.
      3. If the size is less than some number (zero, say), then stop tracking that blob anymore

Getting the <canvas> in place

We need to start by making the canvas and placing it on the page. Almost everything else will be done with JS.

<div id="page">
    <canvas id="mouseTrailEffect"></canvas>
    <div class="content"></div>
</div>

The page is the container for both the canvas and the content. The content div contains all of the other content which should appear over the top of the canvas.

We want to do a couple things with the CSS here too:

#page {

    position: relative;
}

#page canvas {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
}

#page .content {
    position: absolute;
}

This makes sure that it fills its entire parent, but remains sorted behind the rest of the content.

In the JavaScript, we now need to create a reference to the canvas, it's drawing context, and we also need to adjust its size, so that the width and height are set explictly --- even though this has been done with CSS, this will actually just stretch the canvas's pixels to cover the requested area, rather than adding more pixels to it to match the space it covers 1-to-1, which results in it looking quite blurry. We also need to make sure we adjust its size again whenever the user resizes the window, to make it responsive.

function resizeCanvas(cv) {
    let rect = cv.getBoundingClientRect();
    cv.width = rect.width;
    cv.height = rect.height;
}

function setupMouseTrailEffect() {
    let cv = document.getElementById("mouseTrailEffect");
    let ctx = cv.getContext("2d");
    resizeCanvas(cv);
    
    window.addEventListener("resize", ()=>{
        resizeCanvas(cv);
    });
}

window.addEventListener("load", ()=>{ // When the page load is completed
    setupMouseTrailEffect();
});

Keeping track of the mouse movements

Whenever the user moves their cursor over our canvas, we get a mousemove event fired on it that we can listen to. We can then extract the X and Y positions of the mouse from that event (relative to the page's scroll position and the canvas's offset from the top-left corner of the page), and create an object like to represent the blob.

{x: ____, y: ____, size: ____}

We can add those objects into an array, which I'll call blobs. One thing to note here is that we actually listen for the mousemove event on the canvas's parent, not the canvas itself. This is so that it gets called even if the user hovers over something else on the page (e.g. some content) which isn't directly the canvas itself.

let blobs = [];
const INITIAL_BLOB_SIZE = 50; // This is radius that blobs will be when they start out! You can change it to make them bigger / smaller.

document.getElementById("page").addEventListener("mousemove", (ev)=>{
    blobs.push({
        x: ev.pageX - cv.offsetLeft,
        y: ev.pageY - cv.offsetTop,
        size: INITIAL_BLOB_SIZE
    });
});

Updating every tick

Now, we need to figure out how to update our canvas every tick. This can be done by first defining the function which should be run each tick, and then at the end of it telling the browser that the next animation frame it gets it should try and run our function again.

const tick = ()=>{
    ctx.clearRect(0, 0, cv.width, cv.height);
    ctx.fillStyle = "#ffffff"; // white
    ctx.fillRect(0, 0, cv.width, cv.height); // fill the entire canvas with white
    
    window.requestAnimationFrame(tick); // run this again ASAP
}

tick(); // Run the first tick to get it started!

Drawing the blobs

Now for the fun part!

Inside our tick function, after we fill everything white, lets loop through our blobs and erase them from the canvas. Note that the canvas works like e.g. Microsoft Paint does, in that there are no layers --- every pixel can have exactly one colour at one time. Filling it white sets them all to white, and to erase the area occupied by a blob we'll just draw a new shape there, and composite it so that it gets removed and the pixels are set to a fully transparent colour.

We also need to make sure that we adjust the size of the blobs each tick, and that we can remove them from the array once they reach a set minimum size. To do the latter, we'll make sure we loop through the array backwards, so that deleting one doesn't change the indexes that we're looping over.

const BLOB_DECAY_RATE = 2; // How many pixels to remove from the size of a blob each tick

const MIN_BLOB_SIZE = 0; // When a blob reaches this size, we stop tracking it and it disappears.

ctx.globalCompositeOperation = "destination-out"; // Instead of drawing these shapes, erase them instead!

for (let i = blobs.length - 1; i >= 0; i--) { // Loop over the list of blobs backwards
    ctx.beginPath(); // Create a path which we'll erase
        ctx.arc(blobs[i].x, blobs[i].y, blobs[i].size, 0, Math.PI * 2); // Add a arc to the path with is a circle, centered at the blob's X and Y positions, and with its size as the radius.
    ctx.fill();

    blobs[i].size -= BLOB_DECAY_RATE; // Adjust the blob's size
    
    if (blobs[i].size <= MIN_BLOB_SIZE) {
        blobs.splice(i, 1); // Remove the blob from the array
    }
}

ctx.globalCompositeOperation = "source-out"; // Stop erasing when we draw

All together now

Here's all that code put together into a working example!

const INITIAL_BLOB_SIZE = 50; // This is radius that blobs will be when they start out! You can change it to make them bigger / smaller.
const BLOB_DECAY_RATE = 4; // How many pixels to remove from the size of a blob each tick
const MIN_BLOB_SIZE = 20; // When a blob reaches this size, we stop tracking it and it disappears.


function resizeCanvas(cv) {
    let rect = cv.getBoundingClientRect();
    cv.width = rect.width;
    cv.height = rect.height;
}

function setupMouseTrailEffect() {
    let cv = document.getElementById("mouseTrailEffect");
    let ctx = cv.getContext("2d");
    resizeCanvas(cv);
    
    window.addEventListener("resize", ()=>{
        resizeCanvas(cv);
    });
    
    let blobs = [];

    document.getElementById("page").addEventListener("mousemove", (ev)=>{
        blobs.push({
            x: ev.pageX - cv.offsetLeft,
            y: ev.pageY - cv.offsetTop,
            size: INITIAL_BLOB_SIZE
        });
    });
    
    const tick = ()=>{
        ctx.globalCompositeOperation = "source-out"; // Stop erasing when we draw
        ctx.clearRect(0, 0, cv.width, cv.height);

        ctx.fillStyle = "#fff"; // white
        ctx.fillRect(0, 0, cv.width, cv.height); // fill the entire canvas with white

        ctx.globalCompositeOperation = "destination-out"; // Instead of drawing these shapes, erase them instead!

        for (let i = blobs.length - 1; i >= 0; i--) { // Loop over the list of blobs backwards
            ctx.beginPath(); // Create a path which we'll erase
                ctx.arc(blobs[i].x, blobs[i].y, blobs[i].size, 0, Math.PI * 2); // Add a arc to the path with is a circle, centered at the blob's X and Y positions, and with its size as the radius.
            ctx.fill();

            blobs[i].size -= BLOB_DECAY_RATE; // Adjust the blob's size

            if (blobs[i].size <= MIN_BLOB_SIZE) {
                blobs.splice(i, 1); // Remove the blob from the array
            }
        }

        window.requestAnimationFrame(tick); // Run this again ASAP!
    }

    tick(); // Run the first tick to get it started!
}

window.addEventListener("load", ()=>{ // When the page load is completed
    setupMouseTrailEffect();
});
#page {
    /** Gradient just so we can see the effect **/
    background-image: linear-gradient(0deg, #f00 0%, #ff0 100%);
    border: solid 1px #000;
    height: 250px;
    position: relative;
    padding: 10px;
}

#page canvas {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
}

#page .content {
    position: absolute;
}
<div id="page">
    <canvas id="mouseTrailEffect"></canvas>
    <div class="content">
        <h1>Some content</h1>
        Stuff above the canvas.
    </div>
</div>

You can tweak the three constants, INITIAL_BLOB_SIZE, BLOB_DECAY_RATE and MIN_BLOB_SIZE to make the effect closer to what you want (and I've moved them all together to the top of the snippet to make that easy :D).

Upvotes: 1

Related Questions