Làm thế nào để phát hiện khi chuột ở ngoài một vòng tròn nhất định?


8

Khi một con chuột đang lơ lửng một hình ảnh. Nó được phát hiện bởi điều này nếu câu lệnh:

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

Tôi cũng muốn phát hiện khi một con chuột nó bên ngoài một hình ảnh. Sau câu lệnh if trước đó tôi không thể sử dụng lý do khác là vì:

Khi tôi tạo nhiều hình ảnh trên màn hình và khi chuột của tôi nếu di chuột qua 1 hình ảnh. Nó không di chuột của hình ảnh đó và mã phát hiện ra nó nhưng nó cũng không di chuột qua tất cả các hình ảnh khác. Đó là lý do hiển thị 4 lần "vòng tròn bên ngoài" và 1 lần "vòng tròn bên trong"

Như đã thấy trong nhật ký:

Đầu ra Console.log:

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

Tôi đang tìm cách phát hiện khi chuột rời khỏi một vòng tròn.

Bạn có thể tìm thấy mã tôi đang làm việc với bên dưới:

PS: điều quan trọng là nó phát hiện trong vòng tròn (chỉ mục) con chuột là gì và rời đi. Tôi muốn tạo ra một số lượng lớn hình ảnh, nhưng trong đoạn mã dưới đây tôi đã sử dụng 5 cho các bản demo.

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>

Câu trả lời:


3

chỉ cần thêm một thuộc tính khác vào vòng tròn

  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
    }

và sau đó thay đổi logic cập nhật cho điều này

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

kiểm tra xem các vòng tròn có trùng nhau không và bạn có thể quyết định xem bạn có muốn cập nhật không

  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>


Hi cảm ơn bạn đã trả lời. Khi di chuột qua nhiều vòng tròn trong 1 điểm (cùng một lúc). Thuộc tính mouseinside của nhiều vòng tròn được đặt thành "true". Làm cách nào tôi có thể ưu tiên (theo chỉ mục) chỉ một thuộc tính "mouseinside" của vòng tròn được đặt thành "true".
AttackTheWar

Làm thế nào chính xác bạn di chuột nhiều hơn một vòng tròn cùng một lúc?
Naor Tedgi

@NaorTedgi OP có lẽ có nghĩa là khi có hai vòng tròn chồng chéo.
Richard

Khi 2 vòng tròn chồng chéo @NaorTedgi
AttackTheWar

tôi thêm một đoạn về cách nhận biết nếu các vòng tròn chồng chéo lên nhau, sau đó bạn có thể quyết định xem bạn có muốn cập nhật vòng tròn cụ thể đó không
Naor Tedgi

1

Sự mơ hồ

Không rõ những gì bạn cần liên quan đến vòng tròn và một số điểm (trong câu trả lời này là một thay thế cho chuột và chỉ yêu cầu rằng nó có các thuộc tính xyphải hợp lệ).

Việc thiếu thông tin trong câu hỏi của bạn liên quan đến sự thật

  • rằng nhiều vòng tròn có thể ở dưới điểm cùng một lúc.

  • và nhiều hơn một vòng tròn có thể di chuyển từ dưới ra ngoài hoặc ra ngoài theo điểm trên mỗi khung.

  • từ ngữ của câu hỏi gợi ý bạn chỉ sau một vòng tròn mâu thuẫn với 2 mối quan tâm trên.

Giả định

Tôi sẽ giả định rằng sự tương tác với các vòng tròn không chỉ đơn giản là sự kiện đơn giản như sự tương tác. Rằng chúng có thể bao gồm các hành vi liên quan đến hoạt hình được kích hoạt bởi trạng thái liên quan đến điểm.

Tôi giả định rằng thứ tự trực quan của các vòng tròn sẽ xác định cách bạn chọn vòng tròn quan tâm.

Tất cả các vòng tròn trên mỗi khung đáp ứng các điều kiện cần thiết và có thể được truy cập nhanh chóng.

Hiệu suất đó rất quan trọng vì bạn muốn có nhiều vòng tròn tương tác với một điểm.

Rằng chỉ có một điểm (chuột, chạm, nguồn khác) trên mỗi khung tương tác với các vòng tròn

Không có yêu cầu cho tương tác vòng tròn

Giải pháp

Ví dụ dưới đây bao gồm các giả định ở trên và giải quyết bất kỳ sự mơ hồ nào trong câu hỏi. Nó được thiết kế để có hiệu quả và linh hoạt.

Các vòng tròn được lưu trữ trong một mảng có các thuộc tính được mở rộng gọi là circles

Kết xuất và bộ trạng thái

Các chức năng circles.updateDraw(point)cập nhật và vẽ tất cả các vòng tròn. Đối số pointlà một điểm để kiểm tra vòng tròn chống lại. Nó mặc định cho mouse.

Tất cả các vòng tròn được vẽ với một phác thảo. Các vòng tròn dưới điểm (ví dụ chuột) có màu xanh lá cây, Vòng tròn vừa được di chuyển đến dưới điểm (ví dụ: onMouseOver) được lấp đầy bằng màu vàng, vòng tròn vừa di chuyển từ bên dưới được lấp đầy bằng màu đỏ.

Có 3 mảng là thuộc tính của vòng tròn chứa vòng tròn như định nghĩa ...

  • circles.under Tất cả các vòng tròn dưới điểm
  • circles.outFromUnder Tất cả các vòng tròn chỉ ra từ dưới điểm
  • circles.newUnder Tất cả các vòng tròn mới dưới điểm

Các mảng này được điền bởi hàm circles.updateDraw(point)

Truy vấn tất cả các trạng thái điểm vòng tròn

Vòng kết nối cũng có 3 hàm tham chiếu đến các mảng trên là settập hợp mặc định circles.under.

Các chức năng là ..

  • circles.firstInSet(set)Trả về vòng tròn đầu tiên (Phần dưới cùng trực quan nhất) trong sethoặcundefined
  • circles.lastInSet(set)Trả về vòng tròn cuối cùng (Phần trên cùng trực quan nhất) trong sethoặcundefined
  • circles.closestInSet(set)Trả về vòng tròn gần nhất tới điểm trong sethoặcundefined

Ví dụ: để có được vòng tròn trên cùng trực quan ngay dưới con chuột bạn sẽ gọi circles.lastInSet(circles.newUnder)hoặc để có được vòng tròn gần chuột nhất từ ​​tất cả các vòng tròn dưới con chuột bạn sẽ gọi circles.closestInSet(circles.newUnder)(hoặc mặc định nó đặt undercuộc gọi circles.closestInSet())

Khoanh tròn trạng thái bổ sung

Mỗi Circle có một số thuộc tính bổ sung.

  • Circle.distSqr là bình phương của khoảng cách từ điểm
  • Circle.rSqr là bình phương bán kính tính khi xây dựng.
  • Circle.underCount Giá trị này có thể được sử dụng để áp dụng hình ảnh động cho vòng tròn dựa trên trạng thái tương đối của nó với điểm.
    • Nếu dương là số khung cộng với 1, vòng tròn nằm dưới điểm.
    • Nếu giá trị này là 1 thì vòng tròn sẽ được chuyển từ không xuống dưới.
    • Nếu giá trị này là 0, nó vừa được chuyển ra từ dưới điểm.
    • Nếu âm giá trị này là số lượng khung hình thì vòng tròn không nằm dưới điểm

Chạy thử nghiệm

Sử dụng chuột để di chuyển qua các vòng tròn. Vòng tròn gần nhất và bên dưới chuột được tô màu trắng với alpha = 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>


0

Chà, con chuột đang di chuyển và bạn có thể chỉ cần tạo một Bộ sẽ chứa các đối tượng hình tròn sẽ lưu trữ (các) vòng tròn bạn đang ở:

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

và sau đó tại vòng lặp:

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
}

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;

},

0

Tôi sẽ đề xuất như sau:

  1. Giữ một chồng các hình với thứ tự cách chúng được tạo ra (hoặc bất kỳ thứ tự có ý nghĩa nào khác). Điều này là cần thiết để phát hiện di chuyển trên các con số chồng chéo.

  2. Thực hiện một chức năng / phương thức lặp lại ngăn xếp và xác định xem con trỏ có nằm trong bất kỳ hình nào không.

  3. Hãy nhớ trạng thái cuối cùng, khi chuyển trạng thái bên trong-> ouside kích hoạt một sự kiện.

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

Bây giờ sử dụng nó:

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.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.