9

関係のない重なり合う HTML 要素間の条件付きマウス イベント パススルーを管理するための標準的な Javascript 手法またはライブラリはありますか?

たとえば、一連の HTML 要素 (Thee.js CSS3 レンダラーによって管理されますが、それは関係ありません) の前に部分的に透明な WebGL キャンバス (Three.js によって管理される) があります。これらの HTML 要素には、mouseover および mouseout イベントが登録されています。これらの要素の前に 3D オブジェクトを浮かせて、マウス イベントをブロックしたいと考えています。

レイキャスターを使用して、マウスが 3D オブジェクト上にあるかどうかを判断する方法は既に知っています。私が知らないのは、3D オブジェクトが間にない場合に、マウス イベントがキャンバスを基になる HTML 要素に「通過」できるようにする方法です。

ここに画像の説明を入力

マウスの下にある要素が見つかるまでDOMツリーをトラバースするソリューションについて読んだことがあります。しかし、それは非常に複雑で遅いようです。できれば、キャンバスが一瞬存在しないふりをして、イベントが自然に通過できるようにしたいです。

車輪の再発明を避けるために、このためのライブラリがすでにあれば素晴らしいでしょう。

4

3 に答える 3

7

David Thomas が提案したようにポインターイベントを使用したくないため、マウスは常に最高の z-index を持つ要素、または同じ z-index を持つ最後の兄弟 (比較的上に積み重ねられた場合) をターゲットにしますその他)。

そうは言っても、私が考えることができる唯一の方法は次のとおりです。

  1. キャンバスを隠す
  2. 下の要素を読む
  3. キャンバスをすぐに表示する

これは非常に高速であるため、目立ったちらつきは発生しません。

マウスの下に要素があった場合は、その要素のマウス イベントをトリガーします。

デモはこちら

$("canvas").mousemove(function (event) {
    if (document.elementFromPoint) {
        var $canvas = $(this);

        // hide canvas visibility
        // don't do display:none as we want to maintain canvas layout
        $canvas.css('visibility', 'hidden');

        // get the element underneath, if any
        var $under = $(document.elementFromPoint(event.clientX, event.clientY));

        // show again the canvas
        $canvas.css('visibility', 'visible');
        if ($under.hasClass('underneath')) { 
           $under.triggerHandler('mouseover');
        } else {
            $("#result").text("mouse is over the canvas");
        }
    }
})
于 2014-06-15T20:28:02.527 に答える
1

私はあなたのためにかなり簡単な解決策を見つけました。そして、あなたのコメントの 1 つで jsfiddle の例を提供したので、そのコードを少し調整して、それを見てみましょう。

私がやっていることはとてもシンプルです。マウスが div に重なっているかどうかを確認し、Three.js レイキャスターがボックスにヒットしないときに関数「changeTheDivBox」が呼び出されます。

var divBox = document.getElementById("background");
divBox.addEventListener("mouseover", mouseOver, false);
divBox.addEventListener('mouseout', mouseOut, false);
var mouseOverBox = false; // Are you currently hovering over the "background" div?
function mouseOver () {
    mouseOverBox = true;
}
function mouseOut () {
    mouseOverBox = false;
}
function changeTheDivBox () { // This function is called when your raycaster does not hit any boxes
    if ( mouseOverBox ) {
        divBox.style.backgroundColor = 'blue';
    }
    else {
        divBox.style.backgroundColor = 'green';
    }
}

基本的:

Is the mouse overlapping the <div> ?
    Yes.
    Is the mouse (raycaster) hitting any boxes?
    No.
        Then Change the <div>'s color!

私が行ったもう 1 つのことは、css プロパティを使用することでしたpointer-events。これにより、選択した要素がマウスイベントに登録されないようにすることで、「さらに後ろに」div をクリックできます。すべてのマウス イベントを無効にするために を追加pointer-events: none; し、次に div 要素に追加して再度有効にしました。そのため、マウスはその div のイベントのみを検出するようになりました。かなりきれい。bodypointer-events: auto;

残りのコードはそのまま残しました。個人的には、ループを使用して、常に mouseIn / mouseOutSo に依存するのではなく、オーバーラップ/レイキャストがまだ有効かどうかを確認することを好みますが、これはあなたの例なので、試してみてください:)

更新されたJSFIDDLE

個人的な好みとして。関数を HTML に入れないようにすることをお勧めします。それらは常に期待どおりに機能するとは限りません。(thisオブジェクトはウィンドウを参照し続けます)そして、コードをより混乱させる可能性があります。個人的には、JavaScript をタグ内に保持することを好みます。とにかく、eventListenersはインライン関数呼び出しよりも強力です:)

于 2014-06-15T21:38:42.277 に答える
0

z-index:-1;コードで#backgroundとを設定しますdisplay:absolute;。つまり、これはのz レイヤーの背後にあり、マウス イベントは体または上のレイヤー内の他のものを対象とします。

本文またはすべての要素を設定することもできますが、それは後でサイトに含まれる他のコードで悪いことになる可能性がありpointer-events:none;ます. #container#container>canvasのように、他のカバー要素をより高い z-index で絶対的に配置することをお勧めします。

DOM イベントが伝播するパスを確認してください: http://www.w3.org/TR/DOM-Level-3-Events/

キャプチャ フェーズでは、イベントは最初にビューに伝播され、次に html と body に伝播され、その後、マウス カーソルの下に表示されているターゲットに到達します。バブリング フェーズでは、伝搬は逆方向にビューに戻ります。

レンダリングされたシーン オブジェクトは、この DOM イベント パスにはありません。ドキュメント イベント リスナーでヒットをレイトレーシングしました。伝播が #background 要素に到達する前にこれを行い、イベントが処理されることがわかっている場合はどこかで安全にします。を試しpreventDefault()ました。残念ながら、mousemove イベントはキャンセルできないため、これは効果がありません。後で呼び出されるイベント リスナーpreventedDefaultが取得しfalseます。の意味は、ユーザー エージェントによる伝播後にpreventDefault()実行される既定の操作を防止することです。たとえば、ダブルクリック後にマウス ポインターの下にある単語をマークします。デフォルトの操作を実行する必要がある場合、イベントがすでに処理されていることを他のリスナーに伝えるために使用することはできません。preventDefault()

Event のカスタム プロパティを true に設定できます。少なくとも firefox では動作しますが、ホスト オブジェクトは W3C によって厳密に指定されていません。これにより、他のブラウザまたは将来的に何らかのエラーが発生する可能性があります。stopPropagation()他のリスナーを呼び出す必要がない場合に使用できます。状況によっては、一部の JavaScript フレームワークと組み合わせて、望ましくない動作やバグが発生する可能性があります。

もう 1 つの方法は、すべてのイベント リスナーがアクセスできるスコープに変数を設定することです。たとえば、グローバル スコープまたはカプセル化された無名関数などです。これは、最後に処理されたイベントを保持でき、伝播が完了するまで変更されません (次のイベントは前に発生しません)。

レイトレーサーがシーン オブジェクトにヒットしない場合は、ドキュメントのリスナーで#backgroundを対象とするイベントを処理することもできます。Event.targetマウスイベントがヒットした最上位の DOM 要素を保持します。

さまざまなアプローチのデモンストレーションを含む変更されたコード:

<!DOCTYPE html>
<html>
  <head>
    <title>events passthrough</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style type="text/css">
body {
  font-family: Monospace;
  margin: 0px;
  overflow: hidden;
}

div#background {
  position: absolute;
  top: 40px;
  left: 40px;
  width: 100px;
  height: 100px;
  background-color: pink;
  /*z-index: -1;            /* this is behind the body */
}


#container>canvas
{ position: absolute;
  z-index: 100;
}

/* Explicitly disable mouse events on covering element. */
/* If z-index of background object is below zero then also disable body */

/* body, */
#container, #container canvas
{ pointer-events:none;
}

/* but let enabled all other elements */
*
{ pointer-events:auto;
}
    </style>
  </head>

  <body>
    <script type="text/javascript" src="https://mrdoob.github.io/three.js/build/three.min.js"></script>
    <script type="text/javascript" src="https://mrdoob.github.io/three.js/examples/js/libs/stats.min.js"></script>

    <div id="background"></div>
    <div id="container"></div>

    <script type="text/javascript">

var lastHandledEvent;

var container, stats;
var camera, scene, projector, renderer;
var particleMaterial;

var objects = [];

// don't run DOM relevant scripts before construction of DOM is guaranteed
document.addEventListener("DOMContentLoaded", function()
{
  init();
  animate();
}, false);

function init() {
    container = document.getElementById( 'container' );
    camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
    camera.position.set( 10, 300, 500 );
    scene = new THREE.Scene();
    var geometry = new THREE.BoxGeometry( 100, 100, 100 );
    for ( var i = 0; i < 10; i ++ ) {
        var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
        object.position.x = Math.random() * 800 - 400;
        object.position.y = Math.random() * 800 - 400;
        object.position.z = Math.random() * 800 - 400;
        object.scale.x = Math.random() * 2 + 1;
        object.scale.y = Math.random() * 2 + 1;
        object.scale.z = Math.random() * 2 + 1;
        object.rotation.x = Math.random() * 2 * Math.PI;
        object.rotation.y = Math.random() * 2 * Math.PI;
        object.rotation.z = Math.random() * 2 * Math.PI;
        scene.add( object );
        objects.push( object );
    }

    var PI2 = Math.PI * 2;
    particleMaterial = new THREE.SpriteCanvasMaterial( {
        color: 0x000000,
        program: function ( context ) {
            context.beginPath();
            context.arc( 0, 0, 0.5, 0, PI2, true );
            context.fill();
        }
    } );
    projector = new THREE.Projector();
    renderer = new THREE.WebGLRenderer({ alpha: true });
    renderer.setSize( window.innerWidth, window.innerHeight );
    // renderer.domElement.style.position = 'absolute';  // is done in CSS
    // renderer.domElement.style.zIndex = 100;           // is done in CSS
    container.appendChild( renderer.domElement );

    // register document mousemove in capturing phase
    // if you want to use another handlers subsequently
    document.addEventListener('mousemove', onDocumentMouseMove, true );
    document.querySelector('#background').addEventListener('mousemove', onMouseMove, false);

    window.addEventListener( 'resize', onWindowResize, false );
}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
    renderer.setSize( window.innerWidth, window.innerHeight );
}

var background = document.getElementById('background');

function onDocumentMouseMove( event ) 
{
    var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
    projector.unprojectVector( vector, camera );
    var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
    var intersects = raycaster.intersectObjects( objects );
    if ( intersects.length > 0 ) 
    {
        intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
        //event.preventDefault();   // mousemove is not cancelable

        // extending event objects works at least in Firefox, not sure if crossbrowser combatible
        // always be careful when extending DOM
        event.handled = true;       // add custom property
        lastHandledEvent = event;   // another way: store in a variable accessible by all handlers
                                    //              to remember this one was the last handled event
        console.log('event marked as handled in document listener');
        //event.stopPropagation();  // or could stop propagation, however,
                                    // often not a good praxis in conjuction with frameworks
    }
    else if(event.target === background)
      console.log('background could be handled in document listener');
}

function onMouseMove( event )
{ 
  // if(event.defaultPrevented   // mousemove not cancable, always false
  if(event.handled)              // TODO: check crossbrowser compatibility
                                 //       of custom properties on event objects.
                                 //       In doubt use a var outside the functions.
    console.log('other listener: event.handled is: '+event.handled);

  if(lastHandledEvent === event) // the safer way: use variable accessible
  {
    console.log('NOT handling event in other listener');
    return;
  }
  console.log(event.target, '...or handled in other listener');
  event.target.style.backgroundColor = '#'+('00000' + (Math.random() * 0xffffff).toString(16)).slice(-6);
}

function animate() {
    requestAnimationFrame( animate );
    render();
}

var radius = 600;
var theta = 0;

function render() {
    theta += 0.1;
    camera.position.x = radius * Math.sin( THREE.Math.degToRad( theta ) );
    camera.position.y = radius * Math.sin( THREE.Math.degToRad( theta ) );
    camera.position.z = radius * Math.cos( THREE.Math.degToRad( theta ) );
    camera.lookAt( scene.position );
    renderer.render( scene, camera );
}
    </script>    
  </body>
</html>
于 2014-06-22T05:30:06.950 に答える