You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为JavaScript Canvas元素(如fillRect())添加CSS类及实现贪吃蛇游戏蛇身单元格圆角效果

解决方案:贪吃蛇圆角单元格与Canvas图形样式处理

我来帮你搞定这两个问题——给蛇身加上圆角效果,还有处理Canvas绘制图形的类样式需求:

一、给蛇的单元格添加圆角效果

Canvas自带的fillRect只能画直角矩形,要实现圆角,你可以用现代浏览器支持的roundRect方法,或者自己封装一个兼容旧浏览器的圆角矩形绘制函数。下面是具体修改方案:

方案1:使用原生roundRect(推荐)

替换你代码中蛇身绘制的部分,用roundRect替代原来错误的arc调用:

// 绘制蛇的圆角单元格
context.fillStyle = 'green';
snake.cells.forEach(function(cell, index) {
  // roundRect参数:x坐标、y坐标、宽度、高度、圆角半径
  context.roundRect(cell.x, cell.y, grid - 1, grid - 1, 4);
  context.fill();

  // 原有的苹果碰撞、身体碰撞逻辑保持不变...
});

方案2:兼容旧浏览器的自定义函数

如果需要支持老版本浏览器,自己写一个绘制圆角矩形的函数:

function drawRoundRect(ctx, x, y, width, height, radius) {
  ctx.beginPath();
  ctx.moveTo(x + radius, y);
  ctx.lineTo(x + width - radius, y);
  ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
  ctx.lineTo(x + width, y + height - radius);
  ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
  ctx.lineTo(x + radius, y + height);
  ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
  ctx.lineTo(x, y + radius);
  ctx.quadraticCurveTo(x, y, x + radius, y);
  ctx.closePath();
}

// 蛇身绘制时调用这个函数
snake.cells.forEach(function(cell, index) {
  context.fillStyle = 'green';
  drawRoundRect(context, cell.x, cell.y, grid - 1, grid - 1, 4);
  context.fill();

  // 原有逻辑...
});

这样每个蛇身单元格就会有圆润的边角了。

二、给Canvas绘制的图形添加"CSS类"的替代方案

Canvas是位图绘制技术,没办法直接给绘制出来的图形加CSS类——因为这些图形不是DOM元素,浏览器识别不了类名。不过我们可以用几种方式模拟类似CSS类的样式管理:

1. 用样式对象映射类名(最实用)

提前定义类似CSS类的样式对象,绘制时根据"类名"调用对应的样式:

// 定义模拟CSS类的样式映射
const canvasStyles = {
  'snake-body': { fillStyle: 'green', borderRadius: 4 },
  'snake-head': { fillStyle: 'darkgreen', borderRadius: 6 },
  'apple': { fillStyle: 'crimson', borderRadius: 8 }
};

// 绘制蛇时区分头部和身体样式
snake.cells.forEach(function(cell, index) {
  const style = index === 0 ? canvasStyles['snake-head'] : canvasStyles['snake-body'];
  context.fillStyle = style.fillStyle;
  drawRoundRect(context, cell.x, cell.y, grid - 1, grid - 1, style.borderRadius);
  context.fill();
});

// 绘制苹果时使用apple样式
context.fillStyle = canvasStyles['apple'].fillStyle;
drawRoundRect(context, apple.x, apple.y, grid - 1, grid - 1, canvasStyles['apple'].borderRadius);
context.fill();

2. 用CSS变量同步样式(和页面CSS联动)

如果想让Canvas样式和页面CSS保持一致,可以读取CSS变量的值来绘制:

/* 在页面CSS中定义变量 */
:root {
  --snake-color: #2ecc71;
  --snake-radius: 4px;
  --apple-color: #e74c3c;
}
// 读取CSS变量并应用到Canvas绘制
const rootStyles = getComputedStyle(document.documentElement);
const snakeColor = rootStyles.getPropertyValue('--snake-color').trim();
const snakeRadius = parseInt(rootStyles.getPropertyValue('--snake-radius').trim());

// 绘制时使用这些变量
context.fillStyle = snakeColor;
drawRoundRect(context, cell.x, cell.y, grid - 1, grid - 1, snakeRadius);
context.fill();

3. DOM元素叠加方案(适合简单场景)

如果游戏元素不多,可以用绝对定位的DOM元素放在Canvas上方,给这些DOM元素加CSS类。不过这种方法会影响性能,不适合贪吃蛇这种频繁更新的游戏:

<canvas id="game" style="position: relative;"></canvas>
// 每次循环更新蛇身DOM元素
function updateSnakeDOM() {
  // 先移除旧元素
  document.querySelectorAll('.snake-cell').forEach(el => el.remove());
  
  snake.cells.forEach(cell => {
    const cellEl = document.createElement('div');
    cellEl.className = 'snake-cell';
    cellEl.style.position = 'absolute';
    cellEl.style.left = `${cell.x}px`;
    cellEl.style.top = `${cell.y}px`;
    cellEl.style.width = `${grid - 1}px`;
    cellEl.style.height = `${grid - 1}px`;
    cellEl.style.borderRadius = '4px';
    cellEl.style.backgroundColor = 'green';
    document.getElementById('game').appendChild(cellEl);
  });
}

完整修改后的代码示例

这里是整合了圆角效果和样式映射的完整代码:

var canvas = document.getElementById('game');
var context = canvas.getContext('2d');
var grid = 16;
var count = 0;
var snake = {
 x: 160,
 y: 160,
 dx: grid,
 dy: 0,
 cells: [],
 maxCells: 4,
};
var apple = {
 x: 320,
 y: 320
};

// 模拟CSS类的样式映射
const canvasStyles = {
  'snake-body': { fillStyle: 'green', borderRadius: 4 },
  'snake-head': { fillStyle: 'darkgreen', borderRadius: 6 },
  'apple': { fillStyle: 'crimson', borderRadius: 8 }
};

// 兼容式绘制圆角矩形
function drawRoundRect(ctx, x, y, width, height, radius) {
  if (ctx.roundRect) {
    ctx.roundRect(x, y, width, height, radius);
  } else {
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.lineTo(x + width - radius, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
    ctx.lineTo(x + width, y + height - radius);
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
    ctx.lineTo(x + radius, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
    ctx.lineTo(x, y + radius);
    ctx.quadraticCurveTo(x, y, x + radius, y);
    ctx.closePath();
  }
}

function getRandomInt(min, max) {
 return Math.floor(Math.random() * (max - min)) + min;
}

function loop() {
 requestAnimationFrame(loop);

 if (++count < 4) {
 return;
 }
 count = 0;

 context.clearRect(0,0,canvas.width,canvas.height);

 snake.x += snake.dx;
 snake.y += snake.dy;

 if (snake.x < 0) {
 snake.x = canvas.width - grid;
 } else if (snake.x >= canvas.width) {
 snake.x = 0;
 }

 if (snake.y < 0) {
 snake.y = canvas.height - grid;
 } else if (snake.y >= canvas.height) {
 snake.y = 0;
 }

 snake.cells.unshift({x: snake.x, y: snake.y});

 if (snake.cells.length > snake.maxCells) {
 snake.cells.pop();
 }

 // 绘制苹果
 context.fillStyle = canvasStyles.apple.fillStyle;
 drawRoundRect(context, apple.x, apple.y, grid-1, grid-1, canvasStyles.apple.borderRadius);
 context.fill();

 // 绘制蛇
 snake.cells.forEach(function(cell, index) {
  const style = index === 0 ? canvasStyles['snake-head'] : canvasStyles['snake-body'];
  context.fillStyle = style.fillStyle;
  drawRoundRect(context, cell.x, cell.y, grid-1, grid-1, style.borderRadius);
  context.fill();

  if (cell.x === apple.x && cell.y === apple.y) {
 snake.maxCells++;
 apple.x = getRandomInt(0, 25) * grid;
 apple.y = getRandomInt(0, 25) * grid;
 }

 for (var i = index + 1; i < snake.cells.length; i++) {
 if (cell.x === snake.cells[i].x && cell.y === snake.cells[i].y) {
 snake.x = 160;
 snake.y = 160;
 snake.cells = [];
 snake.maxCells = 4;
 snake.dx = grid;
 snake.dy = 0;
 apple.x = getRandomInt(0, 25) * grid;
 apple.y = getRandomInt(0, 25) * grid;
 }
 }
 });
}

document.addEventListener('keydown', function(e) {
 if (e.which === 37 && snake.dx === 0) {
 snake.dx = -grid;
 snake.dy = 0;
 }
 else if (e.which === 38 && snake.dy === 0) {
 snake.dy = -grid;
 snake.dx = 0;
 }
 else if (e.which === 39 && snake.dx === 0) {
 snake.dx = grid;
 snake.dy = 0;
 }
 else if (e.which === 40 && snake.dy === 0) {
 snake.dy = grid;
 snake.dx = 0;
 }
});

requestAnimationFrame(loop);

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

火山引擎 最新活动