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 1: Line 1:
/**
/**
  * Animated Cursor Follower
  * Animated Cursor Follower
* WalkScape Wiki mouse-following pets.
  */
  */


Line 8: Line 7:


const CONFIG = {
const CONFIG = {
scale: 1.5,
scale: 1,
speed: 0.5,
speed: 0.5,
offsetX: 30,
offsetX: 30,
Line 20: Line 19:
currentY: window.innerHeight / 2,
currentY: window.innerHeight / 2,
facingRight: true,
facingRight: true,
reqId: null
reqId: null,
mounted: false
};
};


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


const styles = window.getComputedStyle(sourceNode);
function mountFollower(sourceNode) {
if (state.mounted) return;
state.mounted = true;


const frameSize =
const pet = sourceNode.cloneNode(true);
parseInt(styles.getPropertyValue('--frame') || 48, 10);
const wrapper = document.createElement('span');


const centerOffset =
const frameSize = getFrameSize(sourceNode);
(frameSize * CONFIG.scale) / 2;
const centerOffset = (frameSize * CONFIG.scale) / 2;


pet.style.setProperty(
wrapper.className = 'animated-cursor-follower-wrapper';
'--scale',
String(CONFIG.scale)
);


Object.assign(pet.style, {
Object.assign(wrapper.style, {
position: 'fixed',
position: 'fixed',
top: '0',
top: '0',
Line 46: Line 47:
zIndex: '99999',
zIndex: '99999',
margin: '0',
margin: '0',
willChange: 'transform',
padding: '0',
transition: 'none'
width: `${frameSize * CONFIG.scale}px`,
height: `${frameSize * CONFIG.scale}px`,
willChange: 'transform'
});
 
pet.classList.add('animated-cursor-follower-pet');
pet.style.setProperty('--scale', String(CONFIG.scale));
 
// Keep the already-applied sprite data/style from the original clone.
// Do not transform the sprite itself; transform only the wrapper.
Object.assign(pet.style, {
position: 'static',
margin: '0',
pointerEvents: 'none'
});
});


document.body.appendChild(pet);
wrapper.appendChild(pet);
document.body.appendChild(wrapper);
 
document.addEventListener('mousemove', function (e) {
state.targetX = e.clientX + CONFIG.offsetX;
state.targetY = e.clientY + CONFIG.offsetY;
}, { passive: true });


function render() {
function render() {
state.currentX +=
state.currentX += (state.targetX - state.currentX) * CONFIG.speed;
(state.targetX - state.currentX) *
state.currentY += (state.targetY - state.currentY) * CONFIG.speed;
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;
} else if (
} else if (state.targetX < state.currentX - 1) {
state.targetX < state.currentX - 1
) {
state.facingRight = false;
state.facingRight = false;
}
}


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


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


state.reqId =
state.reqId = requestAnimationFrame(render);
requestAnimationFrame(render);
}
}
document.addEventListener(
'mousemove',
function (e) {
state.targetX =
e.clientX + CONFIG.offsetX;
state.targetY =
e.clientY + CONFIG.offsetY;
},
{ passive: true }
);
document.addEventListener(
'mouseleave',
function () {
cancelAnimationFrame(
state.reqId
);
}
);
document.addEventListener(
'mouseenter',
function () {
render();
}
);


render();
render();
Line 114: Line 95:


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


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


mountPet(sprite);
mountFollower(sprite);
}
}


mw.loader.using(
if (document.readyState === 'complete') {
['mediawiki.util'],
init();
function () {
} else {
$(window).on(
window.addEventListener('load', init);
'load',
}
function () {
setTimeout(init, 1000);
}
);
}
);


}());
}());

Revision as of 01:17, 23 May 2026

/**
 * Animated Cursor Follower
 */

(function () {
	'use strict';

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

	const state = {
		targetX: window.innerWidth / 2,
		targetY: window.innerHeight / 2,
		currentX: window.innerWidth / 2,
		currentY: window.innerHeight / 2,
		facingRight: true,
		reqId: null,
		mounted: 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 pet = sourceNode.cloneNode(true);
		const wrapper = document.createElement('span');

		const frameSize = getFrameSize(sourceNode);
		const centerOffset = (frameSize * CONFIG.scale) / 2;

		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'
		});

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

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

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

		document.addEventListener('mousemove', function (e) {
			state.targetX = e.clientX + CONFIG.offsetX;
			state.targetY = e.clientY + CONFIG.offsetY;
		}, { passive: true });

		function render() {
			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;

			wrapper.style.transform =
				`translate3d(${state.currentX - centerOffset}px, ` +
				`${state.currentY - 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);
	}

}());