ben.wiegand.pw/www/cursor-thing.js

207 lines
5.4 KiB
JavaScript

const thing = document.getElementById('cursor-thing');
let updateTimerId;
let lastUpdate = 0;
// targeting
let mouseX = 0;
let mouseY = 0;
let scrollX = document.scrollingElement.scrollLeft;
let scrollY = document.scrollingElement.scrollTop;
const getTargetX = () => mouseX + scrollX;
const getTargetY = () => mouseY + scrollY;
// physics
let xPos = 0;
let yPos = 0;
let angle = 0;
let velocity = 0;
const maxVel = 600;
let accel = 0;
const maxAccel = 1500;
const accelCurve = (x) => maxAccel*(x/200)**3; // maxAccel when >150px distance
// more physics
const slideFric = .9; // coefficient of friction
const turnSpeedCoeff = .08; // coefficient of turning
// other stuff
let brake = false;
const brakeDist = 70;
const duckSize = 50;
// animation
let alt = false;
let lastAlt = 0;
function update() {
// timing
thisUpdate = Date.now();
deltaT = (thisUpdate - lastUpdate)/1000.0;
lastUpdate = thisUpdate;
// update trajectory
targetX = getTargetX();
targetY = getTargetY();
xDelta = targetX - xPos;
yDelta = targetY - yPos;
// find target angle
let targetAngle = Math.atan(yDelta/xDelta);
if (isNaN(targetAngle)) targetAngle = 0;
if (xDelta < 0) targetAngle = Math.PI*.5 - (targetAngle*-1);
else targetAngle = -((targetAngle*-1) - Math.PI*1.5);
// find shortest rotation delta
let aAngle = angle;
let aTargetAngle = targetAngle;
if (aTargetAngle > aAngle && aTargetAngle - aAngle > Math.PI) {
aAngle += Math.PI;
aTargetAngle -= Math.PI;
} else if (aAngle > aTargetAngle && aAngle - aTargetAngle > Math.PI) {
aAngle -= Math.PI;
aTargetAngle += Math.PI;
}
rDelta = aTargetAngle - aAngle;
// console.log(`myAngle: ${angle * (180 / Math.PI)} target angle: ${targetAngle * (180 / Math.PI)}`)
// console.log(`deelta: ${(rDelta) * (180 / Math.PI)} `);
// apply delta
angle += rDelta * turnSpeedCoeff;
// counter overflow
if (angle > 2*Math.PI) angle -= 2*Math.PI;
else if (angle < 0) angle += 2*Math.PI;
// stop when close
let dist = Math.sqrt((xPos-targetX)**2 + (yPos-targetY)**2);
brake = dist < brakeDist;
if (!brake) {
accel = accelCurve(dist);
if (accel > maxAccel) accel = maxAccel;
else if (accel < -maxAccel) accel = -maxAccel;
} else accel = 0;
// physics
velocity += accel * deltaT;
if (brake) {
// basic friction
velocity *= slideFric;
}
// vel limit
if (velocity > maxVel) velocity = maxVel;
else if (velocity < -maxVel) velocity = -maxVel
// translate vector to 2D
let hAngle = angle % Math.PI;
let right = angle > Math.PI;
let xVel = Math.sin(hAngle) * velocity;
let yVel = Math.cos(hAngle) * velocity;
if (right) {
yVel *= -1;
} else {
xVel *= -1;
}
// apply velocity
xPos += xVel * deltaT;
yPos += yVel * deltaT;
// bounce duck at edge of screen
if (xPos > document.scrollingElement.scrollWidth - duckSize) {
xPos = document.scrollingElement.scrollWidth - duckSize;
angle = 2*Math.PI - angle;
} else if (xPos < 0) {
xPos = 0;
angle = 2*Math.PI - angle;
}
if (yPos > document.scrollingElement.scrollHeight - duckSize) {
yPos = document.scrollingElement.scrollHeight - duckSize;
angle = Math.PI - angle;
} else if (yPos < 0) {
yPos = 0;
angle = Math.PI - angle;
}
// limit angle again
if (angle > 2*Math.PI) angle -= 2*Math.PI;
else if (angle < 0) angle += 2*Math.PI;
// console.log(`accel: ${accel}`);
// console.log(`velocity: ${velocity} (${xVel}, ${yVel})`)
let visualAngle = angle + (rDelta/2);
visualAngle = targetAngle;
thing.style.left = xPos + 'px';
thing.style.top = yPos + 'px';
// thing.style.rotate = visualAngle + 'rad';
// if (brake) {
// thing.style['background-color'] = '#00ff00';
// } else {
// thing.style['background-color'] = '#ff0000';
// }
let tAngle = visualAngle + Math.PI/4;
if (tAngle < Math.PI/2 || tAngle > Math.PI * 2) {
// front-facing
thing.style['background-position-x'] = (2*duckSize) + 'px';
} else if (tAngle < Math.PI) {
// left-facing
thing.style['background-position-x'] = '0px';
} else if (tAngle < Math.PI*3/2) {
// right-facing
thing.style['background-position-x'] = (3*duckSize) + 'px';
} else {
// back-facing
thing.style['background-position-x'] = duckSize + 'px';
}
if (thisUpdate - lastAlt > 70) {
lastAlt = thisUpdate;
alt = !alt;
}
if (velocity > 50) {
thing.style['background-position-y'] = (duckSize * (alt + 1)) + 'px';
} else {
thing.style['background-position-y'] = '0px';
}
}
// scroll and mouse are separate so the cursor is followed when scrolled
document.addEventListener('mousemove', e => {
mouseX = e.clientX;
mouseY = e.clientY;
});
document.addEventListener('scroll', e => {
el = e.target.scrollingElement;
scrollX = el.scrollLeft;
scrollY = el.scrollTop;
});
// kick-start duck
let duck_start = false;
function click_duck() {
if (duck_start) return;
duck_start = true;
xPos = thing.offsetLeft;
yPos = thing.offsetTop;
thing.style.position = 'absolute';
thing.style.cursor = 'auto';
lastUpdate = Date.now();
updateTimerId = setInterval(update, 10);
}