Wie erkennt man, wenn sich die Maus außerhalb eines bestimmten Kreises befindet?

8

Wenn eine Maus über einem Bild schwebt. Es wird durch diese if-Anweisung erkannt:

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

Ich möchte auch erkennen, wann eine Maus es außerhalb eines Bildes. Nach dieser vorherigen if-Anweisung kann ich sie nicht mehr verwenden. Der Grund dafür ist:

Wenn ich mehrere Bilder auf dem Bildschirm generiere und wenn meine Maus über 1 Bild schwebt. Es schwebt über diesem Bild und der Code erkennt es, aber es schwebt auch nicht über allen anderen Bildern. Dies ist der Grund, warum 4 Mal "außerhalb des Kreises" und 1 Mal "innerhalb des Kreises" angezeigt wird.

Wie im Protokoll zu sehen:

Ausgabe von Console.log:

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

Ich suche nach einem Weg, um zu erkennen, wann die Maus einen Kreis verlässt.

Den Code, mit dem ich arbeite, finden Sie unten:

PS: Es ist wichtig, dass es erkennt, in welchem ​​(Index-) Kreis sich die Maus befindet und verlässt. Ich möchte eine große Anzahl von Bildern erstellen, aber im folgenden Code habe ich 5 für Demo-Zwecke verwendet.

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
quelle

Antworten:

3

Fügen Sie dem Kreis einfach eine weitere Eigenschaft hinzu

  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
    }

und dann ändert sich die Aktualisierungslogik dazu

 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}`)
        }

Überprüfen Sie, ob sich Kreise überlappen, und entscheiden Sie, ob Sie aktualisieren möchten

  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
quelle
Hallo danke für die Antwort. Wenn Sie mit dem Mauszeiger über mehrere Kreise an einer Stelle (gleichzeitig) fahren. Die mouseinside-Eigenschaft mehrerer Kreise wird auf "true" gesetzt. Wie kann ich (nach Index) nur die "mouseinside" -Eigenschaft eines Kreises priorisieren, um sie auf "true" zu setzen?
AttackTheWar
Wie genau schwebst du mehr als einen Kreis gleichzeitig?
Naor Tedgi
@NaorTedgi Das OP bedeutete wahrscheinlich, wenn sich zwei Kreise überlappen.
Richard
Wenn sich 2 Kreise überlappen @NaorTedgi
AttackTheWar
Ich füge einen Ausschnitt hinzu, um zu erkennen, ob sich Kreise überlappen. Dann können Sie entscheiden, ob Sie diesen bestimmten Kreis aktualisieren möchten
Naor Tedgi,
1

Mehrdeutigkeiten

Es ist nicht klar, was Sie in Bezug auf Kreise und einen Punkt benötigen (in diesem Antwortpunkt ist ein Ersatz für die Maus und erfordert nur, dass sie die Eigenschaften hat xund ygültig ist).

Der Mangel an Informationen in Ihrer Frage betrifft die Fakten

  • so viele Kreise können gleichzeitig unter dem Punkt sein.

  • und dass sich mehr als ein Kreis von unter nach außen oder von unter nach unter dem Punkt pro Bild bewegen kann.

  • Der Wortlaut der Frage deutet darauf hin, dass Sie sich nur nach einem Kreis befinden, der im Widerspruch zu den beiden oben genannten Bedenken steht.

Annahmen

Ich gehe davon aus, dass die Interaktion mit den Kreisen mehr als nur eine einfache Interaktion unter Ereignissen ist. Dass sie animationsbezogene Verhaltensweisen enthalten können, die durch den auf den Punkt bezogenen Status ausgelöst werden.

Ich gehe davon aus, dass die visuelle Reihenfolge der Kreise bestimmt, wie Sie interessierende Kreise auswählen.

Dass alle Kreise pro Frame die erforderlichen Bedingungen erfüllen und schnell zugänglich sind.

Diese Leistung ist wichtig, da Sie viele Kreise haben möchten, die mit einem Punkt interagieren.

Dass es nur einen Punkt (Maus, Berührung, andere Quelle) pro Frame gibt, der mit den Kreisen interagiert

Es ist keine Kreis-Kreis-Interaktion erforderlich

Lösung

Das folgende Beispiel deckt die obigen Annahmen ab und löst alle Unklarheiten in der Frage. Es ist so konzipiert, dass es effizient und flexibel ist.

Die Kreise werden in einem Array gespeichert, dessen Eigenschaften erweitert wurden circles

Rendering und Statussätze

Die Funktion circles.updateDraw(point)aktualisiert und zeichnet alle Kreise. Das Argument pointist ein Punkt, anhand dessen der Kreis überprüft werden kann. Der Standardwert ist dermouse .

Alle Kreise werden mit einem Umriss gezeichnet. Kreise unter dem Punkt (z. B. Maus) sind mit Grün gefüllt, Kreise, die gerade unter den Punkt verschoben wurden (z. B. onMouseOver), sind mit Gelb gefüllt, Kreise, die gerade von unten herausgezogen wurden, sind mit Rot gefüllt.

Es gibt 3 Arrays als Eigenschaften von Kreisen, die Kreise enthalten, wie definiert ...

  • circles.under Alle Kreise unter dem Punkt
  • circles.outFromUnder Alle Kreise kommen gerade unter dem Punkt hervor
  • circles.newUnder Alle Kreise neu unter dem Punkt

Diese Arrays werden von der Funktion gefüllt circles.updateDraw(point)

Fragen Sie den Kreisstatus aller Kreise ab

Kreise haben auch 3 Funktionen, setdie sich standardmäßig auf die obigen Arrays beziehen circles.under.

Die Funktionen sind ..

  • circles.firstInSet(set)Gibt den ersten Kreis (der visuell unterste Punkt) in setoder zurückundefined
  • circles.lastInSet(set)Gibt den letzten Kreis (das visuell oberste) in setoder zurückundefined
  • circles.closestInSet(set)Gibt den nächstgelegenen Kreis zum Punkt in setoder zurückundefined

Zum Beispiel, um den visuell obersten Kreis direkt unter der Maus circles.lastInSet(circles.newUnder)zu erhalten, die Sie aufrufen würden, oder um den Kreis, der der Maus am nächsten liegt, aus allen Kreisen unter der Maus zu erhalten, die Sie aufrufen würden circles.closestInSet(circles.newUnder)(oder standardmäßig den underAnruf festzulegen circles.closestInSet()).

Kreise zusätzliche Zustände ein

Jeder Kreis hat einige zusätzliche Eigenschaften.

  • Circle.distSqr ist das Quadrat der Entfernung vom Punkt
  • Circle.rSqr ist das Quadrat des Radius, der bei der Konstruktion berechnet wird.
  • Circle.underCount Dieser Wert kann verwendet werden, um Animationen auf den Kreis basierend auf seinem relativen Status zum Punkt anzuwenden.
    • Wenn positiv die Anzahl der Frames plus 1 ist, befindet sich der Kreis unter dem Punkt.
    • Wenn dieser Wert 1 ist, wird der Kreis nur von nicht unter nach unter verschoben.
    • Wenn dieser Wert 0 ist, ist er gerade unter dem Punkt hervorgegangen.
    • Wenn negativ, ist dieser Wert die Anzahl der Frames, unter denen sich der Kreis nicht unter dem Punkt befindet

Demo ausführen

Bewegen Sie sich mit der Maus über Kreise. Der Kreis, der am nächsten und unter der Maus liegt, ist mit Weiß mit Alpha = 0,5 gefüllt

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

Nun, die Maus bewegt sich und Sie können einfach ein Set erstellen, das Kreisobjekte enthält, in denen die Kreise gespeichert sind, in denen Sie sich befinden:

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

und dann an der Schleife:

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
}

und die 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
quelle
0

Ich würde folgendes vorschlagen:

  1. Bewahren Sie einen Stapel von Figuren in der Reihenfolge auf, in der sie erstellt wurden (oder in einer anderen sinnvollen Reihenfolge). Dies ist erforderlich, um Bewegungen über überlappenden Figuren zu erkennen.

  2. Implementieren Sie eine Funktion / Methode, die den Stapel iteriert und feststellt, ob sich der Cursor in einer der Figuren befindet.

  3. Denken Sie daran, dass der letzte Zustand beim Zustandsübergang innerhalb von>> ouside ein Ereignis auslöst.

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

Verwenden Sie es jetzt:

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
quelle