@meganetaaan/mouse-follower
Version:
TypeScript library for creating animated sprites that smoothly follow mouse cursor or other targets using physics-based movement
134 lines (133 loc) • 4.34 kB
JavaScript
/**
* Sprite utility functions - stateless operations for sprite handling
*/
/**
* Load sprite image from URL
*/
export async function loadSpriteImage(url) {
return new Promise((resolve, reject) => {
const image = new Image();
image.crossOrigin = "anonymous";
image.onload = () => resolve(image);
image.onerror = () => reject(new Error(`Failed to load sprite: ${url}`));
image.src = url;
});
}
/**
* Apply transparency to an image using color key
*/
export function applyTransparency(image, transparentColor) {
// Create temporary canvas for processing
const canvas = document.createElement("canvas");
const context = canvas.getContext("2d");
if (!context) {
return image; // Return original if processing fails
}
canvas.width = image.width;
canvas.height = image.height;
// Draw image to canvas
context.drawImage(image, 0, 0);
// Get image data
const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
// Parse transparent color
const [targetR, targetG, targetB] = parseColor(transparentColor);
// Apply transparency
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Check if pixel matches transparent color (with tolerance)
if (Math.abs(r - targetR) <= 5 &&
Math.abs(g - targetG) <= 5 &&
Math.abs(b - targetB) <= 5) {
data[i + 3] = 0; // Set alpha to 0
}
}
// Put modified image data back
context.putImageData(imageData, 0, 0);
// Create new image from canvas
const processedImage = new Image();
processedImage.src = canvas.toDataURL();
return processedImage;
}
/**
* Parse color string to RGB values
*/
export function parseColor(colorString) {
// Handle rgb(r, g, b) format
const rgbMatch = colorString.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
return [
Number.parseInt(rgbMatch[1], 10),
Number.parseInt(rgbMatch[2], 10),
Number.parseInt(rgbMatch[3], 10),
];
}
// Handle hex format (#RRGGBB or #RGB)
const hexMatch = colorString.match(/^#([0-9a-fA-F]{3,6})$/);
if (hexMatch) {
const hex = hexMatch[1];
if (hex.length === 3) {
// Convert #RGB to #RRGGBB
return [
Number.parseInt(hex[0] + hex[0], 16),
Number.parseInt(hex[1] + hex[1], 16),
Number.parseInt(hex[2] + hex[2], 16),
];
}
if (hex.length === 6) {
return [
Number.parseInt(hex.slice(0, 2), 16),
Number.parseInt(hex.slice(2, 4), 16),
Number.parseInt(hex.slice(4, 6), 16),
];
}
}
// Default to green if parsing fails
console.warn(`Failed to parse color: ${colorString}, using default green`);
return [0, 255, 0];
}
/**
* Create wrapper element for sprite
*/
export function createWrapper(className = "mouse-follower") {
const wrapper = document.createElement("div");
wrapper.className = className;
wrapper.style.position = "fixed";
wrapper.style.left = "0";
wrapper.style.top = "0";
wrapper.style.pointerEvents = "none";
wrapper.style.zIndex = "9999";
return wrapper;
}
/**
* Create sprite container element
*/
export function createSpriteContainer(width, height) {
const container = document.createElement("div");
container.style.width = `${width}px`;
container.style.height = `${height}px`;
container.style.position = "absolute";
container.style.pointerEvents = "none";
return container;
}
/**
* Process sprite image with transparency
*/
export async function processSpriteImage(url, transparentColor) {
const image = await loadSpriteImage(url);
if (transparentColor) {
// Wait for processed image to load
return new Promise((resolve) => {
const processed = applyTransparency(image, transparentColor);
if (processed.complete) {
resolve(processed);
}
else {
processed.onload = () => resolve(processed);
}
});
}
return image;
}