悬停时更新 HTML5 canvas 矩形?

Update HTML5 canvas rectangle on hover?

我有一些代码可以在 canvas 上绘制一个矩形,但我希望当我将鼠标悬停在它上面时该矩形会改变颜色。

问题是在我画完矩形后,我不确定如何select再次调整它。

我想做的事情:

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();

$('c.[rectangle]').hover(function(this){
    this.fillStyle = 'red';
    this.fill();
});

    var c=document.getElementById("myCanvas");
    var ctx=c.getContext("2d");
    ctx.rect(20,20,150,100);
    ctx.stroke();
    
    $(c).hover(function(e){
        ctx.fillStyle = 'red';
        ctx.fill();
    });
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="myCanvas"/>

考虑以下代码:

var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();

c.addEventListener("mouseover", doMouseOver, false);//added event to canvas

function doMouseOver(e){
    ctx.fillStyle = 'red';
    ctx.fill();
}

DEMO

您可能需要使用 JavaScript 跟踪 canvas 上的鼠标,然后查看它何时位于您的矩形上方并更改颜色。请参阅下面来自我的 blog post

的代码
<!DOCTYPE html>
<html>
<body>

<canvas id="myCanvas" width="700" height="500" style="border:1px solid #c3c3c3;">
Your browser does not support the HTML5 canvas tag.
</canvas>

<script>
var myRect={x:150, y:75, w:50, h:50, color:"red"};
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = myRect.color;
ctx.fillRect(myRect.x, myRect.y, myRect.w, myRect.h);

c.addEventListener("mousemove", function(e){
if ((e.clientX>=myRect.x)&(e.clientX<=myRect.x+myRect.w)&(e.clientY>=myRect.y)&(e.clientY<=myRect.y+myRect.h)){
myRect.color = "green";}
else{
myRect.color = "red";}
updateCanvas();
}, false);


function updateCanvas(){
ctx.fillStyle = myRect.color;
ctx.fillRect(myRect.x, myRect.y, myRect.w, myRect.h);
}
</script>

</body>
</html>

您不能使用 canvas 开箱即用。 Canvas只是位图,悬停逻辑需要手动实现

方法如下:

  • 将所有你想要的矩形存储为简单对象
  • 鼠标在 canvas 元素上的每一次移动:
    • 获取鼠标位置
    • 遍历对象列表
    • 使用 isPointInPath() 检测 "hover"
    • 重新绘制两个状态

例子

var canvas = document.querySelector("canvas"),
    ctx = canvas.getContext("2d"),
    rects = [
        {x: 10, y: 10, w: 200, h: 50},
        {x: 50, y: 70, w: 150, h: 30}    // etc.
    ], i = 0, r;

// render initial rects.
while(r = rects[i++]) ctx.rect(r.x, r.y, r.w, r.h);
ctx.fillStyle = "blue"; ctx.fill();

canvas.onmousemove = function(e) {

  // important: correct mouse position:
  var rect = this.getBoundingClientRect(),
      x = e.clientX - rect.left,
      y = e.clientY - rect.top,
      i = 0, r;
  
  ctx.clearRect(0, 0, canvas.width, canvas.height); // for demo
   
  while(r = rects[i++]) {
    // add a single rect to path:
    ctx.beginPath();
    ctx.rect(r.x, r.y, r.w, r.h);    
    
    // check if we hover it, fill red, if not fill it blue
    ctx.fillStyle = ctx.isPointInPath(x, y) ? "red" : "blue";
    ctx.fill();
  }

};
<canvas/>

这是基于@K3N 答案的稳定代码。他的代码的基本问题是,当一个框在另一个框上方时,两个框可能会同时悬停鼠标。我的回答完美解决了添加 'DESC' 到 'ASC' 循环。

var canvas = document.getElementById("canvas"),
    ctx = canvas.getContext("2d");

var map = [
    {x: 20, y: 20, w: 60, h: 60},
    {x: 30, y: 50, w: 76, h: 60}
];

var hover = false, id;
var _i, _b;
function renderMap() {
    for(_i = 0; _b = map[_i]; _i ++) {
        ctx.fillStyle = (hover && id === _i) ? "red" : "blue";
        ctx.fillRect(_b.x, _b.y, _b.w, _b.h);
    }
}
// Render everything
renderMap();
canvas.onmousemove = function(e) {
    // Get the current mouse position
    var r = canvas.getBoundingClientRect(),
        x = e.clientX - r.left, y = e.clientY - r.top;
    hover = false;

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for(var i = map.length - 1, b; b = map[i]; i--) {
        if(x >= b.x && x <= b.x + b.w &&
           y >= b.y && y <= b.y + b.h) {
            // The mouse honestly hits the rect
            hover = true;
            id = i;
            break;
        }
    }
    // Draw the rectangles by Z (ASC)
    renderMap();
}
<canvas id="canvas"></canvas>

你可以使用 canvas.addEventListener

var canvas = document.getElementById('canvas0');
canvas.addEventListener('mouseover', function() { /*your code*/ }, false);

它在 google chrome

上有效

我相信这是一个更深入的答案,对您来说会更好,特别是如果您对使用 canvas 元素的游戏设计感兴趣的话。

这对您更有效的主要原因是它更侧重于 OOP(面向对象编程)方法。这允许在以后通过某些事件或情况来定义、跟踪和更改对象。它还允许轻松扩展您的代码,在我看来只是更具可读性和组织性。

基本上你在这里看到的是两个形状的碰撞。光标和它悬停的单个点/对象。对于基本的正方形、矩形或圆形,这还算不错。但是,如果您要比较两个更独特的形状,则需要阅读更多有关 Separating Axis Theorem (SAT) 和其他碰撞技术的信息。届时优化和性能将成为一个问题,但目前我认为这是最佳方法。

const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
const cx = width / 2;
const cy = height / 2;
const twoPie = Math.PI * 2;
const points = []; // This will be the array we store our hover points in later

class Point {
  constructor(x, y, r) {
    this.x = x;
    this.y = y;
    this.r = r || 0;
  }
}

class HoverPoint extends Point {
  constructor(x, y, r, color, hoverColor) {
    super(x, y, r);
    this.color = color;
    this.hoverColor = hoverColor;
    this.hovered = false;
    this.path = new Path2D();
  }

  draw() {
    this.hovered ? ctx.fillStyle = this.hoverColor : ctx.fillStyle = this.color;
    this.path.arc(this.x, this.y, this.r, 0, twoPie);
    ctx.fill(this.path);
  }
}

class Cursor extends Point {
  constructor(x, y, r) {
    super(x, y, r);
  }

  collisionCheck(points) {
  // This is the method that will be called during the animate function that 
  // will check the cursors position against each of our objects in the points array.
    document.body.style.cursor = "default";
    points.forEach(point => {
      point.hovered = false;
      if (ctx.isPointInPath(point.path, this.x, this.y)) {
        document.body.style.cursor = "pointer";
        point.hovered = true;
      }
    });
  }
}

function createPoints() {
  // Create your points and add them to the points array.
  points.push(new HoverPoint(cx, cy, 100, 'red', 'coral'));
  points.push(new HoverPoint(cx + 250, cy - 100, 50, 'teal', 'skyBlue'));
  // ....
}

function update() {
  ctx.clearRect(0, 0, width, height);
  points.forEach(point => point.draw());
}

function animate(e) {
  const cursor = new Cursor(e.offsetX, e.offsetY);
  update();
  cursor.collisionCheck(points);
}

createPoints();
update();
canvas.onmousemove = animate;

我还想建议一件事。我还没有对此进行测试,但我怀疑使用一些简单的三角函数来检测我们的圆形物体是否发生碰撞会比 ctx.IsPointInPath() 方法更好。

但是,如果您使用的是更复杂的路径和形状,那么 ctx.IsPointInPath() 方法很可能是可行的方法。如果不是我之前提到的其他更广泛的碰撞检测形式。

最终的变化看起来像这样...

class Cursor extends Point {
  constructor(x, y, r) {
    super(x, y, r);
  }

  collisionCheck(points) {
    document.body.style.cursor = "default";
    points.forEach(point => {
      let dx = point.x - this.x;
      let dy = point.y - this.y;
      let distance = Math.hypot(dx, dy);
      let dr = point.r + this.r;

      point.hovered = false;
      // If the distance between the two objects is less then their combined radius
      // then they must be touching.
      if (distance < dr) {
        document.body.style.cursor = "pointer";
        point.hovered = true;
      }
    });
  }
}

这里是一个 link,其中包含与 collision detection

相关的其他 link 个示例

我希望你能看到在游戏和其他任何东西中修改和使用这样的东西是多么容易。希望这有帮助。

下面的代码在悬停时向 canvas 圆圈添加阴影。

<html>

<body>
  <canvas id="myCanvas" width="1000" height="500" style="border:1px solid #d3d3d3;">
    Your browser does not support the HTML5 canvas tag.</canvas>
</body>

<script>
  var canvas = document.getElementById("myCanvas"),
    ctx = canvas.getContext("2d"),
    circle = [{
        x: 60,
        y: 50,
        r: 40,
      },
      {
        x: 100,
        y: 150,
        r: 50,
      } // etc.
    ];

  // render initial rects.
  for (var i = 0; i < circle.length; i++) {

    ctx.beginPath();
    ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
    ctx.fillStyle = "blue";
    ctx.fill();
  }

  canvas.onmousemove = function(e) {
    var x = e.pageX,
      y = e.pageY,
      i = 0,
      r;

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    for (let i = 0; i < circle.length; i++) {
      if ((x > circle[i].x - circle[i].r) && (y > circle[i].y - circle[i].r) && (x < circle[i].x + circle[i].r) && (y < circle[i].y + circle[i].r)) {


        ctx.beginPath();
        ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
        ctx.fillStyle = "blue";
        ctx.fill();
        ctx.shadowBlur = 10;
        ctx.lineWidth = 3;
        ctx.strokeStyle = 'rgb(255,255,255)';
        ctx.shadowColor = 'grey';
        ctx.stroke();
        ctx.shadowColor = 'white';
        ctx.shadowBlur = 0;

      } else {

        ctx.beginPath();
        ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
        ctx.fillStyle = "blue";
        ctx.fill();
        ctx.shadowColor = 'white';
        ctx.shadowBlur = 0;
      }

    }

  };
</script>

</html>

我知道这已经过时了,但我很惊讶没有人提到 JCanvas。它增加了动画 canvas 事件的简单性。更多文档在这里 https://projects.calebevans.me/jcanvas/docs/mouseEvents/

<html lang="en">
<head>
<!-- css and other -->
</head>
<body onload="draw();">
<canvas id = "canvas" width="500" height="500" style= border:1px solid #000000;"> </canvas>
<script>
function draw() {
    $('canvas').drawRect({
        layer: true,
        fillStyle:'#333',
        x:100, y: 200,
        width: 600,
        height: 400,
        mouseover: function(layer) {
                $(this).animateLayer(layer, {
                    fillStyle: 'green'
                 }, 1000, 'swing');
                }
    }); 
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js"  crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jcanvas/21.0.1/jcanvas.js" crossorigin="anonymous"></script>
</body>
</html>