通过 CSS 变换进行画布缩放

问题描述 投票:0回答:1

在下面的代码中我遇到了缩放问题。当鼠标位于一个位置并且我缩放到该位置时,一切都很好。但是,当我将鼠标移动到另一个位置并在那里缩放时,画布就会移动到另一个位置。我认为问题在于缩放后,新的鼠标位置由于缩放而不正确。我怎样才能正确地重新计算它?

缩放系数必须始终为 0.05 -> 1.0、0.95、0.9 并返回 0.9、0.95、1.0

变焦范围从0.3到1.0

const container = document.getElementById('container');
const citycanvas = document.getElementById('citycanvas');
const citycontext = citycanvas.getContext('2d');

// Funktion zum Zeichnen von Bergen
function drawMountains(context) {
    const gradient = context.createLinearGradient(0, 0, 0, 400);
    gradient.addColorStop(0, "#000000");
    gradient.addColorStop(1, "#000000");
    context.fillStyle = gradient;

    context.beginPath();
    let startX = 200;
    let startY = 400;
    let width = 300;
    let height = 200;
    context.moveTo(startX, startY);
    context.lineTo(startX + width / 2, startY - height);
    context.lineTo(startX + width, startY);
    context.lineTo(startX, startY);

    let mountainData = [
        { startX: 500, startY: 400, width: 250, height: 150 },
        { startX: 800, startY: 400, width: 350, height: 250 },
        { startX: 1100, startY: 400, width: 200, height: 100 }
    ];

    mountainData.forEach(mountain => {
        context.moveTo(mountain.startX, mountain.startY);
        context.lineTo(mountain.startX + mountain.width / 2, mountain.startY - mountain.height);
        context.lineTo(mountain.startX + mountain.width, mountain.startY);
        context.lineTo(mountain.startX, mountain.startY);
    });

    context.closePath();
    context.fill();
}

drawMountains(citycontext);

let Zoomfaktor = 0.05;
const min_Faktor = 0.3;
const max_Faktor = 1.0;
let mouseX, mouseY;
let Zoomlevel = 1.0;
let woldX = 0;
let woldY = 0;
let windowcursorposX = 0;
let windowcursorposY = 0;
let mapleftposX = 0;
let mapleftposY = 0;
let mapcursorposX = 0;
let mapcursorposY = 0;



// Bestimmt die Cursorposition auf der Map
function cursorpos(e)
{
  woldX = windowcursorposX;
  woldY = windowcursorposY;

  console.log('woldX: ' + woldX);
//  console.log('woldY: ' + woldY);
  
  windowcursorposX = parseInt(e.clientX); 
  windowcursorposY = parseInt(e.clientY);

  console.log('windowcursorposX: ' + windowcursorposX);
//  console.log('windowcursorposY: ' + windowcursorposY);
  

  // Berechne die tatsächliche Position auf der Karte basierend auf dem aktuellen Zoomlevel
  mapcursorposX = windowcursorposX - mapleftposX;
  mapcursorposY = windowcursorposY - mapleftposY;

console.log('mapleftposX: ' + mapleftposX);
//console.log('mapleftposY: ' + mapleftposY);
  
console.log('mapcursorposX: ' + mapcursorposX);
//console.log('mapcursorposY: ' + mapcursorposY);
    
}

// Aktualisiere die Mausposition bei jeder Bewegung
citycanvas.addEventListener('mousemove', (e) => {
  cursorpos(e);
});

// Zoomfunktion nicht löschen und verändern
citycanvas.addEventListener('wheel', (e) => 
{
  e.preventDefault();

  cursorpos(e);

  // Richtung des Mausrads bestimmen
  const delta = e.deltaY > 0 ? -1 : 1; // Zoom-In (delta < 0) und Zoom-Out (delta > 0)
  console.log('delta: ' + delta);

  // Berechnung des neuen Zoomlevels
  let newZoomLevel = Zoomlevel - (delta * Zoomfaktor);

  // Begrenzung des Zoomlevels (optional)
  newZoomLevel = Math.max(min_Faktor, Math.min(max_Faktor, newZoomLevel));
  console.log('newZoomLevel: ' + newZoomLevel);

  // Nur weiterfahren, wenn das neue Zoomlevel sich ändert
  if (newZoomLevel !== Zoomlevel)
  {
        // Berechnung des neuen Transformationsursprungs
        const newPosX = mapcursorposX / Zoomlevel;
        const newPosY = mapcursorposY / Zoomlevel;
  
        console.log('mapcursorposX: ' + mapcursorposX);
        console.log('mapcursorposY: ' + mapcursorposY);
        console.log('Zoomlevel: ' + Zoomlevel);
  
        console.log('newPosX: ' + newPosX);
        console.log('newPosY: ' + newPosY);
  
        // Setze den neuen Zoomfaktor und den neuen Transformationsursprung
        citycanvas.style.transformOrigin = `${newPosX}px ${newPosY}px`;
        citycanvas.style.transform = `scale(${newZoomLevel})`;
  
        // Position der Maus relativ zum Canvas
        const rect = citycanvas.getBoundingClientRect();
        mapleftposX = rect.left;
        mapleftposY = rect.top;
  
        console.log('mapleftposX: ' + mapleftposX);
        console.log('mapleftposY: ' + mapleftposY);
  
        // Aktualisiere das Zoomlevel
        Zoomlevel = newZoomLevel;
  }
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas Zoom</title>
    <style>
    
    html, body
    {
            overflow: hidden;
    }
        #container {
            width: 100vw; /* Nimmt die gesamte Breite des Fensters ein */
            height: 100vh; /* Nimmt die gesamte Höhe des Fensters ein */
            overflow: hidden;
            position: relative;
            background-color: lightgrey; /* Hilfreich zum Debuggen */
        }
        #citycanvas {
            position: absolute;
            transform-origin: top left;
            background-color: green;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div id="container">
        <canvas id="citycanvas" width="12600" height="9600"></canvas>
    </div>
    <script src="zoom.js"></script>
</body>
</html>

javascript html css canvas transform
1个回答
0
投票

首先也是最重要的,我想说在每个 mousemove 事件上查询鼠标位置是多余的,因为一旦轮子事件被触发,它就已经被查询过 - 并且只是需要的。

所以摆脱:

citycanvas.addEventListener('mousemove', (e) => {
  cursorpos(e);
});

其次,当您实际缩放时,您始终使用鼠标在屏幕上的实际位置。因此,当然,一旦用户将其移动到其他位置,您也会移动缩放的中心点。

为了解决这个问题,我建议设置一个全局变量,如果缩放级别等于 1,它将跟踪鼠标位置,并使用该点进行缩放计算。

const container = document.getElementById('container');
const citycanvas = document.getElementById('citycanvas');
const citycontext = citycanvas.getContext('2d');

// Funktion zum Zeichnen von Bergen
function drawMountains(context) {
  const gradient = context.createLinearGradient(0, 0, 0, 400);
  gradient.addColorStop(0, "#000000");
  gradient.addColorStop(1, "#000000");
  context.fillStyle = gradient;

  context.beginPath();
  let startX = 200;
  let startY = 400;
  let width = 300;
  let height = 200;
  context.moveTo(startX, startY);
  context.lineTo(startX + width / 2, startY - height);
  context.lineTo(startX + width, startY);
  context.lineTo(startX, startY);

  let mountainData = [{
      startX: 500,
      startY: 400,
      width: 250,
      height: 150
    },
    {
      startX: 800,
      startY: 400,
      width: 350,
      height: 250
    },
    {
      startX: 1100,
      startY: 400,
      width: 200,
      height: 100
    }
  ];

  mountainData.forEach(mountain => {
    context.moveTo(mountain.startX, mountain.startY);
    context.lineTo(mountain.startX + mountain.width / 2, mountain.startY - mountain.height);
    context.lineTo(mountain.startX + mountain.width, mountain.startY);
    context.lineTo(mountain.startX, mountain.startY);
  });

  context.closePath();
  context.fill();
}

drawMountains(citycontext);

let Zoomfaktor = 0.05;
const min_Faktor = 0.3;
const max_Faktor = 1.0;
let mouseX, mouseY;
let Zoomlevel = 1.0;
let woldX = 0;
let woldY = 0;
let windowcursorposX = 0;
let windowcursorposY = 0;
let mapleftposX = 0;
let mapleftposY = 0;
let mapcursorposX = 0;
let mapcursorposY = 0;

// Bestimmt die Cursorposition auf der Map
function cursorpos(e) {
  windowcursorposX = woldX;
  windowcursorposY = woldX;

  console.log('windowcursorposX: ' + windowcursorposX);
  //  console.log('windowcursorposY: ' + windowcursorposY);


  // Berechne die tatsächliche Position auf der Karte basierend auf dem aktuellen Zoomlevel
  mapcursorposX = windowcursorposX - mapleftposX;
  mapcursorposY = windowcursorposY - mapleftposY;

  console.log('mapleftposX: ' + mapleftposX);
  //console.log('mapleftposY: ' + mapleftposY);

  console.log('mapcursorposX: ' + mapcursorposX);
  //console.log('mapcursorposY: ' + mapcursorposY);

}

// Zoomfunktion nicht löschen und verändern
citycanvas.addEventListener('wheel', (e) => {
  e.preventDefault();

  // Richtung des Mausrads bestimmen
  const delta = e.deltaY > 0 ? -1 : 1; // Zoom-In (delta < 0) und Zoom-Out (delta > 0)
  console.log('delta: ' + delta);
  if (Zoomlevel == 1) {
    woldX = parseInt(e.clientX);
    woldY = parseInt(e.clientY);
  }
  // Berechnung des neuen Zoomlevels
  let newZoomLevel = Zoomlevel - (delta * Zoomfaktor);

  // Begrenzung des Zoomlevels (optional)
  newZoomLevel = Math.max(min_Faktor, Math.min(max_Faktor, newZoomLevel));
  console.log('newZoomLevel: ' + newZoomLevel);

  cursorpos();
  // Nur weiterfahren, wenn das neue Zoomlevel sich ändert
  if (newZoomLevel !== Zoomlevel) {
    // Berechnung des neuen Transformationsursprungs
    const newPosX = mapcursorposX / Zoomlevel;
    const newPosY = mapcursorposY / Zoomlevel;

    console.log('mapcursorposX: ' + mapcursorposX);
    console.log('mapcursorposY: ' + mapcursorposY);
    console.log('Zoomlevel: ' + Zoomlevel);

    console.log('newPosX: ' + newPosX);
    console.log('newPosY: ' + newPosY);

    // Setze den neuen Zoomfaktor und den neuen Transformationsursprung
    citycanvas.style.transformOrigin = `${newPosX}px ${newPosY}px`;
    citycanvas.style.transform = `scale(${newZoomLevel})`;

    // Position der Maus relativ zum Canvas
    const rect = citycanvas.getBoundingClientRect();
    mapleftposX = rect.left;
    mapleftposY = rect.top;

    console.log('mapleftposX: ' + mapleftposX);
    console.log('mapleftposY: ' + mapleftposY);

    // Aktualisiere das Zoomlevel
    Zoomlevel = newZoomLevel;
  }
});
html,
body {
  overflow: hidden;
}

#container {
  width: 100vw;
  /* Nimmt die gesamte Breite des Fensters ein */
  height: 100vh;
  /* Nimmt die gesamte Höhe des Fensters ein */
  overflow: hidden;
  position: relative;
  background-color: lightgrey;
  /* Hilfreich zum Debuggen */
}

#citycanvas {
  position: absolute;
  transform-origin: top left;
  background-color: green;
  overflow: hidden;
}
<div id="container">
  <canvas id="citycanvas" width="12600" height="9600"></canvas>
</div>

© www.soinside.com 2019 - 2024. All rights reserved.