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

使用AJAX时JavaScript为何生成位置错误的棋子图像?

问题分析与解决方案

首先,我帮你定位了问题的核心原因:全局状态残留、旧DOM元素的异步加载干扰,这两个因素共同导致了点击"New Board"后棋子位置错乱的问题。下面是具体的分析和修复方案:


问题根源

  1. 全局状态未重置:last_colour、colour_row、colour_column这三个全局变量在每次生成新棋盘时没有重置为初始值,状态会从上一次调用中继承,导致位置计算逻辑混乱。
  2. 旧图片元素残留:每次点击按钮都会创建新的<img>元素添加到隐藏容器中,旧的元素并未被删除。这些旧元素的load事件可能延迟触发,在新的画布上绘制旧的棋子,造成画面错乱。
  3. 异步加载的闭包隐患:虽然当前棋子数组是固定的红车在前、黑车在后,但全局变量的设计本身就容易引发状态污染,后续如果数组顺序变化会直接导致位置错误。

修复方案

方案1:快速修复(最小改动)

只需要在每次生成新棋盘时重置全局状态,并清理旧的图片元素:

function displayPieces(context) {
    // 1. 重置全局状态,确保每次新棋盘从初始值开始计算
    last_colour = '';
    colour_row = 0;
    colour_column = 0;

    // 2. 清空旧的图片元素,避免残留的load事件干扰新绘制
    const pieceImages = document.getElementById('pieceImages');
    while (pieceImages.firstChild) {
        pieceImages.removeChild(pieceImages.firstChild);
    }

    // 原有代码保持不变
    var red_count = document.getElementById('red-count');
    var black_count = document.getElementById('black-count');
    red_count.innerText = context.colour_length['red']
    black_count.innerText = context.colour_length['black']

    var max_colour_length = context.max_colour_length;
    var position_id = 'top-image'

    var ctx = prepareCanvas(position_id, max_colour_length)

    var piece_index = 0;
    context.pieces.forEach(piece_name => {
        createModelImage (position_id, piece_index)
        displayPieceImage (context, ctx, position_id, piece_name, piece_index)
        piece_index ++;
    } )
}

方案2:最优修复(消除全局变量)

将位置计算的状态封装为局部变量,彻底避免全局状态污染,同时优化图片加载逻辑:

// static/js/test.js
// 'Constants' to define image cropping
var RAW_WIDTH = 552
var RAW_HEIGHT = 640
var SCALE = 0.1
var PIECE_WIDTH = RAW_WIDTH * SCALE
var PIECE_HEIGHT = RAW_HEIGHT * SCALE
var CANVAS_HEIGHT = 3 * PIECE_HEIGHT;

// 预加载棋子图片,避免重复创建DOM元素
const pieceCache = {};

$(document).ready(function () {
    var new_board_link = document.getElementById('new-board');
    new_board_link.onclick = getNewBoard;
    getNewBoard();
});

function getNewBoard() {
    $.ajax( {
        type: "GET",
        url: 'new-board',
        cache: false,
        success: function (context) {
            // 缓存图片URL,后续直接使用
            Object.keys(context.piece_images).forEach(key => {
                if (!pieceCache[key]) {
                    const img = new Image();
                    img.src = context.piece_images[key];
                    pieceCache[key] = img;
                }
            });
            displayPieces(context);
        }
    } );
}

function displayPieces(context) {
    var red_count = document.getElementById('red-count');
    var black_count = document.getElementById('black-count');
    red_count.innerText = context.colour_length['red']
    black_count.innerText = context.colour_length['black']

    var max_colour_length = context.max_colour_length;
    var position_id = 'top-image'

    var ctx = prepareCanvas(position_id, max_colour_length)

    // 使用局部变量保存位置状态,替代全局变量
    let lastColour = '';
    let colourColumn = -1;

    context.pieces.forEach((pieceName, index) => {
        const { posX, posY } = calculatePiecePosition(pieceName, lastColour, colourColumn);
        // 更新局部状态
        lastColour = pieceName.substring(0, 1);
        colourColumn = pieceName.substring(0, 1) === lastColour ? colourColumn + 1 : 0;
        // 绘制棋子
        drawPiece(ctx, pieceName, posX, posY);
    })
}

function prepareCanvas(position_id, max_colour_length) {
    const canvas = document.getElementById(position_id);
    canvas.width = max_colour_length * PIECE_WIDTH;
    canvas.height = CANVAS_HEIGHT;
    const ctx = canvas.getContext('2d');
    // 清空画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    return ctx
}

// 纯函数:计算棋子位置,不依赖全局状态
function calculatePiecePosition(pieceName, currentLastColour, currentColumn) {
    const pieceColour = pieceName.substring(0, 1);
    let column = currentColumn;

    if (pieceColour !== currentLastColour) {
        column = 0;
    } else {
        column += 1;
    }

    // 黑车在顶行(y=0),红车在底行(y=PIECE_HEIGHT)
    const row = pieceColour === 'b' ? 0 : 1;
    return {
        posX: column * PIECE_WIDTH,
        posY: row * PIECE_HEIGHT
    };
}

function drawPiece(ctx, pieceName, posX, posY) {
    const img = pieceCache[pieceName];
    if (img.complete) {
        // 图片已加载完成,直接绘制
        ctx.drawImage(img, 0, 0, RAW_WIDTH, RAW_HEIGHT, posX, posY, PIECE_WIDTH, PIECE_HEIGHT);
    } else {
        // 图片未加载完成,等待加载后绘制
        img.addEventListener('load', () => {
            ctx.drawImage(img, 0, 0, RAW_WIDTH, RAW_HEIGHT, posX, posY, PIECE_WIDTH, PIECE_HEIGHT);
        }, { once: true });
    }
}

方案说明

  • 方案1:适合快速修复现有代码,改动最小,能解决当前问题。
  • 方案2:采用了更健壮的设计:
    • 消除了全局状态,使用局部变量管理位置计算逻辑,避免状态污染。
    • 预加载棋子图片,重复使用同一个<img>元素,避免创建大量DOM元素。
    • 使用once: true确保load事件只触发一次,避免重复绘制。
    • 位置计算函数改为纯函数,逻辑更清晰,易于维护和测试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 15:24:06