const moveStepVr = 10;  // Шаг для перемещения
const zoomStepVr = 0.01;  // Шаг для увеличения-уменьшения
const MIN_SCALEVr = 0.05; // Минимальный масштаб
const MAX_SCALEVr = 5;   // Максимальный масштаб
const DEAD_ZONE = 0.1; // Мертвая зона для стиков

let translateVrXLeft = 0;
let translateVrYLeft = 0;
let scaleVrLeft = 0.05; // Начальный масштаб для левой части

let translateVrXRight = 0;
let translateVrYRight = 0;
let scaleVrRight = 0.05; // Начальный масштаб для правой части

function getInitialTransformValues(board) {
    const transform = board.style.transform || '';
    const translateMatch = transform.match(/translate\((-?\d*\.?\d*)px,\s*(-?\d*\.?\d*)px\)/);
    const scaleMatch = transform.match(/scale\((-?\d*\.?\d*)\)/);

    const translateX = translateMatch ? parseFloat(translateMatch[1]) : 0;
    const translateY = translateMatch ? parseFloat(translateMatch[2]) : 0;
    const scale = scaleMatch ? parseFloat(scaleMatch[1]) : 0.05;

    return { translateX, translateY, scale };
}

window.addEventListener('gamepadconnected', function(e) {
    console.log('Gamepad connected at index %d: %s. %d buttons, %d axes.',
        e.gamepad.index, e.gamepad.id,
        e.gamepad.buttons.length, e.gamepad.axes.length);
    requestAnimationFrame(handleGamepadInput);
});

function handleGamepadInput() {
    const gamepads = navigator.getGamepads();
    if (!gamepads) return;

    const gamepad = gamepads[0];
    if (!gamepad) return;

    const boardLeft = document.getElementById('board-left');
    const boardRight = document.getElementById('board-right');

    if (boardLeft && boardRight) {
        if (translateVrXLeft === 0 && translateVrYLeft === 0 && scaleVrLeft === 0.05) {
            const initialLeft = getInitialTransformValues(boardLeft);
            translateVrXLeft = initialLeft.translateX;
            translateVrYLeft = initialLeft.translateY;
            scaleVrLeft = initialLeft.scale;
        }

        if (translateVrXRight === 0 && translateVrYRight === 0 && scaleVrRight === 0.05) {
            const initialRight = getInitialTransformValues(boardRight);
            translateVrXRight = initialRight.translateX;
            translateVrYRight = initialRight.translateY;
            scaleVrRight = initialRight.scale;
        }

        let scaleChanged = false;
        let previousScaleLeft = scaleVrLeft;
        let previousScaleRight = scaleVrRight;

        // Изменение масштаба
        if (gamepad.buttons[3].pressed) {
            scaleVrLeft += zoomStepVr;
            scaleVrRight += zoomStepVr;
            scaleChanged = true;
        }
        if (gamepad.buttons[5].pressed) {
            scaleVrLeft -= zoomStepVr;
            scaleVrRight -= zoomStepVr;
            scaleChanged = true;
        }

        // Ограничиваем масштаб между минимальным и максимальным значениями
        scaleVrLeft = Math.min(Math.max(MIN_SCALEVr, scaleVrLeft), MAX_SCALEVr);
        scaleVrRight = Math.min(Math.max(MIN_SCALEVr, scaleVrRight), MAX_SCALEVr);

        // Выполняем коррекцию только если масштаб изменился
        if (scaleChanged) {
            const rectLeft = boardLeft.getBoundingClientRect();
            const rectRight = boardRight.getBoundingClientRect();
            const midXLeft = rectLeft.left + rectLeft.width / 2;
            const midYLeft = rectLeft.top + rectLeft.height / 2;
            const midXRight = rectRight.left + rectRight.width / 2;
            const midYRight = rectRight.top + rectRight.height / 2;

            const offsetXLeft = (midXLeft - rectLeft.left) / previousScaleLeft;
            const offsetYLeft = (midYLeft - rectLeft.top) / previousScaleLeft;
            const offsetXRight = (midXRight - rectRight.left) / previousScaleRight;
            const offsetYRight = (midYRight - rectRight.top) / previousScaleRight;

            translateVrXLeft -= offsetXLeft * (scaleVrLeft - previousScaleLeft);
            translateVrYLeft -= offsetYLeft * (scaleVrLeft - previousScaleLeft);
            translateVrXRight -= offsetXRight * (scaleVrRight - previousScaleRight);
            translateVrYRight -= offsetYRight * (scaleVrRight - previousScaleRight);
        }

        // Логика перемещения
        const moveLeftX = Math.abs(gamepad.axes[0]) > DEAD_ZONE ? gamepad.axes[0] * moveStepVr : 0;
        const moveLeftY = Math.abs(gamepad.axes[1]) > DEAD_ZONE ? gamepad.axes[1] * moveStepVr : 0;
        const moveRightX = Math.abs(gamepad.axes[0]) > DEAD_ZONE ? gamepad.axes[0] * moveStepVr : 0;
        const moveRightY = Math.abs(gamepad.axes[1]) > DEAD_ZONE ? gamepad.axes[1] * moveStepVr : 0;

        translateVrXLeft -= moveLeftX;
        translateVrYLeft -= moveLeftY;
        translateVrXRight -= moveRightX;
        translateVrYRight -= moveRightY;

        updateVRTransform(boardLeft, translateVrXLeft, translateVrYLeft, scaleVrLeft);
        updateVRTransform(boardRight, translateVrXRight, translateVrYRight, scaleVrRight);
    }

    requestAnimationFrame(handleGamepadInput);
}

function updateVRTransform(board, translateX, translateY, scale) {
    board.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
    board.style.backgroundPosition = `${translateX}px ${translateY}px`;
}

У меня перемещение при зуме всегда стримиться к координатам 0 0. Такое чувство будто перед каждым увеличением не берется текущие координаты центров boardLeft и boardRight