【问题标题】:Update HTML5 canvas rectangle on hover?悬停时更新 HTML5 画布矩形?
【发布时间】:2015-05-31 18:10:36
【问题描述】:

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

问题是在我绘制了矩形之后,我不确定如何再次选择它来进行调整。

我想做什么:

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();
});

【问题讨论】:

  • 画布只是像素,其中没有可供您选择的对象。您既不能设置事件侦听器,也不能更改先前绘制的形状的属性。除非您想编写大量低级代码,否则请使用 SVG(或用于矩形的纯 HTML)或库。
  • 如果你想让你绘制的东西有事件监听器等,请改用 SVG。
  • 您可能会说我是 HTML 画布的新手,但据我所知,HTML5 游戏通常是使用它们构建的,那么如果没有事件侦听器,这将如何工作?

标签: javascript html canvas


【解决方案1】:

    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"/>

【讨论】:

  • @Manwal,你添加了 jQuery 参考吗?
  • @Manwal,请检查所提供的 sn-p
  • 什么?离开时没有模糊处理程序去除红色。
【解决方案2】:

考虑以下代码:

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

【讨论】:

  • 但我不想填满整个画布,我想填满上面的不同矩形
【解决方案3】:

您可能必须使用 JavaScript 在画布上跟踪鼠标,然后查看鼠标何时位于矩形上方并更改颜色。请参阅以下来自我的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>

【讨论】:

  • 我的回答和你的差不多:jsfiddle.net/e3tqwL0x/1。你可能会解释一下你的解决方案是如何工作的;-)
  • 简而言之,当鼠标在画布上移动时,事件监听器会被激活,然后检查鼠标的位置(e.clientX 和 e.clientY)以查看它是否在矩形的边界,如果是,则更改 myRect 的颜色,updateCanvas 函数然后用新颜色(来自 myRect)重绘矩形。
【解决方案4】:

您无法使用画布进行开箱即用的操作。 Canvas 只是一个位图,所以必须手动实现悬停逻辑。

方法如下:

  • 将所需的所有矩形存储为简单对象
  • 对于画布元素上的每次鼠标移动:
    • 获取鼠标位置
    • 遍历对象列表
    • 使用 isPointInPath() 检测“悬停”
    • 重绘两种状态

示例

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();
  }

};
&lt;canvas/&gt;

【讨论】:

  • 如果一个矩形在另一个矩形之上,它就不会起作用。检查我的答案以获得想法,以便您更新它。
  • 不仅如此。由于矩形索引,您必须执行“DESC”到“ASC”循环。在这个“DESC”循环中,您构建了一个矩形的渲染队列,或者只是检查鼠标是否击中一个并中断,而在正常循环中,您只需渲染它们。
  • 我明白了,但最好不要在代码中保留基本错误。
  • @Matheus 没有错误,只是不支持 z-order
  • 但是,是的,它丢失了。看到自己两个矩形同时悬停。我在我的回答中做了 sn-p 来展示一个解决方案的例子。
【解决方案5】:

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

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();
}
&lt;canvas id="canvas"&gt;&lt;/canvas&gt;

【讨论】:

    【解决方案6】:

    你可以使用 canvas.addEventListener

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

    它可以在谷歌浏览器上运行

    【讨论】:

      【解决方案7】:

      我相信这是一个稍微深入一点的答案,对你来说会更好,尤其是如果你对使用画布元素的游戏设计感兴趣的话。

      这对您更有效的主要原因是因为它更侧重于 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;
            }
          });
        }
      }
      

      这是一个包含示例的链接以及与collision detection相关的其他链接

      我希望你能看到这样的东西可以很容易地在游戏和其他任何东西中修改和使用。希望这会有所帮助。

      【讨论】:

        【解决方案8】:

        以下代码在悬停画布圆时为其添加阴影。

        <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>

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多