You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Khan Academy记忆瓦片项目hover状态失效问题求助

解决记忆瓦片游戏中Hover状态闪烁消失的问题

嘿,我完全懂你卡在这里的挫败感——这个hover效果一闪而过的问题确实戳中了Canvas绘图的核心逻辑:draw()函数会每一帧清空画布并重绘所有元素,你之前在mouseMoved()里直接调用hoverState()绘制的内容,下一秒就被draw()循环里的tiles[i].draw()给覆盖掉了。加上代码里还有几个小bug(比如循环越界、调用不存在的方法),导致效果完全出不来。

下面是一步步的解决方案:

1. 给Tile类添加hover状态标记

我们不需要直接在鼠标事件里绘制,而是给每个瓦片加一个isHovered属性,用来标记它是否处于hover状态,让draw()函数根据这个状态来渲染样式:

var Tile = function(x, y, face) { 
  this.x = x; 
  this.y = y; 
  this.width = 70; 
  this.face = face; 
  this.isFaceUp = false; 
  this.isMatch = false; 
  this.isHovered = false; // 新增:标记是否处于hover状态
}; 

2. 重构Tile的draw方法,支持状态驱动绘制

把所有绘制逻辑统一到draw()里,让它根据isMatch、isFaceUp、isHovered的优先级来渲染对应的样式:

Tile.prototype.draw = function() {
  strokeWeight(2);
  // 根据状态依次判断渲染逻辑
  if (this.isMatch) {
    // 匹配成功的瓦片,保持显示正面
    fill(214, 247, 202);
    rect(this.x, this.y, this.width, this.width, 10);
    image(this.face, this.x, this.y, this.width, this.width);
  } else if (this.isFaceUp) {
    // 已翻面的瓦片,显示正面
    fill(214, 247, 202);
    rect(this.x, this.y, this.width, this.width, 10);
    image(this.face, this.x, this.y, this.width, this.width);
  } else if (this.isHovered) {
    // hover状态的瓦片,显示你自定义的样式
    fill(74, 122, 128);
    rect(this.x, this.y, this.width, this.width, 10);
    image(getImage("avatars/leaf-yellow"), this.x, this.y, this.width, this.width);
  } else {
    // 默认未翻面状态
    fill(214, 247, 202);
    rect(this.x, this.y, this.width, this.width, 10);
    image(getImage("avatars/leaf-green"), this.x, this.y, this.width, this.width);
  }
};

3. 修复mouseMoved函数,仅更新状态而非直接绘制

原来的mouseMoved有两个问题:循环条件越界(i <= tiles.length)、调用不存在的drawFaceUp()/drawFaceDown()方法。现在我们改成只更新瓦片的isHovered状态:

mouseMoved = function() {
  // 先重置所有瓦片的hover状态
  for (var i = 0; i < tiles.length; i++) {
    tiles[i].isHovered = false;
  }
  // 找到鼠标下方的瓦片,设置hover状态(仅未匹配、未翻面的瓦片生效)
  for (var i = 0; i < tiles.length; i++) {
    var tile = tiles[i];
    if (tile.isUnderMouse(mouseX, mouseY) && !tile.isFaceUp && !tile.isMatch) {
      tile.isHovered = true;
    }
  }
  // 触发重绘,让draw()函数渲染最新状态
  loop();
};

4. 清理冗余代码

删掉原来的Tile.prototype.hoverState()方法,因为我们已经把hover的绘制逻辑整合到draw()里了,不需要单独的方法。

为什么这样能解决问题?

现在所有的绘制逻辑都由draw()函数统一管理,mouseMoved()只负责更新瓦片的状态标记。每一帧draw()都会根据最新的状态(包括hover)重新绘制所有元素,再也不会出现“刚绘制的hover效果被下一帧清空”的情况了。

完整修改后的代码

var Tile = function(x, y, face) { 
  this.x = x; 
  this.y = y; 
  this.width = 70; 
  this.face = face; 
  this.isFaceUp = false; 
  this.isMatch = false; 
  this.isHovered = false; // 新增hover状态标记
}; 

Tile.prototype.draw = function() {
  strokeWeight(2);
  if (this.isMatch) {
    fill(214, 247, 202);
    rect(this.x, this.y, this.width, this.width, 10);
    image(this.face, this.x, this.y, this.width, this.width);
  } else if (this.isFaceUp) {
    fill(214, 247, 202);
    rect(this.x, this.y, this.width, this.width, 10);
    image(this.face, this.x, this.y, this.width, this.width);
  } else if (this.isHovered) {
    fill(74, 122, 128);
    rect(this.x, this.y, this.width, this.width, 10);
    image(getImage("avatars/leaf-yellow"), this.x, this.y, this.width, this.width);
  } else {
    fill(214, 247, 202);
    rect(this.x, this.y, this.width, this.width, 10);
    image(getImage("avatars/leaf-green"), this.x, this.y, this.width, this.width);
  }
}; 

Tile.prototype.isUnderMouse = function(x, y) { 
  return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.width; 
}; 

// Global config 
var NUM_COLS = 5; 
var NUM_ROWS = 4; 

// Declare an array of all possible faces 
var faces = [ 
  getImage("avatars/leafers-seed"), 
  getImage("avatars/leafers-seedling"), 
  getImage("avatars/leafers-sapling"), 
  getImage("avatars/leafers-tree"), 
  getImage("avatars/leafers-ultimate"), 
  getImage("avatars/marcimus"), 
  getImage("avatars/mr-pants"), 
  getImage("avatars/mr-pink"), 
  getImage("avatars/old-spice-man"), 
  getImage("avatars/robot_female_1") 
]; 

// Make an array which has 2 of each, then randomize it 
var possibleFaces = faces.slice(0); 
var selected = []; 
for (var i = 0; i < (NUM_COLS * NUM_ROWS) / 2; i++) { 
  var randomInd = floor(random(possibleFaces.length)); 
  var face = possibleFaces[randomInd]; 
  selected.push(face); 
  selected.push(face); 
  possibleFaces.splice(randomInd, 1); 
} 

// Now shuffle the elements of that array 
var shuffleArray = function(array) { 
  var counter = array.length; 
  while (counter > 0) { 
    var ind = Math.floor(Math.random() * counter); 
    counter--; 
    var temp = array[counter]; 
    array[counter] = array[ind]; 
    array[ind] = temp; 
  } 
}; 
shuffleArray(selected); 

// Create the tiles 
var tiles = []; 
for (var i = 0; i < NUM_COLS; i++) { 
  for (var j = 0; j < NUM_ROWS; j++) { 
    var tileX = i * 78 + 10; 
    var tileY = j * 78 + 40; 
    var tileFace = selected.pop(); 
    tiles.push(new Tile(tileX, tileY, tileFace)); 
  } 
} 

background(255, 255, 255); 
var numTries = 0; 
var numMatches = 0; 
var flippedTiles = []; 
var delayStartFC = null; 

mouseClicked = function() { 
  for (var i = 0; i < tiles.length; i++) { 
    var tile = tiles[i]; 
    if (tile.isUnderMouse(mouseX, mouseY)) { 
      if (flippedTiles.length < 2 && !tile.isFaceUp) { 
        tile.isFaceUp = true; 
        flippedTiles.push(tile); 
        if (flippedTiles.length === 2) { 
          numTries++; 
          if (flippedTiles[0].face === flippedTiles[1].face) { 
            flippedTiles[0].isMatch = true; 
            flippedTiles[1].isMatch = true; 
            flippedTiles.length = 0; 
            numMatches++; 
          } 
          delayStartFC = frameCount; 
        } 
      } 
      loop(); 
    } 
  } 
}; 

draw = function() { 
  background(255, 255, 255); 
  if (delayStartFC && (frameCount - delayStartFC) > 30) { 
    for (var i = 0; i < tiles.length; i++) { 
      var tile = tiles[i]; 
      if (!tile.isMatch) { 
        tile.isFaceUp = false; 
      } 
    } 
    flippedTiles = []; 
    delayStartFC = null; 
    noLoop(); 
  } 
  for (var i = 0; i < tiles.length; i++) { 
    tiles[i].draw(); 
  } 
  if (numMatches === tiles.length/2) { 
    fill(0, 0, 0); 
    textSize(20); 
    text("You found them all in " + numTries + " tries!", 20, 375); 
  } 
}; 

noLoop(); 

mouseMoved = function() {
  // 重置所有hover状态
  for (var i = 0; i < tiles.length; i++) {
    tiles[i].isHovered = false;
  }
  // 标记鼠标下的瓦片为hover状态
  for (var i = 0; i < tiles.length; i++) {
    var tile = tiles[i];
    if (tile.isUnderMouse(mouseX, mouseY) && !tile.isFaceUp && !tile.isMatch) {
      tile.isHovered = true;
    }
  }
  loop();
};

内容的提问来源于stack exchange,提问作者Graham Billington

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:45:51