242 lines
10 KiB
JavaScript
242 lines
10 KiB
JavaScript
![]() |
// src/core/GameBox.ts
|
||
|
export class GameBox {
|
||
|
destroy() {
|
||
|
// 移除事件监听器
|
||
|
window.removeEventListener('resize', () => this.updateSize());
|
||
|
window.removeEventListener('orientationchange', () => {
|
||
|
setTimeout(() => this.updateSize(), 100);
|
||
|
});
|
||
|
// 移除 DOM 元素
|
||
|
if (this.element && this.element.parentNode) {
|
||
|
this.element.parentNode.removeChild(this.element);
|
||
|
}
|
||
|
}
|
||
|
//
|
||
|
constructor(config) {
|
||
|
this.lastBackgroundPosition = { x: 50, y: 50 }; // Track background position
|
||
|
this.isTouching = false;
|
||
|
this.currentDirection = { x: 0, y: 0 };
|
||
|
this.moveInterval = null;
|
||
|
this.aspectRatio = config.aspectRatio;
|
||
|
this.backgroundColor = config.backgroundColor;
|
||
|
this.borderColor = config.borderColor;
|
||
|
this.container = config.element;
|
||
|
this.element = this.createBoxElement();
|
||
|
this.ballElement = this.createBall();
|
||
|
this.touchRingElement = this.createTouchRing();
|
||
|
this.container.style.display = 'flex';
|
||
|
this.container.style.justifyContent = 'center';
|
||
|
this.container.style.alignItems = 'center';
|
||
|
this.container.style.width = '100vw';
|
||
|
this.container.style.height = '100vh';
|
||
|
this.container.style.overflow = 'hidden';
|
||
|
this.bindEvents();
|
||
|
this.updateSize();
|
||
|
}
|
||
|
// 删除 createContainer 方法
|
||
|
createBoxElement() {
|
||
|
const element = document.createElement('div');
|
||
|
element.className = 'game-box';
|
||
|
element.style.boxSizing = 'border-box';
|
||
|
element.style.backgroundSize = 'auto 100%';
|
||
|
element.style.backgroundRepeat = 'no-repeat';
|
||
|
element.style.backgroundPosition = 'center center';
|
||
|
element.style.display = 'flex';
|
||
|
element.style.justifyContent = 'center';
|
||
|
element.style.alignItems = 'center';
|
||
|
this.applyBoxStyle(element);
|
||
|
this.container.appendChild(element);
|
||
|
return element;
|
||
|
}
|
||
|
applyBoxStyle(element) {
|
||
|
// Create grid background with random colors
|
||
|
const gridSize = 200; // Size of each grid square (doubled from previous)
|
||
|
const gridColor = 'rgba(0, 0, 0, 0.1)';
|
||
|
// Create canvas for random grid colors
|
||
|
const canvas = document.createElement('canvas');
|
||
|
const ctx = canvas.getContext('2d');
|
||
|
canvas.width = 2000;
|
||
|
canvas.height = 2000;
|
||
|
// Create gradient grid pattern
|
||
|
const baseColor = 200; // Base gray level
|
||
|
const colorStep = 40; // Increased color step for larger grids
|
||
|
for (let x = 0; x < canvas.width; x += gridSize) {
|
||
|
for (let y = 0; y < canvas.height; y += gridSize) {
|
||
|
// Calculate gradient based on grid position
|
||
|
const xCycle = Math.floor((x / gridSize) % 5);
|
||
|
const yCycle = Math.floor((y / gridSize) % 5);
|
||
|
const colorValue = baseColor - (xCycle + yCycle) * colorStep;
|
||
|
// Ensure color stays within valid range
|
||
|
const finalColor = Math.max(0, Math.min(255, colorValue));
|
||
|
ctx.fillStyle = `rgba(${finalColor},${finalColor},${finalColor},1)`;
|
||
|
ctx.fillRect(x, y, gridSize, gridSize);
|
||
|
// Add subtle border between cells
|
||
|
ctx.strokeStyle = 'rgba(0,0,0,0.1)';
|
||
|
ctx.strokeRect(x, y, gridSize, gridSize);
|
||
|
}
|
||
|
}
|
||
|
// Add grid lines and coordinates
|
||
|
const gridImage = canvas.toDataURL();
|
||
|
element.style.background = `
|
||
|
url('${gridImage}'),
|
||
|
linear-gradient(to right, ${gridColor} 1px, transparent 1px),
|
||
|
linear-gradient(to bottom, ${gridColor} 1px, transparent 1px),
|
||
|
${this.backgroundColor}
|
||
|
`;
|
||
|
element.style.backgroundSize = `${gridSize}px ${gridSize}px`;
|
||
|
element.style.border = `1px solid ${this.borderColor}`;
|
||
|
// Add coordinate labels
|
||
|
this.addGridCoordinates(element);
|
||
|
}
|
||
|
addGridCoordinates(element) {
|
||
|
const gridSize = 200;
|
||
|
const width = 2000; // Use canvas width for consistent coordinates
|
||
|
const height = 2000; // Use canvas height for consistent coordinates
|
||
|
// X-axis labels
|
||
|
for (let x = 0; x < width; x += gridSize) {
|
||
|
const label = document.createElement('div');
|
||
|
label.style.position = 'absolute';
|
||
|
label.style.left = `${x}px`;
|
||
|
label.style.bottom = '-20px';
|
||
|
label.style.color = this.borderColor;
|
||
|
label.style.fontSize = '12px';
|
||
|
label.innerText = `${x / gridSize}`;
|
||
|
element.appendChild(label);
|
||
|
}
|
||
|
// Y-axis labels
|
||
|
for (let y = 0; y < height; y += gridSize) {
|
||
|
const label = document.createElement('div');
|
||
|
label.style.position = 'absolute';
|
||
|
label.style.top = `${y}px`;
|
||
|
label.style.left = '-20px';
|
||
|
label.style.color = this.borderColor;
|
||
|
label.style.fontSize = '12px';
|
||
|
label.innerText = `${y / gridSize}`;
|
||
|
element.appendChild(label);
|
||
|
}
|
||
|
}
|
||
|
createBall() {
|
||
|
const ball = document.createElement('div');
|
||
|
ball.style.position = 'absolute';
|
||
|
ball.style.width = '40px';
|
||
|
ball.style.height = '40px';
|
||
|
ball.style.borderRadius = '50%';
|
||
|
ball.style.backgroundColor = 'red';
|
||
|
ball.style.transform = 'translate(-50%, -50%)';
|
||
|
ball.style.left = '50%';
|
||
|
ball.style.top = '50%';
|
||
|
this.element.appendChild(ball);
|
||
|
return ball;
|
||
|
}
|
||
|
createTouchRing() {
|
||
|
const ring = document.createElement('div');
|
||
|
ring.style.position = 'absolute';
|
||
|
ring.style.width = '100px';
|
||
|
ring.style.height = '100px';
|
||
|
ring.style.borderRadius = '50%';
|
||
|
ring.style.border = '2px solid rgba(255,255,255,0.5)';
|
||
|
ring.style.left = '20%';
|
||
|
ring.style.bottom = '30%';
|
||
|
ring.style.background = 'rgba(245, 222, 179, 0.5)'; // wheat color with 50% opacity
|
||
|
ring.style.transform = 'translate(-50%, 50%)';
|
||
|
ring.style.pointerEvents = 'auto';
|
||
|
this.element.appendChild(ring);
|
||
|
return ring;
|
||
|
}
|
||
|
bindEvents() {
|
||
|
window.addEventListener('resize', () => this.updateSize());
|
||
|
window.addEventListener('orientationchange', () => {
|
||
|
setTimeout(() => this.updateSize(), 100);
|
||
|
});
|
||
|
// Add touch event listeners
|
||
|
this.touchRingElement.addEventListener('touchstart', this.handleTouchStart.bind(this));
|
||
|
this.touchRingElement.addEventListener('touchmove', this.handleTouchMove.bind(this));
|
||
|
this.touchRingElement.addEventListener('touchend', this.handleTouchEnd.bind(this));
|
||
|
}
|
||
|
handleTouchStart(event) {
|
||
|
event.preventDefault();
|
||
|
this.isTouching = true;
|
||
|
this.updateBallPosition(event.touches[0]);
|
||
|
// Start continuous movement
|
||
|
this.moveInterval = window.setInterval(() => {
|
||
|
if (this.isTouching) {
|
||
|
this.lastBackgroundPosition.x += this.currentDirection.x;
|
||
|
this.lastBackgroundPosition.y += this.currentDirection.y;
|
||
|
this.element.style.backgroundPosition = `${this.lastBackgroundPosition.x}% ${this.lastBackgroundPosition.y}%`;
|
||
|
}
|
||
|
}, 16); // ~60fps
|
||
|
}
|
||
|
handleTouchMove(event) {
|
||
|
event.preventDefault();
|
||
|
this.updateBallPosition(event.touches[0]);
|
||
|
}
|
||
|
handleTouchEnd() {
|
||
|
this.isTouching = false;
|
||
|
if (this.moveInterval) {
|
||
|
window.clearInterval(this.moveInterval);
|
||
|
this.moveInterval = null;
|
||
|
}
|
||
|
}
|
||
|
updateBallPosition(touch) {
|
||
|
const rect = this.touchRingElement.getBoundingClientRect();
|
||
|
const ringCenterX = rect.left + rect.width / 2;
|
||
|
const ringCenterY = rect.top + rect.height / 2;
|
||
|
const ringRadius = rect.width / 2;
|
||
|
// Get touch position relative to ring center
|
||
|
const touchX = touch.clientX - ringCenterX;
|
||
|
const touchY = touch.clientY - ringCenterY;
|
||
|
// Calculate distance from center
|
||
|
const distance = Math.sqrt(touchX * touchX + touchY * touchY);
|
||
|
const normalizedDistance = Math.min(distance / ringRadius, 1);
|
||
|
// Calculate speed factor
|
||
|
let speedFactor = 0;
|
||
|
if (normalizedDistance > 0.5 && normalizedDistance <= 1) {
|
||
|
// Map distance from 0.5-1 to 0.8-1.2 in 5% increments
|
||
|
const steps = (normalizedDistance - 0.5) / 0.1;
|
||
|
speedFactor = 0.8 + Math.floor(steps) * 0.05;
|
||
|
}
|
||
|
// Calculate direction vector
|
||
|
const directionX = touchX / distance;
|
||
|
const directionY = touchY / distance;
|
||
|
// Normalize direction vector to maintain equal speed in both axes
|
||
|
const magnitude = Math.sqrt(directionX * directionX + directionY * directionY);
|
||
|
const normalizedX = directionX / magnitude;
|
||
|
const normalizedY = directionY / magnitude;
|
||
|
// Update current direction for continuous movement
|
||
|
this.currentDirection = {
|
||
|
x: -normalizedX * speedFactor * 0.5, // Keep horizontal speed unchanged
|
||
|
y: -normalizedY * speedFactor * 1.0 // Double vertical speed
|
||
|
};
|
||
|
// Update background position based on last position
|
||
|
this.lastBackgroundPosition.x += this.currentDirection.x;
|
||
|
this.lastBackgroundPosition.y += this.currentDirection.y;
|
||
|
this.element.style.backgroundPosition = `${this.lastBackgroundPosition.x}% ${this.lastBackgroundPosition.y}%`;
|
||
|
}
|
||
|
updateSize() {
|
||
|
const visualWidth = window.innerWidth;
|
||
|
const visualHeight = window.innerHeight;
|
||
|
const windowRatio = visualWidth / visualHeight;
|
||
|
let boxWidth, boxHeight;
|
||
|
if (windowRatio > this.aspectRatio) {
|
||
|
// Window is wider than aspect ratio - fit to height
|
||
|
boxHeight = visualHeight;
|
||
|
boxWidth = boxHeight * this.aspectRatio;
|
||
|
}
|
||
|
else {
|
||
|
// Window is taller than aspect ratio - fit to width
|
||
|
boxWidth = visualWidth;
|
||
|
boxHeight = boxWidth / this.aspectRatio;
|
||
|
}
|
||
|
// Set box dimensions
|
||
|
this.element.style.width = `${boxWidth}px`;
|
||
|
this.element.style.height = `${boxHeight}px`;
|
||
|
this.element.style.margin = 'auto'; // Use flexbox centering
|
||
|
// Update touch ring position
|
||
|
// this.touchRingElement.style.left = '20px';
|
||
|
// this.touchRingElement.style.bottom = '20px';
|
||
|
}
|
||
|
setBackground(imageUrl) {
|
||
|
this.element.style.backgroundImage = `url('${imageUrl}')`;
|
||
|
}
|
||
|
}
|