马黑黑 发表于 2022-12-27 08:20

伪元素点击事件(二)

本帖最后由 马黑黑 于 2022-12-27 09:21 编辑

上一帖,《伪元素点击事件(一)》,我们采用元素和伪元素的 pointer-events 鼠标指针事件属性,以牺牲掉主元素的鼠标指针事件功能,在 ::before 和 ::after 两个伪元素上成功赋予了可接受鼠标交互能力。本帖我们会推进一步,保留主元素的 pointer-events 事件权限,以类似于“热点”的方式实现伪元素的的相应功能。

使用过 DW(即Dreamweaver,一款网页制作软件)的朋友会有深刻印象,它提供一个工具,可以在图片上创建“热点”,点击图片上的不同地方,会有不同的链接指向。我们要为伪元素创建的“热点”类似于此:在主元素上,在两个伪元素所在的地方,各自“构筑”一个矩形区域的可响应鼠标指针事件的“热点”。实现细节中,会涉及一些之前没有介绍过的JS和CSS知识,还有一些算法也很精妙、亮眼。

判断鼠标指针是否处在我们规定的矩形区域上,我们需要五个已知条件:

——鼠标指针所在点的xy坐标数据 ①e
——矩形区域左上角xy坐标 ② x ③ y
——矩形区域宽高 ④ w ⑤ h

我们利用这五个已知条件,创建一个工具函数,以便将来调用:

    let isOver = (e,x,y,w,h) => e.offsetX > x && e.offsetX< w &&e.offsetY > y && e.offsetY < h;

这个函数,使用 && 将四个比较式子连接起来,表示,这四个式子若同时成立,函数返回真(true),四条式子若不能同时成立,返回假(false)。&& 是“与”(and)运算符,与“或”(|| ,or)运算符一样,很常用。各比较式子的意思是比较鼠标指针坐标值 ① 大于矩形X坐标、② 小于矩形宽度、③ 大于矩形Y坐标、④ 小于矩形高度。说明一下,函数的w和h不是真正意义的宽高,它们也是坐标值,w在x基础上加矩形宽度、h在y基础上加矩形高度。

接下来,与上一帖一样,我们也需要通过 window.getComputedStyle(元素).getPropertyValue(属性名) 获取伪元素的xy坐标值和宽高等CSS属性值,但由于机制的改变,我们要获取的伪元素相关属性值会很多,所以,还需要准备另一个工具函数:

      let pseVal = (ele,pseudo) => {
                let items = { left: '', top: '', width: '', height: '', padding: '', border: '', 'box-sizing': '' };
                for(key in items) items = window.getComputedStyle(ele,pseudo).getPropertyValue(key);
                return items;
      };


这个获取伪元素属性值函数,pseVal(ele,pseudo) ,需要两个参数:ele 指伪元素的宿主元素,如 id="papa" 盒子的 papa;pseudo,伪元素名称,如 '::before' 或 '::after'。

函数中,先构建一个实体对象 items {},其内的键值对,键一一对应伪元素(或元素)的CSS属性,left、top、width、height、padding、box-sizing,这些属性都将会影响伪元素的实际矩形占位,必须予以考虑。其各值暂时都是空值,我们会一一用 window.getComputedStyle(元素).getPropertyValue(属性名) 方法来获得,for in 语句就是干这个事,它是for循环语句的高级变种,很适合用于循环对象。最后函数返回各键值对已经赋好值的对象 items。

准备工作还有两个变量的声明与设计:

let hover = 2;
let res = ['伪元素1','伪元素2','元素其它区域'];


hover 变量是鼠标指针记录,它的值预设为 0、1、2,对应于下一个(数组)变量的下标。res 变量是一个数组变量,用来装载鼠标指针所处方位的提示语,这样可以简化后续的操作。

下一步,侦测鼠标指针滑过窗体,这一步原理简单,但非常繁琐。繁琐原因有二:其一,我们要一一获取和计算每一个伪元素的xywh值,子项内容很多,其二,要考虑伪元素的 box-sizing 属性设定,缺省或默认值为 content-box,该值之下,边框 border 和 内边距 padding 的设定都会撑宽撑高盒子,因而w和h的计算要纳入其值;另外一个box-sizing 值,border-box,如果设置为它,则,border和padding不影响盒子的最终宽高。

下面来看看这个繁琐的 onmousemove 事件代码(我们的主元素 id 为 papa):

papa.onmousemove = (e) => {
      let v1 = pseVal(papa,'::before'),
                v2 = pseVal(papa,'::after');
                let x1 = parseInt(v1.left),
                y1 = parseInt(v1.top),
                w1 = parseInt(v1.top),
                h1 = parseInt(v1.height),
                p1 = parseInt(v1.padding),
                b1 = parseInt(v1.border),
                x2 = parseInt(v2.left),
                y2 = parseInt(v2.top),
                w2 = parseInt(v2.width),
                h2 = parseInt(v2.height),
                p2 = parseInt(v2.padding),
                b2 = parseInt(v2.border);
      let width1 = x1 + w1 + (v1['box-sizing'] === 'border-box' ? 0 : (b1+p1)*2),
                width2 = x2 + w2 + (v2['box-sizing'] === 'border-box' ? 0 : (b2+p2)*2),
                height1 = y1 + h1 + (v1['box-sizing'] === 'border-box' ? 0 : (b1+p1)*2),
                height2 = y2 + h2 + (v1['box-sizing'] === 'border-box' ? 0 : (b2+p2)*2);


      let a1 = isOver(e, x1,y1,width1, height1), a2 = isOver(e, x2, y2, width2, height2);
      hover = a1 || a2 ? (a1 ? 0 : 1) : 2;
      papa.style.cursor = (hover < 2) ? 'pointer' : 'default';
};


暗红色部分的代码,全是处理两个伪元素的一些CSS属性和属性值,它们其实都好懂,就是过程繁琐、代码量大。这里,我使用 parseInt JS内置函数将伪元素相关的CSS属性值强制为数值类型,是忽视了小数点,毕竟我们设置 left、top 等值时,都是用整数的,当然,出于严谨,应当考虑强制浮点数函数 parseFloat 或采用正则替换更为稳妥。

红色部分代码,就三句,第一句是给两个伪元素带入工具函数 isOver() 将值赋予 a1 和 a2 两个布尔值变量;第二句处理前面声明的公用变量 hover 的值,两个三元运算嵌套着用,这里解释一下,就是,如果 a1 或 a2 真,则,如果 a1 真,hover 等 0,否则,如果 a1 假,hover等于1,否则,如果 a1 和 a2 没有一个是真,则 hover 等于2;第三句,根据hover变量值将鼠标指针样式赋予窗体。

最后一步,窗体的点击事件处理:

    papa.onclick = (e) => clickMsg.innerText = '点击了' + res;

完工。

马黑黑 发表于 2022-12-27 08:21

<style>
#papa {
        margin: auto;
        width: 600px;
        height: 460px;
        border: 1px solid olive;
        position: relative;
}
#papa::before, #papa::after {
        position: absolute;
        content: '伪元素1';
        top: 100px;
        left: 100px;
        width: 100px;
        height: 100px;
        border: 1px solid silver;
        padding: 10px;
        box-sizing: content-box;
}

#papa::after {
        content: '伪元素2';
        left: 260px;
}
#clickMsg { position: absolute; top: 20px; left: 20px; }
</style>

<div id="papa">
        <div id="clickMsg">点击消息 ...</div>
</div>

<script>

let hover = 2;
let res = ['伪元素1','伪元素2','元素其它区域'];
papa.onmousemove = (e) => {
        let v1 = pseVal(papa,'::before'), v2 = pseVal(papa,'::after');
        let x1 = parseInt(v1.left), y1 = parseInt(v1.top), w1 = parseInt(v1.top), h1 = parseInt(v1.height), p1 = parseInt(v1.padding), b1 = parseInt(v1.border),
                x2 = parseInt(v2.left), y2 = parseInt(v2.top), w2 = parseInt(v2.width), h2 = parseInt(v2.height), p2 = parseInt(v2.padding), b2 = parseInt(v2.border);
        let width1 = x1 + w1 + (v1['box-sizing'] === 'border-box' ? 0 : (b1+p1)*2),
                width2 = x2 + w2 + (v2['box-sizing'] === 'border-box' ? 0 : (b2+p2)*2),
                height1 = y1 + h1 + (v1['box-sizing'] === 'border-box' ? 0 : (b1+p1)*2),
                height2 = y2 + h2 + (v1['box-sizing'] === 'border-box' ? 0 : (b2+p2)*2);
        let a1 = isOver(e, x1,y1,width1, height1), a2 = isOver(e, x2, y2, width2, height2);
        hover = a1 || a2 ? (a1 ? 0 : 1) : 2;
        papa.style.cursor = (hover < 2) ? 'pointer' : 'default';
};

papa.onclick = (e) => clickMsg.innerText = '点击了' + res;

let isOver = (e,x,y,w,h) => e.offsetX > x && e.offsetX< w &&e.offsetY > y && e.offsetY < h;

let pseVal = (ele,pseudo) => {
        let items = { left: '', top: '', width: '', height: '', padding: '', border: '', 'box-sizing': '' };
        for(key in items) items = window.getComputedStyle(ele,pseudo).getPropertyValue(key);
        return items;
};

</script>

马黑黑 发表于 2022-12-27 08:21

二楼完整代码

<style>
#papa {
        margin: auto;
        width: 600px;
        height: 460px;
        border: 1px solid olive;
        position: relative;
}
#papa::before, #papa::after {
        position: absolute;
        content: '伪元素1';
        top: 100px;
        left: 100px;
        width: 100px;
        height: 100px;
        border: 1px solid silver;
        padding: 10px;
        box-sizing: content-box;
}

#papa::after {
        content: '伪元素2';
        left: 260px;
}
#clickMsg { position: absolute; top: 20px; left: 20px; }
</style>

<div id="papa">
        <div id="clickMsg">点击消息 ...</div>
</div>

<script>

let hover = 2;
let res = ['伪元素1','伪元素2','元素其它区域'];
papa.onmousemove = (e) => {
        let v1 = pseVal(papa,'::before'), v2 = pseVal(papa,'::after');
        let x1 = parseInt(v1.left), y1 = parseInt(v1.top), w1 = parseInt(v1.top), h1 = parseInt(v1.height), p1 = parseInt(v1.padding), b1 = parseInt(v1.border),
                x2 = parseInt(v2.left), y2 = parseInt(v2.top), w2 = parseInt(v2.width), h2 = parseInt(v2.height), p2 = parseInt(v2.padding), b2 = parseInt(v2.border);
        let width1 = x1 + w1 + (v1['box-sizing'] === 'border-box' ? 0 : (b1+p1)*2),
                width2 = x2 + w2 + (v2['box-sizing'] === 'border-box' ? 0 : (b2+p2)*2),
                height1 = y1 + h1 + (v1['box-sizing'] === 'border-box' ? 0 : (b1+p1)*2),
                height2 = y2 + h2 + (v1['box-sizing'] === 'border-box' ? 0 : (b2+p2)*2);
        let a1 = isOver(e, x1,y1,width1, height1), a2 = isOver(e, x2, y2, width2, height2);
        hover = a1 || a2 ? (a1 ? 0 : 1) : 2;
        papa.style.cursor = (hover < 2) ? 'pointer' : 'default';
};

papa.onclick = (e) => clickMsg.innerText = '点击了' + res;

let isOver = (e,x,y,w,h) => e.offsetX > x && e.offsetX< w &&e.offsetY > y && e.offsetY < h;

let pseVal = (ele,pseudo) => {
        let items = { left: '', top: '', width: '', height: '', padding: '', border: '', 'box-sizing': '' };
        for(key in items) items = window.getComputedStyle(ele,pseudo).getPropertyValue(key);
        return items;
};

</script>

红影 发表于 2022-12-27 21:08

这个要给两个伪元素设置高和宽,也就是要设置一个带位置的框吧。{:4_204:}

红影 发表于 2022-12-27 21:09

我现在用的这个电脑太难受了,打字特别麻烦。

马黑黑 发表于 2022-12-27 21:28

红影 发表于 2022-12-27 21:08
这个要给两个伪元素设置高和宽,也就是要设置一个带位置的框吧。

所有元素都会有宽高。如果愿意,可以直接根据自己设置的尺寸调用 isOver() 函数,但函数的作用在于,以此示范为例,尺寸的更改,它依然使用,不必每次改变都需要去调整参数,再者,很多时候,我们不一定都能知道元素的尺寸。至于边框的有无,是看实际应用而定,比方说,元素有背景色,就可以不要边框。

马黑黑 发表于 2022-12-27 21:28

红影 发表于 2022-12-27 21:09
我现在用的这个电脑太难受了,打字特别麻烦。

为什么呢

红影 发表于 2022-12-28 21:30

马黑黑 发表于 2022-12-27 21:28
所有元素都会有宽高。如果愿意,可以直接根据自己设置的尺寸调用 isOver() 函数,但函数的作用在于,以此 ...

黑黑这么难的点击事件,不但能完美解决,还能有多重方法,厉害{:4_199:}

红影 发表于 2022-12-28 21:35

马黑黑 发表于 2022-12-27 21:28
为什么呢

没带外接键盘,本本的触控板太难受了。按你的方法去试了,禁用不了,总是把鼠标给禁用了。
compliant Mouse 禁用,鼠标就不动了。PS2/ 兼容鼠标没有禁用,只有卸载,没敢卸,之鞥呢忍着了。

马黑黑 发表于 2022-12-28 21:49

红影 发表于 2022-12-28 21:35
没带外接键盘,本本的触控板太难受了。按你的方法去试了,禁用不了,总是把鼠标给禁用了。
compliant Mo ...

其实是可以操作的。实在不行,可以贴两层贴贴纸。

马黑黑 发表于 2022-12-28 21:50

红影 发表于 2022-12-28 21:30
黑黑这么难的点击事件,不但能完美解决,还能有多重方法,厉害

虽说这些网上都有解决方案,我做这两个东东,没有去查阅。

红影 发表于 2022-12-28 21:51

马黑黑 发表于 2022-12-28 21:49
其实是可以操作的。实在不行,可以贴两层贴贴纸。

哪里去找贴纸啊,呵呵,出门在外,随便用点什么都觉得不方便呢。

红影 发表于 2022-12-28 21:52

马黑黑 发表于 2022-12-28 21:50
虽说这些网上都有解决方案,我做这两个东东,没有去查阅。

能自己写,不借助工具,更厉害{:4_187:}

马黑黑 发表于 2022-12-28 21:55

红影 发表于 2022-12-28 21:52
能自己写,不借助工具,更厉害

掌握一定的知识与技能,一般都可以自己写

马黑黑 发表于 2022-12-28 21:55

红影 发表于 2022-12-28 21:51
哪里去找贴纸啊,呵呵,出门在外,随便用点什么都觉得不方便呢。

文化用品小超市,书店也有,到处有的。

红影 发表于 2022-12-29 19:54

马黑黑 发表于 2022-12-28 21:55
掌握一定的知识与技能,一般都可以自己写

这个还是不容易的,黑黑很厉害{:4_187:}

红影 发表于 2022-12-29 19:55

马黑黑 发表于 2022-12-28 21:55
文化用品小超市,书店也有,到处有的。

我基本不到处跑,人生地不熟的,通常回来了门一关就不出去了。

马黑黑 发表于 2022-12-29 20:21

红影 发表于 2022-12-29 19:55
我基本不到处跑,人生地不熟的,通常回来了门一关就不出去了。

别的了自闭症,很麻烦的

马黑黑 发表于 2022-12-29 20:22

红影 发表于 2022-12-29 19:54
这个还是不容易的,黑黑很厉害

上手了谁都不敢说自己厉害

红影 发表于 2022-12-30 19:49

马黑黑 发表于 2022-12-29 20:21
别的了自闭症,很麻烦的

怎么会,自闭症是真的不跟社会接触,我白天还是要跑到群体里去的啊{:4_173:}
页: [1] 2 3
查看完整版本: 伪元素点击事件(二)