Tip of the day
Equipping a gear set while at a bank will automatically withdraw all the items from the bank.

MediaWiki:Gadget-AnimatedCursorFollower.js: Difference between revisions

From Walkscape Walkthrough
mNo edit summary
mNo edit summary
Line 33: Line 33:
state.mounted = true;
state.mounted = true;


const pet = sourceNode.cloneNode(true);
const wrapper = document.createElement('span');
const wrapper = document.createElement('span');
const frameSize = getFrameSize(sourceNode);
const frameSize = getFrameSize(sourceNode);
const centerOffset = (frameSize * CONFIG.scale) / 2;
const centerOffset = (frameSize * CONFIG.scale) / 2;
let pet = null;


wrapper.className = 'animated-cursor-follower-wrapper';
wrapper.className = 'animated-cursor-follower-wrapper';
Line 55: Line 55:
});
});


pet.classList.add('animated-cursor-follower-pet');
function setPetSource(newSourceNode) {
pet.style.setProperty('--scale', String(CONFIG.scale));
const newPet = newSourceNode.cloneNode(true);
 
newPet.classList.add('animated-cursor-follower-pet');
newPet.style.setProperty('--scale', String(CONFIG.scale));


// Keep the already-applied sprite data/style from the original clone.
Object.assign(newPet.style, {
// Do not transform the sprite itself; transform only the wrapper.
position: 'static',
Object.assign(pet.style, {
margin: '0',
position: 'static',
pointerEvents: 'none'
margin: '0',
});
pointerEvents: 'none'
 
});
wrapper.replaceChildren(newPet);
pet = newPet;
}


wrapper.appendChild(pet);
setPetSource(sourceNode);
document.body.appendChild(wrapper);
document.body.appendChild(wrapper);


document.addEventListener('mousemove', function (e) {
document.addEventListener('mousemove', function (e) {
if (!state.hasMouse) {
if (!state.hasMouse) {
state.hasMouse = true;
state.hasMouse = true;
 
state.targetX = e.clientX;
state.targetX = e.clientX;
state.targetY = e.clientY;
state.targetY = e.clientY;
 
state.currentX = e.clientX;
state.currentX = e.clientX;
state.currentY = e.clientY;
state.currentY = e.clientY;
 
wrapper.style.display = '';
wrapper.style.display = '';
}
}
 
state.targetX = e.clientX;
state.targetX = e.clientX;
state.targetY = e.clientY;
state.targetY = e.clientY;
}, { passive: true });
}, { passive: true });
 
document.documentElement.addEventListener('mouseout', function (e) {
document.documentElement.addEventListener('mouseout', function (e) {
if (!e.relatedTarget && !e.toElement) {
if (!e.relatedTarget && !e.toElement) {
Line 94: Line 97:
}
}
});
});
document.addEventListener('click', function (e) {
const clickedSprite = e.target.closest('span.ws-sprite');
if (!clickedSprite) return;
if (clickedSprite.classList.contains('animated-cursor-follower-pet')) {
return;
}
if (!clickedSprite.dataset.spriteApplied) {
return;
}
setPetSource(clickedSprite);
}, { passive: true });


function render() {
function render() {
Line 100: Line 119:
return;
return;
}
}
 
state.currentX += (state.targetX - state.currentX) * CONFIG.speed;
state.currentX += (state.targetX - state.currentX) * CONFIG.speed;
state.currentY += (state.targetY - state.currentY) * CONFIG.speed;
state.currentY += (state.targetY - state.currentY) * CONFIG.speed;
 
if (state.targetX > state.currentX + 1) {
if (state.targetX > state.currentX + 1) {
state.facingRight = true;
state.facingRight = true;
Line 109: Line 128:
state.facingRight = false;
state.facingRight = false;
}
}
 
const scaleX = state.facingRight ? 1 : -1;
const scaleX = state.facingRight ? 1 : -1;
 
const xOffset = state.facingRight
const xOffset = state.facingRight
? -CONFIG.offsetX
? -CONFIG.offsetX
: CONFIG.offsetX;
: CONFIG.offsetX;
 
wrapper.style.transform =
wrapper.style.transform =
`translate3d(` +
`translate3d(` +
Line 121: Line 140:
`${state.currentY + CONFIG.offsetY - centerOffset}px, 0)` +
`${state.currentY + CONFIG.offsetY - centerOffset}px, 0)` +
` scaleX(${scaleX})`;
` scaleX(${scaleX})`;
 
state.reqId = requestAnimationFrame(render);
state.reqId = requestAnimationFrame(render);
}
}

Revision as of 01:28, 23 May 2026

/**
 * Animated Cursor Follower
 */

(function () {
	'use strict';

	const CONFIG = {
		scale: 1,
		speed: 0.5,
		offsetX: 30,
		offsetY: 30
	};

	const state = {
		targetX: 0,
		targetY: 0,
		currentX: 0,
		currentY: 0,
		facingRight: true,
		reqId: null,
		mounted: false,
		hasMouse: false
	};

	function getFrameSize(sprite) {
		const styles = window.getComputedStyle(sprite);
		return parseInt(styles.getPropertyValue('--frame'), 10) || 48;
	}

	function mountFollower(sourceNode) {
		if (state.mounted) return;
		state.mounted = true;

		const wrapper = document.createElement('span');
		const frameSize = getFrameSize(sourceNode);
		const centerOffset = (frameSize * CONFIG.scale) / 2;

		let pet = null;

		wrapper.className = 'animated-cursor-follower-wrapper';

		Object.assign(wrapper.style, {
			position: 'fixed',
			top: '0',
			left: '0',
			pointerEvents: 'none',
			zIndex: '99999',
			margin: '0',
			padding: '0',
			width: `${frameSize * CONFIG.scale}px`,
			height: `${frameSize * CONFIG.scale}px`,
			willChange: 'transform',
			display: 'none'
		});

		function setPetSource(newSourceNode) {
			const newPet = newSourceNode.cloneNode(true);

			newPet.classList.add('animated-cursor-follower-pet');
			newPet.style.setProperty('--scale', String(CONFIG.scale));

			Object.assign(newPet.style, {
				position: 'static',
				margin: '0',
				pointerEvents: 'none'
			});

			wrapper.replaceChildren(newPet);
			pet = newPet;
		}

		setPetSource(sourceNode);
		document.body.appendChild(wrapper);

		document.addEventListener('mousemove', function (e) {
			if (!state.hasMouse) {
				state.hasMouse = true;

				state.targetX = e.clientX;
				state.targetY = e.clientY;

				state.currentX = e.clientX;
				state.currentY = e.clientY;

				wrapper.style.display = '';
			}

			state.targetX = e.clientX;
			state.targetY = e.clientY;
		}, { passive: true });

		document.documentElement.addEventListener('mouseout', function (e) {
			if (!e.relatedTarget && !e.toElement) {
				state.hasMouse = false;
				wrapper.style.display = 'none';
			}
		});

		document.addEventListener('click', function (e) {
			const clickedSprite = e.target.closest('span.ws-sprite');

			if (!clickedSprite) return;

			if (clickedSprite.classList.contains('animated-cursor-follower-pet')) {
				return;
			}

			if (!clickedSprite.dataset.spriteApplied) {
				return;
			}

			setPetSource(clickedSprite);
		}, { passive: true });

		function render() {
			if (!state.hasMouse) {
				state.reqId = requestAnimationFrame(render);
				return;
			}

			state.currentX += (state.targetX - state.currentX) * CONFIG.speed;
			state.currentY += (state.targetY - state.currentY) * CONFIG.speed;

			if (state.targetX > state.currentX + 1) {
				state.facingRight = true;
			} else if (state.targetX < state.currentX - 1) {
				state.facingRight = false;
			}

			const scaleX = state.facingRight ? 1 : -1;

			const xOffset = state.facingRight
				? -CONFIG.offsetX
				: CONFIG.offsetX;

			wrapper.style.transform =
				`translate3d(` +
				`${state.currentX + xOffset - centerOffset}px, ` +
				`${state.currentY + CONFIG.offsetY - centerOffset}px, 0)` +
				` scaleX(${scaleX})`;

			state.reqId = requestAnimationFrame(render);
		}

		render();
	}

	function init() {
		const sprite = document.querySelector('span.ws-sprite');

		if (!sprite || !sprite.dataset.spriteApplied) {
			setTimeout(init, 250);
			return;
		}

		mountFollower(sprite);
	}

	if (document.readyState === 'complete') {
		init();
	} else {
		window.addEventListener('load', init);
	}

}());