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

基于JS Canvas的客舱座位选择器:点击交互与存储问题及优化咨询

客车/飞机座位选择器优化方案

核心问题拆解

当前通过Canvas实现了动态行列的座位生成,但存在两个核心卡点:

  • 无法处理座位的点击选择与状态存储
  • 后续需要实现婴儿座标记及基于距离的噪音等级显示

优化实现方案

1. 用数据模型管理座位状态

Canvas本身没有DOM元素,必须通过数据对象记录每个座位的位置、编号、选中状态、是否为婴儿座等信息,彻底将数据与绘制逻辑分离。

首先定义并生成座位数据结构:

// 初始化座位数据数组
let seats = [];
const seatSize = window.innerWidth / 10;
const seatGap = 1.25;
const seatRows = 14;
const seatColumns = 4;
const aisleOffset = seatSize; // 过道宽度

// 批量生成座位数据
let seatNumber = 1;
for (let row = 0; row < seatRows; row++) {
  for (let col = 0; col < seatColumns; col++) {
    // 计算座位Canvas坐标(区分过道左右)
    let x = col < seatColumns / 2 
      ? col * seatSize + 10 
      : col * seatSize + aisleOffset + 10;
    let y = (row * seatSize * seatGap) + (seatSize * 2) + 10;

    seats.push({
      id: seatNumber,
      row: row + 1,
      col: col + 1,
      x, y,
      width: seatSize,
      height: seatSize,
      isSelected: false,
      isBabySeat: false,
      noiseLevel: 0 // 后续用于存储噪音等级
    });
    seatNumber++;
  }
}

2. 实现点击检测与状态更新

给Canvas绑定点击事件,通过坐标匹配找到目标座位,更新状态后触发重绘:

const canvas = document.getElementById("seats");
canvas.addEventListener('click', (e) => {
  const rect = canvas.getBoundingClientRect();
  // 转换为Canvas内部相对坐标
  const clickX = e.clientX - rect.left;
  const clickY = e.clientY - rect.top;

  // 查找被点击的座位
  const clickedSeat = seats.find(seat => {
    return clickX >= seat.x && clickX <= seat.x + seat.width &&
           clickY >= seat.y && clickY <= seat.y + seat.height;
  });

  if (clickedSeat) {
    // 切换选中状态
    clickedSeat.isSelected = !clickedSeat.isSelected;
    // 重绘所有座位
    renderSeats();
  }
});

3. 封装渲染函数实现数据同步

把所有绘制逻辑封装成renderSeats函数,每次状态变化时调用,确保视图与数据完全同步:

function renderSeats() {
  const ctx = canvas.getContext('2d');
  // 清空画布
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // 计算客车尺寸并设置Canvas大小
  const busLength = (seatRows * seatSize * seatGap) + (seatSize * 2) - seatSize * (seatGap - 1);
  const busWidth = (seatColumns * seatSize) + aisleOffset;
  canvas.width = busWidth + 20;
  canvas.height = busLength + 20;

  // 绘制客车边框与Front标识
  ctx.font = 0.15 * busWidth + "px Arial";
  ctx.fillStyle = "grey";
  ctx.textAlign = "center";
  ctx.fillText("Front", busWidth / 2 + 5, seatSize * 1.5);

  ctx.strokeStyle = "gray";
  ctx.lineWidth = "2";
  ctx.beginPath();
  ctx.roundRect(10, 10, busWidth, busLength, [5]);
  ctx.stroke();

  // 逐个绘制座位
  seats.forEach(seat => {
    // 设置座位样式:选中状态用蓝色填充,婴儿座用黄色填充
    ctx.strokeStyle = "gray";
    ctx.lineWidth = "2";
    if (seat.isSelected) {
      ctx.fillStyle = "#4285F4";
      ctx.fillRect(seat.x, seat.y, seat.width, seat.height);
    }
    if (seat.isBabySeat) {
      ctx.fillStyle = "#FBBC05";
      ctx.fillRect(seat.x, seat.y, seat.width, seat.height);
    }
    ctx.beginPath();
    ctx.roundRect(seat.x, seat.y, seat.width, seat.height, [5]);
    ctx.stroke();

    // 绘制座位号(选中/婴儿座用白色文字)
    ctx.font = seatSize / 2 + "px Arial";
    ctx.fillStyle = seat.isSelected || seat.isBabySeat ? "white" : "grey";
    ctx.textAlign = "center";
    ctx.fillText(seat.id, seat.x + seat.width/2, seat.y + seat.height/1.6);
  });
}

// 初始化渲染
renderSeats();

4. 实现婴儿座噪音等级计算

根据选中的婴儿座,计算其他座位的曼哈顿距离并划分噪音等级(距离越近噪音越高):

// 标记婴儿座并计算所有座位噪音等级
function setBabySeat(seatId) {
  // 重置所有婴儿座标记
  seats.forEach(s => s.isBabySeat = false);
  const babySeat = seats.find(s => s.id === seatId);
  if (babySeat) {
    babySeat.isBabySeat = true;
    // 计算每个座位与婴儿座的曼哈顿距离
    seats.forEach(seat => {
      const distance = Math.abs(seat.row - babySeat.row) + Math.abs(seat.col - babySeat.col);
      // 划分噪音等级:0=低,1=中,2=高
      if (distance <= 1) seat.noiseLevel = 2;
      else if (distance <= 3) seat.noiseLevel = 1;
      else seat.noiseLevel = 0;
    });
    renderSeats();
  }
}

// 示例:标记ID为5的座位为婴儿座
// setBabySeat(5);

方案优势

  • 数据驱动视图:所有座位状态统一存储在数据模型中,避免Canvas绘制与状态脱节
  • 响应式适配:窗口大小变化时,重新计算seatSize后调用renderSeats()即可更新视图
  • 扩展性强:后续可轻松添加已售座位标记、价格显示、座位筛选等功能

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 07:43:22