【问题标题】:Centering on a canvas object within an HTML5 canvas以 HTML5 画布中的画布对象为中心
【发布时间】:2017-07-13 03:54:43
【问题描述】:

我有一个 Html5 画布,我正在画正方形。 画布本身大致是窗口的大小。 当我检测到对正方形的点击时,我想平移画布,使正方形大致位于窗口的中心。欢迎任何见解、提示或直截了当的回复。

这是我目前尝试过的:

如果一个正方形位于点 (1000, 1000),我会简单地平移画布 (-1000, -1000)。我知道我需要添加一个偏移量,以便它在窗口中居中。但是,画布总是在可见窗口之外结束(在某处的左上角太远)。

更复杂的场景:

最终,我希望能够以已转换(旋转和倾斜)的画布上的单击对象为中心。我想要一个看起来效果很好的等距效果。 我想知道这种转换是否会影响居中逻辑/数学?

【问题讨论】:

  • 您真的要翻译画布,还是只想移动画布上的内容?

标签: html html5-canvas


【解决方案1】:

从屏幕转换到世界并返回

在使用非标准轴(或投影)(例如等轴测图)时,最好使用变换矩阵。它将使用相同的简单功能涵盖所有可能的 2D 投影。

iso 世界的坐标称为世界坐标。您所有的对象都存储为世界坐标。当您渲染它们时,您使用转换矩阵将这些坐标投影到屏幕坐标。

矩阵,不是电影。

矩阵表示世界屏幕坐标中的方向和大小 x 和 y 轴以及世界原点 (0,0) 的屏幕位置

对于iso就是

  • x 轴跨 1 向下 0.5
  • y 轴跨 -1 向下 0.5
  • z 轴向上 1(-1 as up 是 down 的倒数)但是这个例子没有使用 z

所以矩阵作为数组

const isoMat = [1,0.5,-1,0.5,0,0];  // ISO (pixel art) dimorphic projection

前两个是 x 轴,后两个是 y 轴,最后两个值是原点的屏幕坐标。

使用矩阵变换点

您将矩阵应用于一个点,这会将点从一个坐标系转换到另一个坐标系。您也可以通过逆变换转换回来。

屏幕世界

您需要将世界坐标转换为屏幕坐标。

function worldToScreen(pos,retPos){
    retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2] + isoMat[4];
    retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3] + isoMat[5];  
}

在演示中,我忽略了原点,因为我始终将原点设置在画布的中心。因此从该函数中删除原点

function worldToScreen(pos,retPos){
    retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2];
    retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3];  
}

屏幕到世界。

您还需要将屏幕坐标转换为世界坐标。为此,您需要使用逆变换。这有点像乘法的倒数a * 2 = bb / 2 = a 的倒数

有一个标准的计算逆矩阵的方法如下

const invMatrix = [];  // inverse matrix
// I call the next line cross, most call it the determinant which I 
// think is stupid as it is effectively a cross product and is used
// like you would use a cross product. Anyways I digress
const cross = isoMat[0] * isoMat[3] - isoMat[1] * isoMat[2];
invMatrix[0] =  isoMat[3] / cross;
invMatrix[1] = -isoMat[1] / cross;
invMatrix[2] = -isoMat[2] / cross;
invMatrix[3] =  isoMat[0] / cross;

然后我们有一个函数将屏幕 x,y 转换为世界位置

function screenToWorld(pos,retPos){
    const x = pos.x - isoMat[4];
    const y = pos.y - isoMat[5];
    retPos.x = x * invMatrix[0] + y * invMatrix[2];
    retPos.y = x * invMatrix[1] + y * invMatrix[3];  
}

因此您将鼠标坐标作为屏幕像素,使用上述函数转换为世界坐标。然后您可以使用世界坐标找到您正在寻找的对象。

要将世界对象移动到屏幕中心,请将其坐标转换为屏幕坐标,添加屏幕上的位置(画布中心)并将变换矩阵原点设置为该位置。

演示

该演示在世界坐标中创建了一组框。它通过ctx.setTransform( 将2D 上下文变换设置为isoMat(等距投影)

每一帧我都会将鼠标屏幕坐标转换为世界坐标,然后用它来检查鼠标在哪个框上。

如果鼠标按钮按下,我会将该框从世界坐标转换为屏幕并添加屏幕中心。为了平滑步骤,新的屏幕中心被追逐(平滑)..

嗯,你应该能够在代码中解决它,任何问题都可以在 cmets 中询问。

const ctx = canvas.getContext("2d");
const moveSpeed = 0.4;
const boxMin = 20;
const boxMax = 50;
const boxCount = 100;
const boxArea = 2000;
// some canvas vals
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;  // center 
var ch = h / 2;
var globalTime;




const U = undefined;
// Helper function 
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); };
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
const setOf = (count, cb) => {var a = [],i = 0; while (i < count) { a.push(cb(i ++)) } return a };
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
// mouse function and object
const mouse  = {x : 0, y : 0, button : false, world : {x : 0, y : 0}}
function mouseEvents(e){
	mouse.x = e.pageX;
	mouse.y = e.pageY;
	mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));


// boxes in world coordinates.
const boxes = [];
function draw(){
   if(this.dead){
      ctx.fillStyle = "rgba(0,0,0,0.5)"; 
      ctx.fillRect(this.x,this.y,this.w,this.h);
   }
   ctx.strokeStyle = this.col;
   ctx.globalAlpha = 1;
   ctx.strokeRect(this.x,this.y,this.w,this.h);

   // the rest is just overkill
   if(this.col === "red"){
      this.mr = 10;
   }else{
      this.mr = 1;
   }
   this.mc += (this.mr-this.m) * 0.45;
   this.mc *= 0.05;
   this.m += this.mc;
   for(var i = 0; i < this.m; i ++){
      const m = this.m * (i + 1);
      ctx.globalAlpha = 1-(m / 100);
      ctx.strokeRect(this.x-m,this.y-m,this.w,this.h);
   }
}


// make random boxes.
function createBoxes(){
  boxes.length = 0;
  boxes.push(...setOf(boxCount,()=>{
      return {
        x : randI(cw- boxArea/ 2, cw + boxArea/2), 
        y : randI(ch- boxArea/ 2, ch + boxArea/2), 
        w : randI(boxMin,boxMax), 
        h : randI(boxMin,boxMax),
        m : 5,
        mc : 0,
        mr : 5,
        col : "black",
        dead : false,
        draw : draw,
        isOver : isOver,
     }
   }));
}	
// use mouse world coordinates to find box under mouse
function isOver(x,y){
   return x > this.x && x < this.x + this.w && y > this.y && y < this.y + this.h;
}
var overBox;
function findBox(x,y){
   if(overBox){
       overBox.col = "black";
   }
   overBox = undefined;
   eachOf(boxes,box=>{
     if(box.isOver(x,y)){
        overBox = box;
        box.col = "red";
        return true;
     }
   })
}
function drawBoxes(){
   boxes.forEach(box=>box.draw());
}
// next 3 values control the movement of the origin
// rather than move instantly the currentPos chases the new pos.        
const currentPos = {x :0, y : 0};
const newPos = {x :0, y : 0};
const chasePos = {x :0, y : 0};
// this function does the chasing
function updatePos(){
  chasePos.x += (newPos.x - currentPos.x) * moveSpeed;
  chasePos.y += (newPos.y - currentPos.y) * moveSpeed;
  chasePos.x *= moveSpeed;
  chasePos.y *= moveSpeed;
  currentPos.x += chasePos.x;
  currentPos.y += chasePos.y;
}

// ISO matrix and inverse matrix plus 2world and 2 screen
const isoMat = [1,0.5,-1,0.5,0,0];
const invMatrix = [];
const cross = isoMat[0] * isoMat[3] - isoMat[1] * isoMat[2];
invMatrix[0] =  isoMat[3] / cross;
invMatrix[1] = -isoMat[1] / cross;
invMatrix[2] = -isoMat[2] / cross;
invMatrix[3] =  isoMat[0] / cross;

function screenToWorld(pos,retPos){
    const x = pos.x - isoMat[4];
    const y = pos.y - isoMat[5];
    retPos.x = x * invMatrix[0] + y * invMatrix[2];
    retPos.y = x * invMatrix[1] + y * invMatrix[3];  
}
function worldToScreen(pos,retPos){
    retPos.x = pos.x * isoMat[0] + pos.y * isoMat[2];// + isoMat[4];
    retPos.y = pos.x * isoMat[1] + pos.y * isoMat[3];// + isoMat[5];  
}

// main update function
function update(timer){
    // standard frame setup
    globalTime = timer;
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    if(w !== innerWidth || h !== innerHeight){
      cw = (w = canvas.width = innerWidth) / 2;
      ch = (h = canvas.height = innerHeight) / 2;
      createBoxes();
    }else{
      ctx.clearRect(0,0,w,h);
    }
    ctx.fillStyle = "black";
    ctx.font = "28px arial";
    ctx.textAlign = "center";
    ctx.fillText("Click on a box to center it.",cw,28);
    
    // update position      
    updatePos();
    isoMat[4] = currentPos.x;
    isoMat[5] = currentPos.y;
    // set the screen transform to the iso matrix
    // all drawing can now be done in world coordinates.
    ctx.setTransform(isoMat[0], isoMat[1], isoMat[2], isoMat[3], isoMat[4], isoMat[5]);
    // convert the mouse to world coordinates
    screenToWorld(mouse,mouse.world);
    // find box under mouse
	findBox(mouse.world.x, mouse.world.y);
    // if mouse down and over a box
    if(mouse.button && overBox){
       mouse.button = false;
       overBox.dead = true;  // make it gray
       // get the screen coordinates of the box
       worldToScreen({
            x:-(overBox.x + overBox.w/2),
            y:-(overBox.y + overBox.h/2),
          },newPos
       );
       // move it to the screen center
       newPos.x += cw;
       newPos.y += ch;

    }
    // forget what the following function does, think it does something like draw boxes, but I am guessing.. :P
	drawBoxes();
    requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas { position : absolute; top : 0px; left : 0px; }
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【讨论】:

    猜你喜欢
    • 2014-03-25
    • 2011-12-05
    • 1970-01-01
    • 2011-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-04
    • 2012-01-25
    相关资源
    最近更新 更多