Como detectar quando o mouse está fora de um determinado círculo?

8

Quando um mouse está pairando uma imagem. Ele é detectado por esta instrução if:

if ((distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius)

Também quero detectar quando um mouse está fora de uma imagem. Após essa instrução if anterior, não posso usar mais o motivo:

Quando gero várias imagens na tela e quando o mouse passa o mouse sobre 1 imagem. Ele passa o mouse sobre a imagem e o código a detecta, mas também não passa sobre todas as outras imagens. Essa é a razão que é exibida 4 vezes "fora do círculo" e 1 vez "dentro do círculo"

Como visto no log:

Saída do Console.log:

Mouse inside circle 
Mouse outside circle 4 
Mouse inside circle 
Mouse outside circle 4 

Estou procurando uma maneira de detectar quando o mouse está deixando um círculo.

Você pode encontrar o código com o qual estou trabalhando abaixo:

PS: é importante detectar em que círculo (índice) o mouse está e sai. Quero criar uma enorme quantidade de fotos, mas no código abaixo usei 5 para fins de demonstração.

var mouse = {
    x: innerWidth / 2,
    y: innerHeight / 2
};

// Mouse Event Listeners
addEventListener('mousemove', event => {
    mouse.x = event.clientX;
    mouse.y = event.clientY;
});

//Calculate distance between 2 objects
function distance(x1, y1, x2, y2) {
    let xDistance = x2 - x1;
    let yDistance = y2 - y1;
    return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
}


// Sqaure to circle
function makeCircleImage(radius, src, callback) {
    var canvas = document.createElement('canvas');
    canvas.width = canvas.height = radius * 2;
    var ctx = canvas.getContext("2d");
    var img = new Image();
    img.src = src;
    img.onload = function() {
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        // we use compositing, offers better antialiasing than clip()
        ctx.globalCompositeOperation = 'destination-in';
        ctx.arc(radius, radius, radius, 0, Math.PI*2);
        ctx.fill();
        callback(canvas);
    };
}


function Circle( x, y, radius, index ) {
    //Give var for circle
    this.x = x;
    this.y = y;
    this.dx = 1;
    this.dy = 1;
    this.radius = radius;
    this.index = index;
}
// use prototyping if you wish to make it a class
Circle.prototype = {
//Draw circle on canvas
    draw: function () {
        var
            x = (this.x - this.radius),
            y = (this.y - this.radius);
        // draw is a single call
        c.drawImage( this.image, x, y );
    },

    //Updates position of images
    update: function () {
        var
            max_right = canvas.width + this.radius,
            max_left = this.radius * -1;
        this.x += this.dx;
        if( this.x > max_right ) {
            this.x += max_right - this.x;
            this.dx *= -1;
        }
        if( this.x < max_left ) {
            this.x += max_left - this.x;
            this.dx *= -1;
        }


        if ((distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius) {
            // Mouse inside circle
            console.log("Mouse inside circle")

        } else{
            //The mouse is in one circle
            //And out of 4 other circles
            console.log("Mouse outside circle")
        }
    },
    init: function(callback) {
        var url = "https://t4.ftcdn.net/jpg/02/26/96/25/240_F_226962583_DzHr45pyYPdmwnjDoqz6IG7Js9AT05J4.jpg";
        makeCircleImage( this.radius, url, function(img) {
            this.image = img;
            callback();
        }.bind(this));
    }
};

//Animate canvas
function animate() {
    c.clearRect(0, 0, window.innerWidth, window.innerHeight);
    circles.forEach(function( circle ) {
        circle.update();
    });
    circles.forEach(function( circle ) {
        circle.draw();
    });
    requestAnimationFrame(animate);
}

//Init canvas
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

//init circle objects
var circles = [
    new Circle(10, 100, 50,0),
    new Circle(10, 200, 30,1),
    new Circle(10, 300, 50,2),
    new Circle(10, 400, 50,3),
    new Circle(10, 500, 50,4)
];
var ready = 0;

circles.forEach(function(circle) {
    circle.init(oncircledone);
});

function oncircledone() {
    if(++ready === circles.length) {
        animate()
    }
}
<canvas></canvas>

AttackTheWar
fonte

Respostas:

3

basta adicionar outra propriedade para circular

  function Circle(x, y, radius, index) {
        //Give var for circle
        this.x = x;
        this.y = y;
        this.dx = 1;
        this.dy = 1;
        this.radius = radius;
        this.index = index;
        this.mouseInside = false
    }

e então a lógica de atualização muda para este

 if ((distance(this.x, this.y, mouse.x, mouse.y)) < circles[this.index].radius) {
            if (!this.mouseInside) {
                this.mouseInside = true
                console.log(`mouse enter circele at ${this.index}`)
            }
        }
        else if (this.mouseInside) {
            this.mouseInside = false
            console.log(`mouse leave circele at ${this.index}`)
        }

verifique se os círculos se sobrepõem e você pode decidir se deseja atualizar

  var overlapsCircles = circles.filter(circle => {
    var diffrentId = circle.index != this.index
    var overlapping =
      distance(this.x, this.y, circle.x, circle.y) < this.radius
    return diffrentId && overlapping
  })

  if (overlapsCircles.length > 0) {
    var overlapCircle = overlapsCircles.map(circle => circle.index)
    console.log('overlap circle with index ' + overlapCircle)
  }

 var mouse = {
        x: innerWidth / 2,
        y: innerHeight / 2
    };

    // Mouse Event Listeners
    addEventListener('mousemove', event => {
        mouse.x = event.clientX;
        mouse.y = event.clientY;
    });

    //Calculate distance between 2 objects
    function distance(x1, y1, x2, y2) {
        let xDistance = x2 - x1;
        let yDistance = y2 - y1;
        return Math.sqrt(Math.pow(xDistance, 2) + Math.pow(yDistance, 2));
    }


    // Sqaure to circle
    function makeCircleImage(radius, src, callback) {
        var canvas = document.createElement('canvas');
        canvas.width = canvas.height = radius * 2;
        var ctx = canvas.getContext("2d");
        var img = new Image();
        img.src = src;
        img.onload = function () {
            ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
            // we use compositing, offers better antialiasing than clip()
            ctx.globalCompositeOperation = 'destination-in';
            ctx.arc(radius, radius, radius, 0, Math.PI * 2);
            ctx.fill();
            callback(canvas);
        };
    }


    function Circle(x, y, radius, index) {
        //Give var for circle
        this.x = x;
        this.y = y;
        this.dx = 1;
        this.dy = 1;
        this.radius = radius;
        this.index = index;
        this.mouseInside = false
    }
    // use prototyping if you wish to make it a class
    Circle.prototype = {
        //Draw circle on canvas
        draw: function () {
            var
                x = (this.x - this.radius),
                y = (this.y - this.radius);
            // draw is a single call
            c.drawImage(this.image, x, y);
        },

        //Updates position of images
        update: function () {
            var
                max_right = canvas.width + this.radius,
                max_left = this.radius * -1;
            this.x += this.dx;
            if (this.x > max_right) {
                this.x += max_right - this.x;
                this.dx *= -1;
            }
            if (this.x < max_left) {
                this.x += max_left - this.x;
                this.dx *= -1;
            }


            if ((distance(this.x, this.y, mouse.x, mouse.y)) < circles[this.index].radius) {
                if (!this.mouseInside) {
                    this.mouseInside = true
                    console.log(`mouse enter circele at ${this.index}`)
                }
            }
            else if (this.mouseInside) {
                this.mouseInside = false
                console.log(`mouse leave circele at ${this.index}`)
            }
        },
        init: function (callback) {
            var url = "https://t4.ftcdn.net/jpg/02/26/96/25/240_F_226962583_DzHr45pyYPdmwnjDoqz6IG7Js9AT05J4.jpg";
            makeCircleImage(this.radius, url, function (img) {
                this.image = img;
                callback();
            }.bind(this));
        }
    };

    //Animate canvas
    function animate() {
        c.clearRect(0, 0, window.innerWidth, window.innerHeight);
        circles.forEach(function (circle) {
            circle.update();
        });
        circles.forEach(function (circle) {
            circle.draw();
        });
        requestAnimationFrame(animate);
    }

    //Init canvas
    var canvas = document.querySelector('canvas');
    var c = canvas.getContext('2d');
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    //init circle objects
    var circles = [
        new Circle(10, 100, 50, 0),
        new Circle(10, 200, 30, 1),
        new Circle(10, 300, 50, 2),
        new Circle(10, 400, 50, 3),
        new Circle(10, 500, 50, 4)
    ];
    var ready = 0;

    circles.forEach(function (circle) {
        circle.init(oncircledone);
    });

    function oncircledone() {
        if (++ready === circles.length) {
            animate()
        }
    }
    <canvas id="ctx"></canvas>

Naor Tedgi
fonte
Oi, obrigado pela resposta. Ao passar o mouse sobre vários círculos em um ponto (ao mesmo tempo). A propriedade mouseinside de vários círculos é definida como "true". Como posso priorizar (por índice) apenas a propriedade "mouseinside" de 1 círculo para ser definida como "true".
AttackTheWar
Como exatamente você passa o mouse mais de um círculo ao mesmo tempo?
Naor Tedgi 28/02
@NaorTedgi O OP provavelmente quis dizer quando há dois círculos sobrepostos.
Richard
Quando dois círculos se sobrepõem @NaorTedgi
AttackTheWar
i adicionar um trecho sobre como reconhecer se círculos se sobrepõem t, então você pode decidir se você deseja atualizar o círculo especial
Naor Tedgi
1

Ambiguidades

Não está claro o que você precisa em relação a círculos e algum ponto (neste ponto de resposta é um substituto para o mouse e requer apenas que ele tenha as propriedades xe yseja válido).

A falta de informações na sua pergunta diz respeito aos fatos

  • que muitos círculos podem estar sob o ponto ao mesmo tempo.

  • e que mais de um círculo pode passar de baixo para fora ou de fora para baixo no ponto por quadro.

  • o teor da pergunta sugere que você está atrás de apenas um círculo que entra em conflito com as duas preocupações acima.

Premissas

Assumirei que a interação com os círculos é mais do que uma simples sub-evento como a interação. Que eles podem incluir comportamentos relacionados à animação que são acionados pelo estado relacionado ao ponto.

Suponho que a ordem visual dos círculos determinará como você seleciona os círculos de interesse.

Todos os círculos por quadro que atendem às condições necessárias e podem ser acessados ​​rapidamente.

Esse desempenho é importante, pois você deseja ter muitos círculos que interagem com um ponto.

Que existe apenas um ponto (mouse, toque, outra fonte) por quadro que interage com os círculos

Não há requisito para interação com círculo

Solução

O exemplo abaixo cobre as suposições acima e resolve quaisquer ambiguidades na pergunta. Ele foi projetado para ser eficiente e flexível.

Os círculos são armazenados em uma matriz que teve suas propriedades estendidas chamadas circles

Conjuntos de renderização e estado

A função circles.updateDraw(point)atualiza e desenha todos os círculos. O argumento pointé um ponto no qual comparar o círculo. O padrão é o mouse.

Todos os círculos são desenhados com um contorno. Os círculos abaixo do ponto (por exemplo, mouse) são preenchidos em verde; os círculos apenas movidos para abaixo do ponto (por exemplo, onMouseOver) são preenchidos em amarelo; os círculos que acabam de sair de baixo são preenchidos com vermelho.

Existem três matrizes como propriedades de círculos que contêm círculos como definem ...

  • circles.under Todos os círculos abaixo do ponto
  • circles.outFromUnder Todos os círculos apenas abaixo do ponto
  • circles.newUnder Todos os círculos novos abaixo do ponto

Essa matriz é preenchida pela função circles.updateDraw(point)

Consultar todos os círculos apontam estado

Os círculos também têm três funções que se referem às matrizes acima como seto conjunto padrão circles.under.

As funções são ..

  • circles.firstInSet(set)Retorna o primeiro círculo (a parte inferior visual mais) em setouundefined
  • circles.lastInSet(set)Retorna o último círculo (a parte superior visual) em setouundefined
  • circles.closestInSet(set)Retorna o círculo mais próximo ao ponto em setouundefined

Por exemplo, para obter o círculo visual mais alto logo abaixo do mouse que você chamaria circles.lastInSet(circles.newUnder)ou para obter o círculo mais próximo do mouse de todos os círculos sob o mouse que você chamaria circles.closestInSet(circles.newUnder)(ou como o padrão é definir a underchamada circles.closestInSet())

Circunde estados adicionais

Cada círculo tem algumas propriedades adicionais.

  • Circle.distSqr é o quadrado da distância do ponto
  • Circle.rSqr é o quadrado do raio calculado quando construído.
  • Circle.underCount Este valor pode ser usado para aplicar animações ao círculo com base em seu estado relativo ao ponto.
    • Se positivo é o número de quadros mais 1, o círculo está abaixo do ponto.
    • Se esse valor for 1, o círculo será movido de não abaixo para abaixo.
    • Se esse valor for 0, ele acabou de sair do ponto.
    • Se negativo, esse valor é o número de quadros que o círculo não está abaixo do ponto

Executando demonstração

Use o mouse para mover sobre círculos. O círculo mais próximo e abaixo do mouse é preenchido com branco com alfa = 0,5

addEventListener('mousemove', event => {
    mouse.x = event.clientX;
    mouse.y = event.clientY;
});

Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
const CIRCLE_RADIUS = 50;
const UNDER_STYLE = "#0A0";
const NEW_UNDER_STYLE = "#FF0";
const OUT_STYLE = "#F00";
const CIRCLE_STYLE = "#000";
const CIRCLE_LINE_WIDTH = 1.5;
const CIRCLE_COUNT = 100;
const CIRCLE_CLOSEST = "#FFF";
const ctx = canvas.getContext('2d');
const mouse = {x: 0, y: 0};

requestAnimationFrame(() => {
    sizeCanvas();
    var i = CIRCLE_COUNT;
    while (i--) { 
        const r = Math.rand(CIRCLE_RADIUS / 3, CIRCLE_RADIUS);
        
        circles.push(new Circle(
            Math.rand(r, canvas.width - r),
            Math.rand(r, canvas.height - r),
            Math.rand(-1, 1),
            Math.rand(-1, 1),
            r
        ));
    }
    
    animate()
});


function animate() {
    sizeCanvas();
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    circles.updateDraw();
    const c = circles.closestInSet(circles.under);
    if(c) {
        ctx.globalAlpha = 0.5;
        ctx.beginPath();
        ctx.fillStyle = CIRCLE_CLOSEST;
        c.draw();
        ctx.fill();
        ctx.globalAlpha = 1;
    }
    requestAnimationFrame(animate);
}    

function sizeCanvas() {
    if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
        canvas.width = innerWidth;
        canvas.height = innerHeight;
    }
}
function Circle( x, y, dx = 0, dy = 0, radius = CIRCLE_RADIUS) {
    this.x = x + radius;
    this.y = y + radius;
    this.dx = dx;
    this.dy = dy;
    this.radius = radius;
    this.rSqr = radius * radius; // radius squared
    this.underCount = 0; // counts frames under point
}
Circle.prototype = {
    draw() { 
      ctx.moveTo(this.x + this.radius, this.y);
      ctx.arc(this.x, this.y, this.radius, 0, Math.TAU);
    },
    update() {
        this.x += this.dx;
        this.y += this.dy;
        if (this.x >= canvas.width - this.radius) {
            this.x += (canvas.width - this.radius) - this.x;
            this.dx = -Math.abs(this.dx);
        } else if (this.x < this.radius) {
            this.x += this.radius - this.x;
            this.dx = Math.abs(this.dx);
        }
        if (this.y >= canvas.height - this.radius) {
            this.y += (canvas.height - this.radius) - this.y;
            this.dy = -Math.abs(this.dx);
        } else if (this.y < this.radius) {
            this.y += this.radius - this.y;
            this.dy = Math.abs(this.dy);
        }
    },
    isUnder(point = mouse) {
        this.distSqr = (this.x - point.x) ** 2 + (this.y - point.y) ** 2;  // distance squared
        return this.distSqr < this.rSqr;
    }

};
const circles = Object.assign([], {
    under:  [],
    outFromUnder:  [],
    newUnder: [],
    firstInSet(set = this.under) { return set[0] },
    lastInSet(set = this.under) { return set[set.length - 1] },
    closestInSet(set = this.under) {
        var minDist = Infinity, closest;
        if (set.length <= 1) { return set[0] }
        for (const circle of set) {
            if (circle.distSqr < minDist) {
                minDist = (closest = circle).distSqr;
            }
        }
        return closest;
    },
    updateDraw(point) {
        this.under.length = this.newUnder.length = this.outFromUnder.length = 0;
        ctx.strokeStyle = CIRCLE_STYLE;
        ctx.lineWidth = CIRCLE_LINE_WIDTH;
        ctx.beginPath();
        for(const circle of this) {
            circle.update();
            if (circle.isUnder(point)) {
                if (circle.underCount <= 0) {
                    circle.underCount = 1;
                    this.newUnder.push(circle);
                } else { circle.underCount ++ }
                this.under.push(circle);
            } else if (circle.underCount > 0) {
                circle.underCount = 0;
                this.outFromUnder.push(circle);
            } else {
                circle.underCount --;
            }

            
            circle.draw();
        }
        ctx.stroke();
        ctx.globalAlpha = 0.75;
        ctx.beginPath();
        ctx.fillStyle = UNDER_STYLE;
        for (const circle of this.under) {
            if (circle.underCount > 1) { circle.draw() }
        }
        ctx.fill();

        ctx.beginPath();
        ctx.fillStyle = OUT_STYLE;
        for (const circle of this.outFromUnder) { circle.draw() }
        ctx.fill();

        ctx.beginPath();
        ctx.fillStyle = NEW_UNDER_STYLE;
        for (const circle of this.newUnder) { circle.draw() }
        ctx.fill();
        ctx.globalAlpha = 1;
    }
});
#canvas {
    position: absolute;
    top: 0px;
    left: 0px;
    background: #6AF;
}
<canvas id="canvas"></canvas>

Blindman67
fonte
0

Bem, o mouse está se movendo e você pode simplesmente criar um conjunto que conterá objetos de círculo que armazenarão o (s) círculo (s) em que você está:

let circleOfTrust = new Set(); 
//At the initialization you need to add any circles your point is currently in

e depois no loop:

circles.forEach(function( circle ) {
    circleOfTrust[circle.update(circleOfTrust.has(circle)) ? "add" : "delete"](circle);
});
if (circleOfTrust.size() === 0) {
    //point is outside the circles
} else {
    //point is inside the circles in the set
}

e o update:

update: function (isInside) {
    var
        max_right = canvas.width + this.radius,
        max_left = this.radius * -1;
    this.x += this.dx;
    if( this.x > max_right ) {
        this.x += max_right - this.x;
        this.dx *= -1;
    }
    if( this.x < max_left ) {
        this.x += max_left - this.x;
        this.dx *= -1;
    }

    return distance(circles[this.index].x, circles[this.index].y, mouse.x, mouse.y)) < circles[this.index].radius;

},
Lajos Arpad
fonte
0

Eu proporia o seguinte:

  1. Mantenha uma pilha de figuras com a ordem de como elas foram criadas (ou qualquer outra ordem significativa). Isso é necessário para detectar movimentos sobre figuras sobrepostas.

  2. Implemente uma função / método que itera a pilha e determina se o cursor está dentro de qualquer uma das figuras.

  3. Lembre-se do último estado, na transição de estado dentro -> ouside desencadeia um evento.

    function FiguresCollection(canvas, callback)
    {
       var buffer = [];
       var lastHitFigure = null;
    
    
       var addFigure = function(figure)
       {
           buffer.push(figure);
       }
    
       var onMouseMove = function(e)
       {
           var currentHit = null;
           // iterating from the other end, recently added figures are overlapping previous ones
           for (var i= buffer.length-1;i>=0;i--)
           {
             if (distance(e.offsetX, e.offsetY, buffer[i].x, buffer[i].y) <= buffer[i].radius) {
             // the cursor is inside Figure i
             // if it come from another figure
             if (lastHitFigure !== i)
             {
                console.log("The cursor had left figure ", lastHitFigure, " and entered ",i);
                callback(buffer[i]);
             }
             lastHitFigure = i;
             currentHit = i;
             break; // we do not care about figures potentially underneath 
            }
    
         }
    
    
         if (lastHitFigure !== null && currentHit == null)
         {
             console.log("the cursor had left Figure", lastHitFigure, " and is not over any other ");
             lastHitFigure = null;
             callback(buffer[lastHitFigure]);
         }
      } 
    }
    
    canvas.addEventListener("mousemove", onMouseMove);
    this.addFigure = addFigure;
    }

Agora use-o:

var col = new FiguresCollection(canvas, c=> console.log("The cursor had left, ", c) );
for(let i in circles)
{
    c.addFigure(circles[i]);
}

// I hope I got the code right. I haven't tested it. Please point out any issues or errors.
Eriks Klotins
fonte