8.18日更新

后文有一个寻找父节点的,我用的path数组,其实后来我再打印e看了一下,在target里面可以直接找到parentNode,用这个的话更方便一些。

需求描述

项目是基于Vue的,想要实现的功能是:
1、点击ul就知道具体是哪一个li在点击
2、不给li绑定点击事件,只给ul绑定点击事件,因为li很多,一个个给li绑定事件比较麻烦
3、通过知道是点击的哪一个li,进而进行跳转

项目特殊

这个项目中,li标签下面还嵌套了两个标签,分别是img标签和span标签。后面会说到li嵌套和不嵌套对解决这个问题的影响。

<li>
	<img src="..." alt="" />
	<span>message</span>
</li>

关键属性

想要解决这些问题,就需要一些关键的属性,这些属性都是点击事件传过来的e中的
1、e.target,这个属性的作用是获取当前点击的具体元素。
2、childElementCount,这个属性的完整获取路径是e.target.childElementCount,这个属性的值是该节点包含的子节点的数量。
3、path,这个属性的值是一个数组,记录了从window到这个节点所经过的所有节点。
4、dataset,这个属性的值是一个数组,存储我们自己在标签中定义的属性以及对应的值。

问题分析&解决

1、首先肯定是给ul绑定点击事件:

<ul @click="menuNavigation></ul>

2、给ul下面的每一个li都设置一个自定义属性,比如name,并设置一个值:

<li data-name="/home"><li>
<li data-name="/writing"><li>

3、现在我们点击某一个li,ul的click函数就会触发。那么怎么知道我们点击的是哪一个li呢?我们在第2步给每一个li都设置了一个自定义的属性name并赋了值,现在我们使用e.target.dataset可以获取到当前点击的li的name的值,就相当于知道点击的是哪一个li了。

menuNavigation(e) {
  console.log(e.target.dataset.name) //"home" 或 "writing"
}

4、现在有一个问题,如果li里面没有嵌套,直接就是第3步就行了。但是如果li里面嵌套了内容呢?比如本项目中嵌套了一个img和一个span,那么我们第3步还那么简单吗?答案是否定的。在控制台打印e.target,可以发现如果我们点击的区域不同,获取到的e.target的内容也不同。

menuNavigation(e) {
  console.log(e.target) //span 或者 img 或者 li
}

于是当我们点击的地方不是li的时候,就不能直接通过e.target.dataset获取到给li设置的name值了。那么,和li一样,给每一个li里面的img和span标签都设置一个自定义的属性值可以吗?可以,但是做起来也比较麻烦。

5、为了解决第4步中的问题,我们就要用到上文关键属性中的第二个。根据第3步的分析,只需要获取到当前点击的li,根据当前的li的name属性,就知道点击的是哪一个li了。以本项目为例,我的每一个li都是固定的,下面只有两个根节点,那么我通过判断点击的元素是否有子节点就能判断出点击的是li还是img或span。如果childElementCount的值不为0,说明当前点击的元素含有子节点,说明我们点击的肯定就是li,如果为0,说明我们点击的就是span或者img。值不为0就很简单,就是第3步的情况。但是当值为0的时候呢?

  • 不同的区域:
    在这里插入图片描述

如上图,点击红色框线内的区域,e.target获取到的就是img节点;点击绿色框线内区域,e.target获取到的就是span节点,点击黄色框线内,红色和绿色框线外的区域,获取到的就是li节点。每一个区域下的childElementCount打印情况如下:

  • 当点击的是li区域:
    点击li区域

  • 当点击的是span或者img区域:
    当点击的是span或者img

6、当值为0的时候,我们就要用到上文关键属性中的第三个。既然我们取到了img或者span,那么我们就可以通过path数组取到这个img或者span对应的父节点,也就是li,去到了li,就是第3步的情况了。
在这里插入图片描述

观察上图,结合本项目,只需要获取到数组中的第二个元素就能获取到li了,即

console.log(e.path[1]) // Node li

7、通过this.$router.path可以获取到当前页面的路由,再通过以上步骤获取到的自定义属性name的值,做一个判断和跳转就ok了。

if (this.$route.path == toPath) return
this.$router.push(toPath)

其中第一行的代码是防止用户重复跳转到某个相同的地址,比如点击了第一个菜单又点击第一个菜单,此时虽然不会出错,但是控制台会打印错误信息。加上这一行代码,可以防止这个情况。

最终代码

1、html部分:

<ul @click="menuNavigation">
  <li data-path="/home" class="type-activated">
    <img src="../assets/imgs/normal/blog.png" alt="">
    <span>学习博客</span>
  </li>
  <li data-path="/writing">
    <img src="../assets/imgs/normal/writing.png" alt="">
    <span>随笔</span>
  </li>
</li>

2、js部分:

menuNavigation(e) {
  let toPath = ""
  if (e.target.childElementCount != 0) {
    toPath = e.target.dataset.path
  } else {
    toPath = e.path[1].dataset.path
  }
  if (this.$route.path == toPath) return
  this.$router.push(toPath)
}

分析

  • 如果li标签里面嵌套的元素数量不一样或者类型不统一,那么使用本方法可能会比较鸡肋
  • 触类旁通,把自定义属性值放在li的子节点里面也可以,那么我们就需要在直接点击li的那一个情况下,取或者li的子节点,从而获取自定义属性的值。总之就是要获取到设置了自定义属性值的那一个节点。
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐