为什么<a> stealing mouse clicks from a <canvas>?

时间:2017-10-04 05:29:03

标签: javascript html canvas three.js anchor

I'm trying to build a 3D piano using three.js; see the code snippet below.

let renderer, camera, scene, light, keys, selectedKey;

init();
render();

function init() {
  renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.querySelector(".app").appendChild(renderer.domElement);

  camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight);
  camera.position.z = 400;

  scene = new THREE.Scene();

  light = new THREE.SpotLight("#777777");
  light.position.y = 800;
  scene.add(light);
  
  keys = new THREE.Group();
  for (let i = 0; i < 15; i++) {
    let key = new THREE.Mesh();
    key.geometry = new THREE.BoxGeometry(30, 30, 130);
    key.material = new THREE.MeshPhysicalMaterial({ color: "#dddddd", emissive: "#888888" });
    key.position.x = (key.geometry.parameters.width + 2) * i;
    key.rotation.x = Math.PI / 4;
    keys.add(key);
  }
  scene.add(keys);
  
  // Center the keys.
  new THREE.Box3().setFromObject(keys).getCenter(keys.position).multiplyScalar(-1); 

  // Add mouse listeners.
  window.addEventListener("mousedown", onMouseDown);
  window.addEventListener("mouseup", onMouseUp);
  window.addEventListener("mousemove", onMouseMove);
  renderer.domElement.addEventListener("mouseleave", onMouseLeave);
}

function render() {
  requestAnimationFrame(render);
  renderer.render(scene, camera);
}

function onMouseDown(event) {
  unpressKey(); // In case the previous mousedown event wasn't followed by a mouseup, force a key unpress now.
  if (event.buttons !== 1) return; // Only accept left mouse button clicks.
  selectedKey = keys.children.find(key => isKeyAtCoord(key, event.clientX, event.clientY));
  pressKey();
}

function onMouseUp(event) {
  unpressKey();
}

function onMouseMove(event) {
  let key = keys.children.find(key => isKeyAtCoord(key, event.clientX, event.clientY));

  renderer.domElement.style.cursor = key ? "pointer" : null;

  // If a key was previously selected and the mouse has moved to another key, make that
  // the new "selected" key. This allows keys to be pressed in a click+drag manner.
  if (selectedKey && key && selectedKey !== key) {
    unpressKey();
    selectedKey = key;
    pressKey();
  }
}

function onMouseLeave(event) {
  unpressKey(); // The mouse left the canvas, so cancel the last click.
}

function pressKey() {
  if (selectedKey) {
    selectedKey.position.y -= selectedKey.geometry.parameters.height / 2;
  }
}

function unpressKey() {
  if (selectedKey) {
    selectedKey.position.y += selectedKey.geometry.parameters.height / 2;
    selectedKey = null;
  }
}

function isKeyAtCoord(key, x, y) {
  x = (x / renderer.domElement.clientWidth) * 2 - 1;
  y = -(y / renderer.domElement.clientHeight) * 2 + 1;
  let raycaster = new THREE.Raycaster();
  raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
  return raycaster.intersectObject(key).length > 0;
}
body, .app {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: #ff6600;
}

.fork img {
  position: absolute;
  top: 0;
  right: 0;
  border: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/86/three.js">
</script>

<div class="app">
</div>

<a class="fork" href="https://github.com">
  <img src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png">
</a>

If you play around with the piano keys long enough, you'll notice that sometimes the fork link in the top-right corner hijacks mouse clicks made on the keys (which are inside a <canvas>), leading to buggy behaviour. In case that's not clear, here's a GIF demonstrating this bug:

GIF

I've yet to determine what the cause of this is, and am having a hard time reproducing it consistently (sometimes you just have to click the keys for a while and wait for it to happen). I think it has something to do with the position: absolute of the link, or maybe the z-index / tabindex. But tweaking these values hasn't solved the problem so far.

Does anyone know what could be causing this and how I can fix it?

UPDATE: I've found that setting .fork img { to .fork { in the stylesheet reproduces the bug more often. Hopefully this helps someone figure out a solution.

1 个答案:

答案 0 :(得分:0)

好吧,我发现将user-select: none;添加到.fork的CSS会修复该错误。但它更多的是一种解决方法,而不是直接解决问题的方法,所以我不会接受这个作为答案。我希望其他人可以使用适当的解决方案...