【问题标题】:Child element is causing issues with parent onmousemove event listener子元素导致父 onmousemove 事件侦听器出现问题
【发布时间】:2021-05-30 07:38:49
【问题描述】:

我为我的问题创建了一个非常简单的示例。

Fiddle Link

在小提琴中,我创建了一个名为 parent 的 div,其中包含 2 个 img (为了简单起见,我在示例中使用 div,但在我的项目中,这些是图像) 和控制器 div。我通过将第二张图片定位为absolute,将图片放在彼此的顶部。

我想使用 clip-path 属性来剪辑第二张图像,我单击然后将控制器“拖到 parent div 上。

但是 controller div 会导致 parent mousemove 事件出现问题,只要光标移到控制器 div 上,mouseout 事件就会在 上触发父 div 导致动画故障。

将pointer-events: none 属性添加到controller div 修复了故障,但它也消除了元素的各种鼠标交互,我想要点击和拖动效果。

我想创建类似this网站使用的效果。

【问题讨论】:

    标签: javascript html css dom ecmascript-6


    【解决方案1】:

    const parent = document.getElementById('parent'),
      img2 = document.getElementById('img2'),
      controller = document.getElementById('controller');
    
    let pressed = false;
    
    parent.addEventListener('mousemove', (e) => {
      if (pressed) {
        img2.style.clipPath = `inset(0px 0px 0px ${e.clientX - parent.offsetLeft}px)`;
        controller.style.left = `${e.clientX - parent.offsetLeft}px`;
      }
    });
    
    controller.addEventListener('mousedown', (e) => {
      pressed = true;
    });
    
    controller.addEventListener('mouseup', (e) => {
      pressed = false;
    });
    #parent {
      width: 100%;
      position: relative;
    }
    
    #img1,
    #img2 {
      display: block;
      width: 100%;
      height: 200px;
      pointer-events: none;
    }
    
    #img1 {
      background: red;
    }
    
    #img2 {
      background: green;
      position: absolute;
      top: 0;
      left: 0;
    }
    
    #controller {
      position: absolute;
      top: 0;
      left: 10px;
      width: 10px;
      height: 100%;
      background: black;
      z-index: 1;
      cursor: ew-resize;
      /* pointer-events: none; */
    }
    <div id="parent">
      <div id="img1"></div>
      <div id="img2"></div>
      <div id="controller"></div>
    </div>
    
    <h4>
      Click and Drag the controller to clip the front image
    </h4>

    问题是,您使用了offsetX,它定义了controller 元素左上边缘之间的距离。这意味着距离大约是5px,你的控制器从左边跳到5px,现在距离更大了,控制器向后跳等等。

    MouseEvent 接口的 offsetX 只读属性提供 该事件之间鼠标指针 X 坐标的偏移量 以及目标节点的内边距。

    因此,您可以使用鼠标 x 位置和父级 x 位置之间的差异来定位控制器:

    改为使用clientX 获取鼠标相对于窗口的位置。

    img2.style.clipPath = `inset(0px 0px 0px ${e.clientX - parent.offsetLeft}px)`;
    controller.style.left = `${e.clientX - parent.offsetLeft}px`;
    

    top表达式的含义如下:

    <mouse x-position> - <distance between left screen edge and parent>
    

    【讨论】:

    • 我想要鼠标相对于父元素的位置而不是窗口对象,因为控制器位置对于父元素是绝对的,并且 onmousemove 事件附加到父元素而不是窗口对象
    • 如果您使用 clientX 会发生同样的问题,并且无论如何使用 offsetX 应该没问题,因为问题需要。如果您不移动控制器,则 offsetX 始终会给出正确的偏移量,并且“动画”可以正常工作而不会闪烁。但是,如果您移动控制器元素,偏移量偶尔会下降(有时为 0,有时更多)。我无法解释这一点(目前!) - 特别是因为控制器实际上位于 z-index 1,所以不应该影响父级的偏移量。
    • 找到了解决方法 - 已将 sn-p 放入答案中。
    • @AHaworth 我不认为offsetX 可以使用,因为它获取到目标左上角的距离(在这种情况下,事件的目标是控制器) ,因此它闪烁。可以使用父级和鼠标坐标之间的距离。
    • 实际上,问题是(有时)偏移量是 mousemove 在控制器元素内的偏移量,而不是在父元素内,因此有时您会得到 0 到 10 的 offsetX 读数(宽度控制器,这是我观察到的)。我将更新我的答案,将控制器放回父级,但忽略控制器内的移动。
    【解决方案2】:

    问题似乎是控制器的定位有时(并非总是)“干扰”父级上 offsetX 的读取。并且偏移量下降(在给定的小提琴中下降到 0 或高达 10 左右)。因此,当控制器向后移动然后再次向上移动时,您会得到闪烁。

    我目前无法完全解释这一点,特别是因为控制器是一个绝对定位的元素。

    但是,一种解决方案是将控制器移出父级。

    更新 如果忽略控制器中的任何鼠标移动,则可以将控制器留在父级中(因此当鼠标移动在范围内时,我们不会得到 0 到 10 的偏移读数控制器 - 忽略它们,我们将让事件冒泡到父级,然后可以读取偏移量)。 _

    <head>
    <style>
    * {
      margin: 0;
      padding: 0;
    }
    
    #parent {
      width: 100%;
      position: relative;
    }
    
    #img1, #img2 {
      display: block;
      width: 100%;
      height: 200px;
      pointer-events: none;
    }
    
    #img1 {
      background: red;
    }
    
    #img2 {
      background: green;
      position: absolute;
      top: 0;
      left: 0;
    }
    
    #controller {
      position: absolute;
      top: 0;
      left: 10px;
      width: 10px;
      height: 100%;
      height: 200px;
      background: black;
      z-index: 1;
      cursor: ew-resize;
      /* pointer-events: none; */
    }
    </style>
    </head>
    <body>
    
    <div id="parent">
      <div id="img1"></div>
      <div id="img2"></div>
      <div id="controller"></div>
    </div>
    
    <h4>
      Click and Drag the controller to clip the front image
    </h4>
    
    <!-- img1, img2 are images in my case so i named them as imgs -->
    <script>
    const parent = document.getElementById('parent'),
                img2 = document.getElementById('img2'),
                controller = document.getElementById('controller');
          
    let pressed = false;
    console.log(pressed)
    
    parent.addEventListener('mousemove', (e) => {
        if(!pressed) return;
      if (e.target != parent) return;
        img2.style.clipPath = `inset(0px 0px 0px ${e.offsetX}px)`;
      controller.style.left = `${e.offsetX}px`;
    });
    
    // for testing purpose
     /* parent.addEventListener('mouseout', (e) => {
      console.log('mouse out is called');
     }); */
    
    controller.addEventListener('mousedown', (e) => {
        pressed = true;
    });
    
    controller.addEventListener('mouseup', (e) => {
        pressed = false;
    });
    </script>
    </body>

    【讨论】:

    • 感谢您的努力,事件冒泡技巧有效,但动画不是很流畅
    • 我认为,我必须将控制器放在父元素之外,但控制器位置将不再相对于父元素。
    • 不是很流畅,因为它不是一个“真正的”动画——它是离散的。考虑到它是一个剪辑(而不是翻译),不确定你会怎么做才能让它变得流畅。从剪辑转换为翻译是否适合您的最终要求?
    • 我在最初的回答中将控制器放在父级之外 - 我不确定你所说的“位置不再相对于父级”是什么意思 - 我仍然使用 offsetX 来决定将控制器放在哪里,并且仍然监视父母的事件。我不认为它会改变非平滑度。
    • 我不明白“从剪辑转换为翻译”的含义,但我想在控制器在父级中移动时显示背后的图像,方法无关紧要。如果您知道另一种方法而不是剪裁正面图像,请分享。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-12-04
    • 2017-09-14
    • 1970-01-01
    • 2020-06-08
    • 2014-03-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多