I'm trying to create an experience where a user can move a custom cursor over an image and alter the pixels that live within that custom cursor.
In the mousemove
event, I try to recreate this custom cursor path and use isPointInPath to determine if the custom cursor is "over" a given point in the main canvas's context.
The code does alter some pixels in the main canvas. However, the following unexpected behaviors are listed below:
(See the image below)
Why is this happening? How could the code be changed such that the custom cursor (aka circle) only changes pixels corresponding to that part of the main canvas?
let image;
let imageData;
const cursorCanvas = document.createElement("canvas");
const cursorCanvasContext = cursorCanvas.getContext("2d");
cursorCanvas.width = 100;
cursorCanvas.height = 100;
drawCursor();
const imageCanvas = document.querySelector("canvas");
imageCanvas.width = window.innerWidth;
imageCanvas.height = window.innerHeight;
const imageCanvasContext = imageCanvas.getContext("2d");
document.querySelector("img").addEventListener("load", (event) => {
image = event.target;
image.crossOrigin = "Anonymous";
drawBackground();
imageData = imageCanvasContext.getImageData(
0,
0,
imageCanvas.width,
imageCanvas.height
);
});
window.addEventListener("mousemove", (event) => {
requestAnimationFrame(() => {
if (imageData) {
imageCanvasContext.beginPath();
imageCanvasContext.arc(
event.clientX,
event.clientY,
cursorCanvas.width / 2,
0,
2 * Math.PI
);
imageCanvasContext.closePath();
for (let i = 0; i < imageData.data.length; i += 4) {
const y = Math.floor(i / imageData.width);
const x = i - y * imageData.width;
if (imageCanvasContext.isPointInPath(x, y)) {
imageData.data[i] = 40;
imageData.data[i + 1] = 40;
imageData.data[i + 2] = 40;
}
}
imageCanvasContext.putImageData(imageData, 0, 0);
}
imageCanvasContext.drawImage(
cursorCanvas,
event.clientX - cursorCanvas.width / 2,
event.clientY - cursorCanvas.height / 2
);
});
});
function drawBackground() {
imageCanvasContext.clearRect(
0,
0,
imageCanvas.width,
imageCanvas.height
);
if (image) {
const ratio =
window.innerWidth > window.innerHeight ?
window.innerWidth / window.innerHeight :
window.innerHeight / window.innerWidth;
imageCanvasContext.drawImage(
image,
0,
0,
image.width,
image.height,
0,
0,
ratio * imageCanvas.width,
ratio * imageCanvas.height
);
}
}
function drawCursor() {
cursorCanvasContext.beginPath();
cursorCanvasContext.arc(
cursorCanvas.width / 2,
cursorCanvas.width / 2,
cursorCanvas.width / 2,
0,
2 * Math.PI
);
cursorCanvasContext.stroke();
cursorCanvasContext.closePath();
}
html,
body {
width: 100vw;
height: 100vh;
overflow: hidden;
}
body {
font-family: sans-serif;
margin: 0;
}
img {
visibility: hidden;
position: absolute;
}
canvas {
cursor: none;
}
<img src="https://i.imgur.com/vzGhITt.jpeg" />
<canvas />
Ah, it seems that I have miscalculated the x and y coordinates of the imageData.
Incorrect:
const y = Math.floor(i / imageData.width);
const x = i - y * imageData.width;
Correct:
const x = (i / 4) % imageData.width;
const y = Math.floor(i / 4 / imageData.width);
let image;
let imageData;
const cursorCanvas = document.createElement("canvas");
const cursorCanvasContext = cursorCanvas.getContext("2d");
cursorCanvas.width = 100;
cursorCanvas.height = 100;
drawCursor();
const imageCanvas = document.querySelector("canvas");
imageCanvas.width = window.innerWidth;
imageCanvas.height = window.innerHeight;
const imageCanvasContext = imageCanvas.getContext("2d");
document.querySelector("img").addEventListener("load", (event) => {
image = event.target;
image.crossOrigin = "Anonymous";
drawBackground();
imageData = imageCanvasContext.getImageData(
0,
0,
imageCanvas.width,
imageCanvas.height
);
});
window.addEventListener("mousemove", (event) => {
if (imageData) {
imageCanvasContext.beginPath();
imageCanvasContext.arc(
event.clientX,
event.clientY,
cursorCanvas.width / 2,
0,
2 * Math.PI
);
imageCanvasContext.closePath();
for (let i = 0; i < imageData.data.length; i += 4) {
const x = (i / 4) % imageData.width;
const y = Math.floor(i / 4 / imageData.width);
if (imageCanvasContext.isPointInPath(x, y)) {
imageData.data[i] = 40;
imageData.data[i + 1] = 40;
imageData.data[i + 2] = 40;
}
}
imageCanvasContext.putImageData(imageData, 0, 0);
}
imageCanvasContext.drawImage(
cursorCanvas,
event.clientX - cursorCanvas.width / 2,
event.clientY - cursorCanvas.height / 2
);
});
function drawBackground() {
imageCanvasContext.clearRect(
0,
0,
imageCanvas.width,
imageCanvas.height
);
if (image) {
const ratio =
window.innerWidth > window.innerHeight ?
window.innerWidth / window.innerHeight :
window.innerHeight / window.innerWidth;
imageCanvasContext.drawImage(
image,
0,
0,
image.width,
image.height,
0,
0,
ratio * imageCanvas.width,
ratio * imageCanvas.height
);
}
}
function drawCursor() {
cursorCanvasContext.beginPath();
cursorCanvasContext.arc(
cursorCanvas.width / 2,
cursorCanvas.width / 2,
cursorCanvas.width / 2,
0,
2 * Math.PI
);
cursorCanvasContext.stroke();
cursorCanvasContext.closePath();
}
html,
body {
width: 100vw;
height: 100vh;
overflow: hidden;
}
body {
font-family: sans-serif;
margin: 0;
}
img {
visibility: hidden;
position: absolute;
}
canvas {
cursor: none;
}
<img src="https://i.imgur.com/vzGhITt.jpeg" />
<canvas />