1

Below is a script which defines two functions that draw 4 rectangular buttons and 1 circular button respectively. I am trying to implement specific Hover and Click functionality into the buttons (as described in the script alerts) but I am at a bit of a loss as to how to do this. I tried calling the makeInteractiveButton() functions on each click but this caused a lot of odd overlap and lag. I want the script to do the following:

If the circular button is hovered, I would like it's fillColour to change and if it is clicked I would like it to change again to the colours described in the code (#FFC77E for hover, #FFDDB0 for clicked). This should only happen for the duration of the hover or click.

HTML:

<html lang="en">
<body>
    <canvas id="game" width = "750" height = "500"></canvas>
    <script type='text/javascript' src='stack.js'></script>
</body>
</html>

JavaScript:

var c=document.getElementById('game'),
    canvasX=c.offsetLeft,
    canvasY=c.offsetTop,
    ctx=c.getContext('2d')
    elements = [];

c.style.background = 'grey';

function makeInteractiveButton(x, strokeColor, fillColor) {
    ctx.strokeStyle=strokeColor;
    ctx.fillStyle=fillColor;
    ctx.beginPath();
    ctx.lineWidth=6;
    ctx.arc(x, 475, 20, 0, 2*Math.PI);
    ctx.closePath();
    ctx.stroke();
    ctx.fill();
    elements.push({
        arcX: x,
        arcY: 475,
        arcRadius: 20
    });
}

b1 = makeInteractiveButton(235, '#FFFCF8', '#FFB85D');

c.addEventListener('mousemove', function(event) {
    x=event.pageX-canvasX; // cursor location
    y=event.pageY-canvasY;

    elements.forEach(function(element) {
        if (x > element.left && x < element.left + element.width && 
            y > element.top && y < element.top + element.height) { // if cursor in rect
          alert('Rectangle should undergo 5 degree rotation and 105% scale');
        }
        else if (Math.pow(x-element.arcX, 2) + Math.pow(y-element.arcY, 2) < 
                 Math.pow(element.arcRadius, 2)) { // if cursor in circle
            alert('Set b1 fillColour to #FFC77E.');
        }
    });
}, false);

c.addEventListener('click', function(event) {
    x=event.pageX-canvasX; // cursor location
    y=event.pageY-canvasY;

    elements.forEach(function(element) {
        if (x > element.left && x < element.left + element.width && 
            y > element.top && y < element.top + element.height) { // if rect clicked
            alert('Move all cards to centre simultaneously.');
        }
        else if (Math.pow(x-element.arcX, 2) + Math.pow(y-element.arcY, 2) < 
                 Math.pow(element.arcRadius, 2)) { // if circle clicked
            alert('Set b1 fillColour to #FFDDB0.');
        }
    });
}, false);
3
  • This post sounds more like a programming assignment than a question. Can you explain what behavior this code is doing? How much of it is working so far? What problem are you having right now? Commented Apr 27, 2017 at 16:35
  • If you making a game (which I'm guessing by the id), you can take a look at pixi.js. They have a button example with pngs and canvas here: pixijs.github.io/examples/#/demos/interactivity.js Commented Apr 27, 2017 at 16:38
  • I've made this a single question too make it sound less like a programming assignment.. I am making a game but I was hoping to do everything without external libraries. Commented May 2, 2017 at 14:28

1 Answer 1

1

example canvas hitting test

One way is keep all element data and write a hitTest(x,y) function but when you have a lot of complex shapes its better to use a secondary canvas to render element with their ID instead of their color in it and the color of x,y in second canvas is ID of hitted element, I should mention that the second canvas is'nt visible and its just a gelper for get the hitted element.

Github Sample:

https://siamandmaroufi.github.io/CanvasElement/

Simple implementation of hitTest for Rectangles :

    var Rectangle = function(id,x,y,width,height,color){
      this.id = id;
      this.x=x;
      this.y=y;
      this.width = width;
      this.height = height;
      this.color = color  || '#7cf';
      this.selected = false;
    }

    Rectangle.prototype.draw = function(ctx){
     ctx.fillStyle = this.color;
     ctx.fillRect(this.x,this.y,this.width,this.height);
     if(this.selected){
       ctx.strokeStyle='red';
       ctx.setLineDash([5,5]);
       ctx.lineWidth = 5;
       ctx.strokeRect(this.x,this.y,this.width,this.height);
     }
    }

    Rectangle.prototype.hitTest=function(x,y){
     return (x >= this.x) && (x <= (this.width+this.x)) &&
            (y >= this.y) && (y <= (this.height+this.y));
    }

    var Paint  = function(el) {
     this.element = el;
     this.shapes = [];
    }

    Paint.prototype.addShape = function(shape){
     this.shapes.push(shape);
    }

    Paint.prototype.render = function(){
     //clear the canvas
     this.element.width = this.element.width;
     var ctx = this.element.getContext('2d');
     for(var i=0;i<this.shapes.length;i++){
      this.shapes[i].draw(ctx);
     }
    }

    Paint.prototype.setSelected = function(shape){
      for(var i=0;i<this.shapes.length;i++){
        this.shapes[i].selected = this.shapes[i]==shape;
      }
      this.render();
    }

    Paint.prototype.select = function(x,y){
     for(var i=this.shapes.length-1;i>=0;i--){
      if(this.shapes[i].hitTest(x,y)){
       return this.shapes[i];
      }
     }
     return null;
    }

    var el = document.getElementById('panel');
    var paint = new Paint(el);
    var rectA = new Rectangle('A',10,10,150,90,'yellow');
    var rectB = new Rectangle('B',150,90,140,100,'green');
    var rectC = new Rectangle('C',70,85,200,70,'rgba(0,0,0,.5)');

    paint.addShape(rectA);
    paint.addShape(rectB);
    paint.addShape(rectC);

    paint.render();


    function panel_mouseUp(evt){
     var p = document.getElementById('panel'); 
     var x = evt.x - p.offsetLeft;
     var y = evt.y - p.offsetTop;
     var shape = paint.select(x,y);
     if(shape){ 
      alert(shape.id);
     }
     //console.log('selected shape :',shape);
    }

    function panel_mouseMove(evt){
      var p = document.getElementById('panel'); 
      var x = evt.x - p.offsetLeft;
      var y = evt.y - p.offsetTop;
      var shape = paint.select(x,y);

      paint.setSelected(shape);
    }


    el.addEventListener('mouseup',panel_mouseUp);
    el.addEventListener('mousemove',panel_mouseMove);
    body {background:#e6e6e6;}
    #panel {
     border:solid thin #ccc;
     background:#fff;
     margin:0 auto;
     display:block;
    }
    <canvas id="panel" width="400px" height="200px"  >
    </canvas>   

just click or move over the shapes

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks. Could you elaborate on this wrt the code in the question? Is the purpose of the hitTest function to say 'whenever any event happens, redraw the canvas'?
This works for the recognising a click but I am struggling with changing the colour on hovering. See here.
Just add a selected property to Rectangle class and use it inside of draw method as I did, I do some changes in the code , Run code snippet again
Thanks! If you can change your code so that the hover border disappears when you stop hovering over the object I'll accept this answer.
I just remove a condition and it works fine, there is also an advanced example of it at github link which I wrote.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.