Tip of the day
Want to find something particular about the game and don't mind spoilers? Check the Wiki!
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 | ||
*/ | */ | ||
| Line 8: | Line 7: | ||
const CONFIG = { | const CONFIG = { | ||
scale: 1 | 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 | function getFrameSize(sprite) { | ||
const | const styles = window.getComputedStyle(sprite); | ||
return parseInt(styles.getPropertyValue('--frame'), 10) || 48; | |||
} | |||
function mountFollower(sourceNode) { | |||
if (state.mounted) return; | |||
state.mounted = true; | |||
const | const pet = sourceNode.cloneNode(true); | ||
const wrapper = document.createElement('span'); | |||
const centerOffset = | const frameSize = getFrameSize(sourceNode); | ||
const centerOffset = (frameSize * CONFIG.scale) / 2; | |||
wrapper.className = 'animated-cursor-follower-wrapper'; | |||
Object.assign( | 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', | ||
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( | 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.currentY += (state.targetY - state.currentY) * CONFIG.speed; | |||
state.currentY += | |||
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.facingRight = false; | state.facingRight = false; | ||
} | } | ||
const scaleX = | const scaleX = state.facingRight ? 1 : -1; | ||
wrapper.style.transform = | |||
`translate3d( | `translate3d(${state.currentX - centerOffset}px, ` + | ||
`${state.currentY - centerOffset}px, 0) scaleX(${scaleX})`; | |||
`${state.currentY - centerOffset}px, 0) | |||
state.reqId = | state.reqId = requestAnimationFrame(render); | ||
} | } | ||
render(); | render(); | ||
| Line 114: | Line 95: | ||
function init() { | function init() { | ||
const sprite = | const sprite = document.querySelector('span.ws-sprite'); | ||
if (!sprite) { | if (!sprite || !sprite.dataset.spriteApplied) { | ||
setTimeout(init, | setTimeout(init, 250); | ||
return; | return; | ||
} | } | ||
mountFollower(sprite); | |||
} | } | ||
if (document.readyState === 'complete') { | |||
init(); | |||
} else { | |||
window.addEventListener('load', init); | |||
} | |||
}()); | }()); | ||
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);
}
}());
