マウスが特定の円の外側にあることを検出するにはどうすればよいですか?


8

マウスが画像の上にあるとき。これは、ifステートメントによって検出されます。

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

また、画像の外でマウスを検出したいと思います。その前のifステートメントの後、他に使用できない理由は次のとおりです。

画面上に複数の画像を生成したとき、および1つの画像の上にマウスを置いたときにマウスを使用したとき。それはその画像のホバーを行い、コードはそれを検出しますが、他のすべての画像のホバーも行いません。それが「外円」を4回、「内円」を1回表示する理由です

ログに見られるように:

Console.logの出力:

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

マウスが円を離れるときに検出する方法を探しています。

以下で私が作業しているコードを見つけることができます:

PS:マウスがどの(インデックス)サークルで検出され、そこから去るかが重要です。大量の写真を作成したいのですが、以下のコードではデモ用に5を使用しました。

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>

回答:


3

サークルに別のプロパティを追加するだけです

  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
    }

そして、更新ロジックはこれに変わります

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

円が重なっているかどうかを確認し、更新するかどうかを決定できます

  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>


こんにちは回答ありがとうございます。1つのスポットで(同時に)複数の円にカーソルを合わせると。複数の円のmouseinsideプロパティは "true"に設定されています。1つの円の「mouseinside」プロパティのみを「true」に設定するように(インデックスで)優先順位を付ける方法を教えてください。
AttackTheWar

どのように正確に同時に1つ以上の円にカーソルを合わせますか?
Naor Tedgi

@NaorTedgi OPは、2つの円が重なっている場合を意味します。
リチャード

2つの円が重なり合っているとき@NaorTedgi
AttackTheWar

円がtと重なっているかどうかを認識する方法に関するスニペットを追加します。その特定の円を更新するかどうかを決定できます
Naor Tedgi

1

あいまいさ

円といくつかの点に関して何が必要かは明確ではありません(この回答の点ではマウスの代わりになり、プロパティxy、有効でです)。

あなたの質問の情報不足は事実に関係しています

  • 多くの円が同時にポイントの下にあることができます。

  • また、フレームごとに複数の円がポイントの下から外へ、またはポイントから下へ移動する可能性があります。

  • 質問の文言は、あなたが上記の2つの懸念と矛盾するたった1つのサークルの後にいることを示唆しています

仮定

サークルとのインタラクションは、インタラクションのような単純なオンアンダーイベント以上のものであると想定します。ポイントに関連する状態によってトリガーされるアニメーション関連の動作を含めることができること。

円の視覚的な順序によって、関心のある円を選択する方法が決まると思います。

必要な条件を満たし、すばやくアクセスできるフレームごとのすべての円。

ポイントと相互作用する多くのサークルが必要であるため、そのパフォーマンスは重要です。

円と相互作用するフレームごとに1つのポイント(マウス、タッチ、その他のソース)しかないこと

サークルサークルインタラクションの要件はありません

解決

以下の例は、上記の前提をカバーし、質問のあいまいさを解決します。効率的で柔軟になるように設計されています。

円は、プロパティが呼び出されて拡張された配列に格納されます circles

レンダリングと状態セット

この関数circles.updateDraw(point)は、すべての円を更新して描画します。議論pointは円をチェックするポイントです。デフォルトはmouseです。

すべての円は輪郭で描かれます。点の下の円(マウスなど)は緑で塗りつぶされ、点の下に移動したばかりの円(onMouseOverなど)は黄色で塗りつぶされ、下から移動したばかりの円は赤で塗りつぶされます。

定義する円を含む円のプロパティとして3つの配列があります...

  • circles.under ポイントの下のすべての円
  • circles.outFromUnder ポイントの真下からすべての円
  • circles.newUnder ポイントの下に新しいすべての円

これらの配列は関数によって入力されます circles.updateDraw(point)

すべての円の点の状態を照会します

Circlesにsetは、デフォルトのセットがであるため、上記の配列を参照する3つの関数もありますcircles.under

機能は..

  • circles.firstInSet(set)setまたはの最初の円(最も視覚的な一番下)を返しますundefined
  • circles.lastInSet(set)setまたはの最後の円(視覚的に一番上の円)を返しますundefined
  • circles.closestInSet(set)setまたはの点に最も近い円を返しますundefined

たとえば、呼び出すマウスの真下の視覚的に最上部の円circles.lastInSet(circles.newUnder)を取得する、または呼び出すマウスの下のすべての円からマウスに最も近い円を取得するcircles.closestInSet(circles.newUnder)(またはデフォルトでunder呼び出しを設定する)circles.closestInSet()

追加の州を円で囲みます

各サークルにはいくつかの追加プロパティがあります。

  • Circle.distSqr 点からの距離の2乗です
  • Circle.rSqr 構築時に計算された半径の2乗です。
  • Circle.underCount この値を使用して、ポイントとの相対的な状態に基づいてアニメーションを円に適用できます。
    • 正数がフレーム数に1を加えたものである場合、円はポイントの下にあります。
    • この値が1の場合、円は単に下から下に移動します。
    • この値が0の場合、ポイントの下から移動したところです。
    • 負の場合、この値はフレームの数であり、円はポイントの下にありません

デモを実行する

マウスを使用して円の上を移動します。マウスの下の最も近い円は、アルファ= 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

まあ、マウスは動いていて、あなたがいる円を格納する円オブジェクトを含むセットを簡単に作成できます:

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

そしてループで:

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

私は以下を提案します:

  1. 数字のスタックを、それらが作成された順序(またはその他の意味のある順序)で保持します。これは、重なり合う図形の動きを検出するために必要です。

  2. スタックを反復し、カーソルが図の中にあるかどうかを判別する関数/メソッドを実装します。

  3. 最後の状態を覚えてください。inside-> ousideの状態遷移でイベントがトリガーされます。

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

今それを使用してください:

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.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.