首页 > 解决方案 > 重新对齐容器中的光标

问题描述

目前正在为一个项目制作游戏,并在游戏顶部添加了“手电筒”效果。
见:https ://codemyui.com/wp-content/uploads/2019/10/Flashlight-Mouse-Pointer.gif

问题是手电筒与光标不对齐,光标位于屏幕左侧。蓝色圆圈是光标的粗略位置以突出问题。这是它的样子

我找不到太多在容器上重新对齐光标的方法。我怀疑是 JS 导致了这个问题,但我不确定。有任何想法吗?

以下是相关代码:

//game flashlight
function update(e) {
  var x = e.clientX || e.touches[0].clientX
  var y = e.clientY || e.touches[0].clientY

  let gameBox = document.getElementById('gameBox');
  gameBox.style.setProperty('--cursorX', x + 'px');
  gameBox.style.setProperty('--cursorY', y + 'px');
}

document.addEventListener('mousemove', update)
document.addEventListener('touchmove', update)
#gameContainer {
  position: relative;
  height: 1000px;
  width: 1100px;
  float: right;
}

#gameContent {
  float: right;
  margin: 20px 10px 0px 10px;
  width: 1050px;
  border-style: solid;
  background-color: #FFE29C;
  border-color: #E1AA72;
  border-width: 3px;
  padding: 10px;
}

#gameBox {
  position: relative;
  height: 768px;
  width: 768px;
  border-style: solid;
  background-color: #EABF7D;
  border-color: #E1AA72;
  border-width: 3px;
  padding: 10px;
  margin-bottom: 10px;
  float: right;
  text-align: center;
  margin: 0 12% 0 12%;
  line-height: 1em;
}


/* Flashlight Overlay */

.image-container {
  position: relative;
  cursor: auto;
  --cursorX: 50vw;
  --cursorY: 50vh;
}

.image-container .after {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
  background: radial-gradient( circle 10vmax at var(--cursorX) var(--cursorY), rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .5) 80%, rgba(0, 0, 0, .95) 100%);
}

#mapFit {
  position: relative;
  left: 10px;
  height: 768px;
  width: 768px;
  object-fit: contain;
}
<div id="gameContainer">
  <div id="gameContent">
    <div id="gameBox" class="image-container">
      <img id="mapFit" src="images/Temple_Escape.jpg" alt="main map">
      <img id="chest" src="images/chest.png" alt="main map" onclick="chestClick()">
      <img id="chestOpened" src="images/chestopened.png" alt="main map" onclick="chestClick()">
      <img id="gold1" src="images/gold.png" alt="main map" onclick="gold1Click()">
      <img id="gold2" src="images/gold.png" alt="main map" onclick="gold2Click()">
      <img id="gold3" src="images/gold.png" alt="main map" onclick="gold3Click()">
      <img id="gold4" src="images/gold.png" alt="main map" onclick="gold4Click()">
      <img id="gold5" src="images/gold.png" alt="main map" onclick="gold5Click()">
      <img id="gold6" src="images/gold.png" alt="main map" onclick="gold6Click()">
      <img id="podium" src="images/podium.png" alt="main map" onclick="podiumClick()">
      <img id="rope" src="images/rope.png" alt="main map" onclick="ropeClick()">
      <img id="skeleton" src="images/skeleton.png" alt="main map" onclick="skeletonClick()">
      <img id="brokenPickaxe" src="images/brokenpickaxe.png" alt="main map" onclick="pickaxeClick()">
      <img id="stoneSlab1" src="images/stoneslab.png" alt="main map" onclick="slab1Click()">
      <img id="stoneSlab2" src="images/stoneslab.png" alt="main map" onclick="slab2Click()">
      <img id="tile1" src="images/tile1.png" alt="main map" onclick="floorStoneClick()">
      <img id="door1" src="images/door1.png" alt="main map" onclick="exitDoorClick()">
      <img id="redOrb" src="images/redorb.png" alt="main map" onclick="">
      <img id="blueOrb" src="images/blueorb.png" alt="main map" onclick="">
      <img id="greenOrb" src="images/greenorb.png" alt="main map" onclick="">
      <div class="after"></div>
    </div>
  </div>

编辑:还有另一个与指针无关的问题,当手电筒覆盖在地图顶部时,我无法单击游戏内的图像。这可能与 div id="after" 的放置有关。对此的任何帮助将不胜感激。

标签: javascripthtmlcss

解决方案


您需要getBoundingClientRect()目标元素,然后从您的event.clientX位置中减去它,以获得鼠标在元素中的正确位置。

用于pointer-events: none;允许您的鼠标单击flashlight element的底层元素。


编辑:

注意你的第二个问题...

您的代码正在寻找mouseovermouseout事件的 event.target ,因此当您添加 时pointer-events: none,现在会在子元素悬停时为它们创建一个事件目标,这是您的img标签。问题是现在用客户端边界坐标减去客户端线的方程将由于目标的变化而改变。因此,为了缓解这种情况,您将需要适应与父元素相关的子元素的逻辑,父元素是用于查找手电筒元素在页面上的位置的相对隔间。

因此,解决方法是在您的 img 标签中添加一个类,class="img"然后运行条件以确保您触发事件的元素是父元素或其 img 子元素......

因此,添加一个条件来检查是否event.target.id游戏框,否则classList.contains('img')这些条件中的每一个都会不同:鼠标悬停时的父元素将是e.target.getBoundingClientRect()e.clientX/Y减去那些以获得父元素内的光标位置,而e.target.classLists.contains('img')条件将获得e的 parentNode .target然后 e.target.parentNode.getBoundingClientRect()从 .target 中减去它e.clientX/Y。因此,无论mouseover事件如何,它都会获得正确的坐标并正确显示元素中的手电筒坐标,同时允许您使用pointer-events:nonecss 规则单击。

const imgs = document.querySelectorAll('.img')
const gameBox = document.getElementById('gameBox')
let rect, x, y = '';
//game flashlight
// default spot in middle
x = gameBox.getBoundingClientRect().width/2;
y = gameBox.getBoundingClientRect().width/2;
gameBox.style.setProperty(`--cursorX`, `${x}px`);
gameBox.style.setProperty(`--cursorY`, `${y}px`);

const update = (e) => {
  if (e.target.id === "gameBox") {
    rect = e.target.getBoundingClientRect();
    x = e.clientX - rect.left;
    y = e.clientY - rect.top;
    gameBox.style.setProperty(`--cursorX`, `${x}px`);
    gameBox.style.setProperty(`--cursorY`, `${y}px`);
  } else if (e.target.classList.contains('img')) {
    imgs.forEach((img) => {
      rect = e.target.getBoundingClientRect();
      x = e.clientX - e.target.parentNode.getBoundingClientRect().left;
      y = e.clientY - e.target.parentNode.getBoundingClientRect().top;
      gameBox.style.setProperty(`--cursorX`, `${x}px`);
      gameBox.style.setProperty(`--cursorY`, `${y}px`);
    })
  } else {          
      gameBox.style.setProperty(`--cursorX`, `${x}px`);
      gameBox.style.setProperty(`--cursorY`, `${y}px`);
  }
}

document.addEventListener('mousemove', update)
document.addEventListener('touchmove', update)
#gameContainer {
  position: relative;
  height: 1000px;
  width: 1100px;
  display: flex;
}

img:hover~.after {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
  background: radial-gradient( circle 10vmax at var(--cursorX) var(--cursorY), rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .5) 80%, rgba(0, 0, 0, .95) 100%);
  pointer-events: none;
}

#gameContent {
  margin: 20px 10px 0px 10px;
  width: 1050px;
  border-style: solid;
  background-color: #FFE29C;
  border-color: #E1AA72;
  border-width: 3px;
  padding: 10px;
}

#gameBox {
  position: relative;
  height: 768px;
  width: 768px;
  border-style: solid;
  background-color: #EABF7D;
  border-color: #E1AA72;
  border-width: 3px;
  padding: 10px;
  margin-bottom: 10px;
  float: right;
  text-align: center;
  margin: 0 12% 0 12%;
  line-height: 1em;
}


/* Flashlight Overlay */

.image-container {
  position: relative;
  cursor: auto;
  --cursorX: 50vw;
  --cursorY: 50vh;
}

.image-container .after {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: block;
  background: radial-gradient( circle 10vmax at var(--cursorX) var(--cursorY), rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, .5) 80%, rgba(0, 0, 0, .95) 100%);
  pointer-events: none;
}

#mapFit {
  position: relative;
  left: 10px;
  height: 768px;
  width: 768px;
  object-fit: contain;
}
<div id="gameContainer">
  <div id="gameContent">
    <div id="gameBox" class="image-container">
      <img id="mapFit" class="img" src="images/Temple_Escape.jpg" alt="main map">
      <img id="chest" class="img" src="images/chest.png" alt="main map" onclick="chestClick()">
      <img id="chestOpened" class="img" src="images/chestopened.png" alt="main map" onclick="chestClick()">
      <img id="gold1" class="img" src="images/gold.png" alt="main map" onclick="gold1Click()">
      <img id="gold2" class="img" src="images/gold.png" alt="main map" onclick="gold2Click()">
      <img id="gold3" class="img" src="images/gold.png" alt="main map" onclick="gold3Click()">
      <img id="gold4" class="img" src="images/gold.png" alt="main map" onclick="gold4Click()">
      <img id="gold5" class="img" src="images/gold.png" alt="main map" onclick="gold5Click()">
      <img id="gold6" class="img" src="images/gold.png" alt="main map" onclick="gold6Click()">
      <img id="podium" class="img" src="images/podium.png" alt="main map" onclick="podiumClick()">
      <img id="rope" class="img" src="images/rope.png" alt="main map" onclick="ropeClick()">
      <img id="skeleton" class="img" src="images/skeleton.png" alt="main map" onclick="skeletonClick()">
      <img id="brokenPickaxe" class="img" src="images/brokenpickaxe.png" alt="main map" onclick="pickaxeClick()">
      <img id="stoneSlab1" class="img" src="images/stoneslab.png" alt="main map" onclick="slab1Click()">
      <img id="stoneSlab2" class="img" src="images/stoneslab.png" alt="main map" onclick="slab2Click()">
      <img id="tile1" class="img" src="images/tile1.png" alt="main map" onclick="floorStoneClick()">
      <img id="door1" class="img" src="images/door1.png" alt="main map" onclick="exitDoorClick()">
      <img id="redOrb" class="img" src="images/redorb.png" alt="main map" onclick="">
      <img id="blueOrb" class="img" src="images/blueorb.png" alt="main map" onclick="">
      <img id="greenOrb" class="img" src="images/greenorb.png" alt="main map" onclick="">
      <div class="after"></div>
    </div>
  </div>


推荐阅读