javascript 冻结滚动条

fdbelqdn  于 2023-01-24  发布在  Java
关注(0)|答案(1)|浏览(215)

我用javascript创建了一个游戏。代码放在页面的底部。把它形象化,就好像游戏在一个页脚的下面。当用户选择开始游戏按钮时,游戏就开始了。游戏的机制是由箭头键控制的。箭头键用来上下左右移动。问题是当我按向上或向下键时,页面也滚动了。如何停止这个?我希望游戏运行时页面不滚动。我附上了我创建的游戏代码。这是一个蛇游戏。也有CSS,但我没有附上它,因为我不认为它相关

<div id="app" class="app">
          <div class="start-screen">
            <h2>🦊 </h2>
      
            <div class="options">
              <h3>Choose Difficulty</h3>
              <p class="end-score"></p>
              <button data-difficulty="100" class="active">Easy</button>
              <button data-difficulty="75">Medium</button>
              <button data-difficulty="50">Hard</button>
            </div>
      
            <button class="play-btn">Play</button>
          </div>
      
          <canvas id="board"></canvas>
      
          <div class="score">0</div>
        </div>

<script>
class SnakeGame {
  constructor() {
    this.$app = document.querySelector('#app');
    this.$canvas = this.$app.querySelector('canvas');
    this.ctx = this.$canvas.getContext('2d');
    this.$startScreen = this.$app.querySelector('.start-screen');
    this.$score = this.$app.querySelector('.score');

    this.settings = {
      canvas: {
        width: 500,
        height: 500,
        background: 'white',
        border: 'black'
      },
      snake: {
        size: 20,
        background: '#73854A',
        border: '#000'
      }
    };

    this.game = {
      // "direction" (set in setUpGame())
      // "nextDirection" (set in setUpGame())
      // "score" (set in setUpGame())
      speed: 100,
      keyCodes: {
        38: 'up',
        40: 'down',
        39: 'right',
        37: 'left'
      }  
    }

    this.soundEffects = {
      score: new Audio('./sounds/score.mp3'),
      gameOver: new Audio('./sounds/game-over.mp3')
    };

    this.setUpGame();
    this.init();
  }

  init() {
    // Choose difficulty
    // Rather than using "this.$startScreen.querySelectorAll('button')" and looping over the node list
    // and attaching seperate event listeners on each item, it's more efficient to just listen in on the container and run a check at runtime
    this.$startScreen.querySelector('.options').addEventListener('click', event => {
      this.chooseDifficulty(event.target.dataset.difficulty);
    });

    // Play
    this.$startScreen.querySelector('.play-btn').addEventListener('click', () => {
      this.startGame();
    });
  }

  chooseDifficulty(difficulty) {
    if(difficulty) {
      this.game.speed = difficulty;
      this.$startScreen.querySelectorAll('.options button').forEach(btn => btn.classList.remove('active'));
      event.target.classList.add('active');
    }
  }

  setUpGame() {
    // The snake starts off with 5 pieces
    // Each piece is 30x30 pixels
    // Each following piece must be n times as far from the first piece
    const x = 300;
    const y = 300;

    this.snake = [
      { x: x, y: y },
      { x: x - this.settings.snake.size, y: y },
      { x: x - (this.settings.snake.size * 2), y: y },
      { x: x - (this.settings.snake.size * 3), y: y },
      { x: x - (this.settings.snake.size * 4), y: y }
    ];

    this.food = {
      active: false,
      background: '#EC5E0B',
      border: '#73AA24',
      coordinates: {
        x: 0,
        y: 0  
      }
    };

    this.game.score = 0;
    this.game.direction = 'right';
    this.game.nextDirection = 'right';
  }

  startGame() {
    // Stop the game over sound effect if a new game was restarted quickly before it could end
    this.soundEffects.gameOver.pause();
    this.soundEffects.gameOver.currentTime = 0;

    // Reset a few things from the prior game
    this.$app.classList.add('game-in-progress');
    this.$app.classList.remove('game-over');
    this.$score.innerText = 0;

    this.generateSnake();

    this.startGameInterval = setInterval(() => {
      if(!this.detectCollision()) {
        this.generateSnake();
      } else {
        this.endGame();
      }
    }, this.game.speed);

    // Change direction
    document.addEventListener('keydown', event => {
      this.changeDirection(event.keyCode);
    });
  }

  changeDirection(keyCode) {
    const validKeyPress = Object.keys(this.game.keyCodes).includes(keyCode.toString()); // Only allow (up|down|left|right)

    if(validKeyPress && this.validateDirectionChange(this.game.keyCodes[keyCode], this.game.direction)) {
      this.game.nextDirection = this.game.keyCodes[keyCode];
    }
  }

  // When already moving in one direction snake shouldn't be allowed to move in the opposite direction
  validateDirectionChange(keyPress, currentDirection) {
    return (keyPress === 'left' && currentDirection !== 'right') || 
      (keyPress === 'right' && currentDirection !== 'left') ||
      (keyPress === 'up' && currentDirection !== 'down') ||
      (keyPress === 'down' && currentDirection !== 'up');
  }

  resetCanvas() {
    // Full screen size
    this.$canvas.width = this.settings.canvas.width;
    this.$canvas.height = this.settings.canvas.height;
    this.$canvas.style.border = `3px solid ${this.settings.canvas.border}`;

    // Background
    this.ctx.fillStyle = this.settings.canvas.background;
    this.ctx.fillRect(0, 0, this.$canvas.width, this.$canvas.height);
  }

  generateSnake() {
    let coordinate;

    switch(this.game.direction) {
      case 'right':
        coordinate = {
          x: this.snake[0].x + this.settings.snake.size,
          y: this.snake[0].y
        };
      break;
      case 'up':
        coordinate = {
          x: this.snake[0].x,
          y: this.snake[0].y - this.settings.snake.size
        };
      break;
      case 'left':
        coordinate = {
          x: this.snake[0].x - this.settings.snake.size,
          y: this.snake[0].y
        };
      break;
      case 'down':
        coordinate = {
          x: this.snake[0].x,
          y: this.snake[0].y + this.settings.snake.size
        };
    }

    // The snake moves by adding a piece to the beginning "this.snake.unshift(coordinate)" and removing the last piece "this.snake.pop()"
    // Except when it eats the food in which case there is no need to remove a piece and the added piece will make it grow
    this.snake.unshift(coordinate);
    this.resetCanvas();

    const ateFood = this.snake[0].x === this.food.coordinates.x && this.snake[0].y === this.food.coordinates.y;

    if(ateFood) {
      this.food.active = false;
      this.game.score += 10;
      this.$score.innerText = this.game.score;
      this.soundEffects.score.play();
    } else {
      this.snake.pop();
    }

    this.generateFood();
    this.drawSnake();
  }

  drawSnake() {
    const size = this.settings.snake.size;

    this.ctx.fillStyle = this.settings.snake.background;
    this.ctx.strokestyle = this.settings.snake.border;

    // Draw each piece
    this.snake.forEach(coordinate => {
      this.ctx.fillRect(coordinate.x, coordinate.y, size, size);
      this.ctx.strokeRect(coordinate.x, coordinate.y, size, size);
    });

    this.game.direction = this.game.nextDirection;
  }

  generateFood() {
    // If there is uneaten food on the canvas there's no need to regenerate it
    if(this.food.active) {
      this.drawFood(this.food.coordinates.x, this.food.coordinates.y);
      return;
    }

    const gridSize = this.settings.snake.size;
    const xMax = this.settings.canvas.width - gridSize;
    const yMax = this.settings.canvas.height - gridSize;

    const x = Math.round((Math.random() * xMax) / gridSize) * gridSize;
    const y = Math.round((Math.random() * yMax) / gridSize) * gridSize;

    // Make sure the generated coordinates do not conflict with the snake's present location
    // If so recall this method recursively to try again
    this.snake.forEach(coordinate => {
      const foodSnakeConflict = coordinate.x == x && coordinate.y == y;

      if(foodSnakeConflict) {
        this.generateFood();
      } else {
        this.drawFood(x, y);
      }
    });
  }

  drawFood(x, y) {
    const size = this.settings.snake.size;

    this.ctx.fillStyle = this.food.background;
    this.ctx.strokestyle = this.food.border;

    this.ctx.fillRect(x, y, size, size);
    this.ctx.strokeRect(x, y, size, size);

    this.food.active = true;
    this.food.coordinates.x = x;
    this.food.coordinates.y = y;
  }

  detectCollision() {
    // Self collison
    // It's impossible for the first 3 pieces of the snake to self collide so the loop starts at 4
    for(let i = 4; i < this.snake.length; i++) {
      const selfCollison = this.snake[i].x === this.snake[0].x && this.snake[i].y === this.snake[0].y;

      if(selfCollison) {
        return true;
      }
    }

    // Wall collison
    const leftCollison = this.snake[0].x < 0;
    const topCollison = this.snake[0].y < 0;
    const rightCollison = this.snake[0].x > this.$canvas.width - this.settings.snake.size;
    const bottomCollison = this.snake[0].y > this.$canvas.height - this.settings.snake.size;

    return leftCollison || topCollison || rightCollison || bottomCollison;
  }

  endGame() {
    this.soundEffects.gameOver.play();

    clearInterval(this.startGameInterval);

    this.$app.classList.remove('game-in-progress');
    this.$app.classList.add('game-over');
    this.$startScreen.querySelector('.options h3').innerText = 'Game Over';
    this.$startScreen.querySelector('.options .end-score').innerText = `Score: ${this.game.score}`;

    this.setUpGame();
  }
}

const snakeGame = new SnakeGame();
</script>

当游戏处于活动状态时,停止页面上下移动

bq8i3lrv

bq8i3lrv1#

您只需要防止这些键的默认行为:

// Change direction
document.addEventListener('keydown', event => {
  event.preventDefault(); // add this line
  this.changeDirection(event.keyCode);
});

请注意,这样做并不是一个好主意,因为它会阻止页面上 * 所有 * 按键 * 任何地方 * 的默认行为。最值得注意的是,F5永远不会刷新浏览器,如果您有其他元素,如按钮,链接等,用于激活那些的标准按键将停止工作(如果你想让你的网站对那些有残疾的人无障碍,关闭这些是一个特别的问题)。
您最好先检查keyCode,并且只在它对应于其中一个箭头键时才防止默认值--这对于您的代码来说并不容易,但我鼓励您以这种方式重写它。
即使这样做也会给那些不会使用鼠标的人带来问题,因为(取决于你的游戏界面在屏幕上的位置和大小)他们可能仍然需要向下滚动,而preventDefault行为会阻止他们这样做。但这正是你所要求的,我已经给了你解决方案-但有一个重要的警告。

相关问题