只在伪元素上检测点击事件

285

请查看这个 fiddle: http://jsfiddle.net/ZWw3Z/5/

我的代码如下:

p {
    position: relative;
    background-color: blue;
}

p:before {
    content: '';
    position: absolute;
    left:100%;
    width: 10px;
    height: 100%;
    background-color: red;
}
    <p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec pede justo, fringilla vel, aliquet nec, vulputate...</p>

我希望只在伪元素(即红色部分)上触发单击事件。也就是说,我不希望在蓝色部分触发单击事件。


1
如何检查点击位置? https://dev59.com/l3A75IYBdhLWcg3wi5sr - user3080276
1
请阅读我之前发布的解决方案这里 - Amr
可能有一个不错的解决方法在这里 - Reza Mamun
1
尝试使用 $('p').before().click(function...) - Mladen B.
@RezaMamun 你能帮我检查一下这个,并给我建议如何实现吗?https://stackoverflow.com/questions/66923436/on-hover-after-element-move-to-current-div - Husna
13个回答

262

这是不可能的;伪元素根本不属于DOM,因此您无法直接将任何事件绑定到它们,只能将其绑定到它们的父元素。

如果您必须在红色区域上仅有一个单击处理程序,则必须创建一个子元素,例如span,将其放置在打开的<p>标记之后,将样式应用于p span而不是p:before,然后将其绑定。


148
在现代浏览器中,似乎可以通过为元素本身及其伪元素设置不同的“pointer-events”值来实现这种效果:http://jsfiddle.net/ZWw3Z/70/。 - Ilya Streltsyn
8
@Ilya Streltsyn的回答很棒——虽然不是“正确”的答案(如果您还需要单击该元素,则无法使用),但对于div上的“关闭按钮”非常有效。 - RozzA
根据Ilya发布的jsfiddle,pointer-events似乎对IE9无效。 - Matt Sgarlata
@user393274:IE9不支持SVG以外的pointer-events。 - BoltClock
1
@theyuv 不,那个页面上整行都可以点击,链接左右两侧都可以。只有链接本身有不同的点击处理程序,因此单击它不会触发折叠/展开子树。 - Ilya Streltsyn
显示剩余4条评论

187

实际上是有可能的。您可以检查点击位置是否在元素外部,因为只有当单击了::before::after时才会发生这种情况。

此示例仅检查右侧的元素,但在您的情况下应该有效。

span = document.querySelector('span');

span.addEventListener('click', function (e) {
    if (e.offsetX > span.offsetWidth) {
        span.className = 'c2';
    } else {
        span.className = 'c1';
    }
});
div { margin: 20px; }
span:after { content: 'AFTER'; position: absolute; }

span.c1 { background: yellow; }
span.c2:after { background: yellow; }
<div><span>ELEMENT</span></div>

JSFiddle


2
对于 Firefox:http://jsfiddle.net/wC2p7/16/。如果使用嵌套元素,您可能需要根据需要计算嵌套偏移量(例如,jQuery:$(e.target).offset().left)。 - bebbi
1
太棒了,谢谢,它起作用了。但是在Firefox和其他符合标准的浏览器中,如果要测试鼠标是否悬停在:after上,您需要检查if (e.clientX > span.offsetLeft + span.offsetHeight),因为这些浏览器没有e.offsetX属性。Fiddle:http://jsfiddle.net/Noitidart/wC2p7/18/ - Noitidart
5
更酷的是如果jQuery决定支持$('a:after').on('click', fn),但我不认为这会发生:p。 - Linus Unnebäck
46
如果::before::after 位于元素内部,那么这种方法实际上不起作用。 - laconbass
1
@Noitidart 2021 更新:offsetX 现在似乎是安全的 :) https://caniuse.com/mdn-api_mouseevent_offsetx - LuH
显示剩余7条评论

97

现代浏览器中,您可以尝试使用pointer-events CSS属性(但这会导致无法检测父节点上的鼠标事件):

p {
    position: relative;
    background-color: blue;
    color:#ffffff;
    padding:0px 10px;
    pointer-events:none;
}
p::before {
    content: attr(data-before);
    margin-left:-10px;
    margin-right:10px;
    position: relative;
    background-color: red;
    padding:0px 10px;
    pointer-events:auto;
}

当事件目标是你的 "p" 元素时,你知道它是你的 "p:before"。

如果你仍然需要在主要的 p 上检测鼠标事件,你可以考虑修改你的 HTML 结构。你可以添加一个 span 标签和以下的样式:

p span {
    background:#393;
    padding:0px 10px;
    pointer-events:auto;
}

事件的目标现在是“span”和“p:before”元素。

没有jQuery的示例:http://jsfiddle.net/2nsptvcu/

使用jQuery的示例:http://jsfiddle.net/0vygmnnb/

以下是支持pointer-events属性的浏览器列表:http://caniuse.com/#feat=pointer-events


我不知道父节点是什么。你的意思是说,如果我在类似.box:before上绑定点击事件,那么.box就无法检测到任何点击吗? - most venerable sir
1
这在一定程度上是正确的:如果你的CSS包含类似于.box{pointer-events:none;} .box:before{pointer-events:auto;}的内容,那么唯一仍然支持事件的元素是p:before。无论如何,请记住JS将会返回主要的.box元素,因为.box:before实际上并不在DOM中存在。 - Fasoeu
你实际上仍然可以在原始元素上检测鼠标事件。你不需要总是设置这个规则,你只需要设置时间来检查元素是否仍然悬停,即使只有伪元素接收到指针事件:https://jsfiddle.net/0z8p5ret/。但这会导致大量的回流,所以可能还有其他更好的方法。 - Kaiido
不幸的是,pointer-events:none也会禁用hover事件。 - Aurovrata

12

简短回答:

我做到了。我为所有小伙伴编写了一个动态使用的函数...

工作示例,可在页面上显示

工作示例,记录到控制台

详细回答:

...还是做到了。

花了我一些时间才做到这一点,因为伪元素实际上不在页面上。虽然上面的一些答案在某些情况下有效,但它们都无法同时具有动态性并在元素大小和位置意外的情况下(例如绝对定位的元素覆盖父元素的一部分)起作用。我的函数可以。

用法:

//some element selector and a click event...plain js works here too
$("div").click(function() {
    //returns an object {before: true/false, after: true/false}
    psuedoClick(this);

    //returns true/false
    psuedoClick(this).before;

    //returns true/false
    psuedoClick(this).after;

});

工作原理:

它获取父元素的高度、宽度、顶部和左侧位置(基于窗口边缘的位置),并获取父容器的高度、宽度、顶部和左侧位置(基于父容器的边缘),然后比较这些值以确定伪元素在屏幕上的位置。

然后它比较鼠标的位置。只要鼠标在新创建的变量范围内,它就返回true。

注意:

最好将父元素设置为相对定位。如果你有一个绝对定位的伪元素,只有当它根据父元素的尺寸定位时,此函数才能正常工作(所以父元素必须是相对的......也许粘性或固定的也可以......我不知道)。

代码:

function psuedoClick(parentElem) {

    var beforeClicked,
      afterClicked;

  var parentLeft = parseInt(parentElem.getBoundingClientRect().left, 10),
      parentTop = parseInt(parentElem.getBoundingClientRect().top, 10);

  var parentWidth = parseInt(window.getComputedStyle(parentElem).width, 10),
      parentHeight = parseInt(window.getComputedStyle(parentElem).height, 10);

  var before = window.getComputedStyle(parentElem, ':before');

  var beforeStart = parentLeft + (parseInt(before.getPropertyValue("left"), 10)),
      beforeEnd = beforeStart + parseInt(before.width, 10);

  var beforeYStart = parentTop + (parseInt(before.getPropertyValue("top"), 10)),
      beforeYEnd = beforeYStart + parseInt(before.height, 10);

  var after = window.getComputedStyle(parentElem, ':after');

  var afterStart = parentLeft + (parseInt(after.getPropertyValue("left"), 10)),
      afterEnd = afterStart + parseInt(after.width, 10);

  var afterYStart = parentTop + (parseInt(after.getPropertyValue("top"), 10)),
      afterYEnd = afterYStart + parseInt(after.height, 10);

  var mouseX = event.clientX,
      mouseY = event.clientY;

  beforeClicked = (mouseX >= beforeStart && mouseX <= beforeEnd && mouseY >= beforeYStart && mouseY <= beforeYEnd ? true : false);

  afterClicked = (mouseX >= afterStart && mouseX <= afterEnd && mouseY >= afterYStart && mouseY <= afterYEnd ? true : false);

  return {
    "before" : beforeClicked,
    "after"  : afterClicked

  };      

}

支持:

我不知道......看起来IE有时会将auto作为计算值返回,似乎很愚蠢。如果在CSS中设置尺寸,则在所有浏览器中都可以正常工作。因此...在伪元素上设置高度和宽度,并仅使用top和left移动它们。我建议在你可以接受它无法正常工作的东西上使用它,比如动画之类的。Chrome像往常一样工作。


每次事件被触发时,event都会通过事件处理程序函数传递。例如点击或输入。当我们使用.click()函数时,我们正在等待点击事件。我们可以指定并说.click(function(e){...})来使事件变为e,但只使用event也是可以接受的。 - user1816910
5
psEUdo,不是psUEdo! - biziclop
上面的代码无法工作,因为“event”未定义。 - Sebastian Zartner
@SebastianZartner - 事件是一个关键字。它是用户执行某些操作时发生的。我在你的评论上面的两个评论中已经解释过了这一点。当用户与页面交互时,通常会触发事件。事件是从事件监听器".click()"传递过来的关键字。如果开发人员想要为事件使用不同的名称,他们可以在事件监听器中设置它,例如".click(e)",这是更常见的方式。然而...即使我在上面有一个工作链接,我也会包括一个更明显的链接,它不会记录到控制台,而是为需要的人记录到页面上。 - user1816910
我应该提到它在Firefox中不起作用,因为那里的event未定义。但是在Chrome和Edge中可以正常工作。 - Sebastian Zartner
关键元素是 var before = window.getComputedStyle(parentElem, ':before');。非常棒。谢谢。 - Mohamed Allal

9

我的答案适用于想要点击页面上明确区域的任何人。这对我在我的绝对定位 :after 上起作用。

感谢这篇文章,我意识到(使用jQuery)我可以使用e.pageYe.pageX而不必担心浏览器之间的e.offsetY/Xe.clientY/X问题。

通过我的试验和错误,我开始在jQuery事件对象中使用clientX和clientY鼠标坐标。这些坐标给了我相对于浏览器视口左上角的鼠标X和Y偏移量。然而,当我阅读Karl Swedberg和Jonathan Chaffer的jQuery 1.4参考指南时,我看到他们经常提到pageX和pageY坐标。在检查更新的jQuery文档后,我发现这些是jQuery标准化的坐标;而且,我发现它们给了我相对于整个文档的鼠标X和Y偏移量(而不仅仅是视口)。

我喜欢这个event.pageY的想法,因为它始终保持不变,相对于文档而言。我可以使用offset()将其与我的:after父元素进行比较,该函数返回相对于文档的X和Y坐标。
因此,我可以在整个页面上设计一系列永远不会改变的“可点击”区域。

这是我的CodePen演示


如果您懒得使用CodePen,这里是JS代码:
* 我的示例只关心Y值。
var box = $('.box');
// clickable range - never changes
var max = box.offset().top + box.outerHeight();
var min = max - 30; // 30 is the height of the :after

var checkRange = function(y) {
  return (y >= min && y <= max);
}

box.click(function(e){
  if ( checkRange(e.pageY) ) {
    // do click action
    box.toggleClass('toggle');
  }
});

9
这对我有效:
$('#element').click(function (e) {
        if (e.offsetX > e.target.offsetLeft) {
            // click on element
        }
         else{
           // click on ::before element
       }
});

这段代码与定位有关,不是通用的,但使用 e.offsetX/Ye.target.offsetLeft/e.target.offsetWidth 是一个很好的起点,适用于每个特定的情况。 - user151496

6

这是由Fasoeu编辑的答案,使用最新的CSS3和JS ES6

已编辑演示文稿,不使用JQuery。

代码的最短示例:

<p><span>Some text</span></p>

p {
    position: relative;
    pointer-events: none;
}
p::before {
    content: "";
    position: absolute;
    pointer-events: auto;
}
p span {
    display: contents;
    pointer-events: auto;
}

const all_p = Array.from(document.querySelectorAll('p'));

for (let p of all_p) {
    p.addEventListener("click", listener, false);
};

解释:

pointer-events 控制事件的检测,从目标中移除接收事件,但保持从伪元素接收事件使得可以在 ::before::after 上点击,并且你总是知道你正在点击哪个伪元素,然而如果你仍然需要点击,则将所有内容放入嵌套元素中(例如 span),但因为我们不想应用任何额外的样式,display: contents; 成为非常方便的解决方案,它被 大多数浏览器 支持。正如原帖中已经提到的那样,pointer-events: none; 也被广泛 支持

JavaScript 部分也使用了广泛支持的 Array.fromfor...of,但在代码中不必使用它们。


3

我通过在 :after 的css中添加 pointer-events: none; 解决了这个问题。


你能再解释一下吗?目前有多个回答使用相同的解决方案。那么你的回答是如何为其他用户增加了一些东西的呢?请修改你的回答 :) - Elikill58
1
这个解决方案非常好,因为它依赖于支持“pointer-events”的浏览器的标准CSS属性。例如,p { pointer-events: none; } p::before { pointer-events: auto; } - morganney
它实际上运行良好,没有缺陷。 - norbertas.gaulia

1

在点击事件中添加条件以限制可点击区域。

    $('#thing').click(function(e) {
       if (e.clientX > $(this).offset().left + 90 &&
             e.clientY < $(this).offset().top + 10) {
                 // action when clicking on after-element
                 // your code here
       }
     });

DEMO


-1

没有使用 JQuery,我使用以下代码来检测侧边栏菜单的点击事件:

HTML:

<ul>
    <li>MENU ELEMENT</li>
    <li>MENU ELEMENT</li>
    <li>MENU ELEMENT</li>
</ul>

CSS:

ul { margin: 30px; }
li { display: flex; width: 300px; justify-content: space-between;}
li:after { content: ' +'}

li.c1 { background: red; }
li.c2:after { background: yellow; }

JS:

document.querySelectorAll("li").forEach(function (e) {
        e.addEventListener('click', function(u) {
            let linkWidth = this.offsetWidth;           
            let pseudoWidth = parseFloat(window.getComputedStyle(this, ':after').width);
            const mouseX = u.offsetX;
            if (mouseX > (linkWidth - pseudoWidth)) {
                console.log ("click pseudo");
                this.className = 'c2';
            } else {
                console.log ("click element");
                this.className = 'c1';
            }
        })
});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接