本文介绍如何在 vue 中实现表格的十字交叉高亮的显示效果,先放效果图,你可以点击 这里 来在线查看这个效果。项目已开源在 github ,你可以点击 这里 查看本效果的源码。
注意,本文中使用了Stylus
预编译样式和Pug
模板语言,请酌情阅读。
实现原理
先来简单说一下这个效果的实现原理,就是 监听鼠标悬停,获取悬停到的横纵轴索引,最后高亮索引行和列并将上一个十字高亮取消。
实现要点
说完了原理,我们来拆分一下细节,这个效果主要包含了如下几个要点:
-
js 监听鼠标悬停事件:通过给单元格绑定
mouseover
事件来完成。 -
高亮时如何切换样式:获取到指定单元格元素,然后通过
dom
元素classList
属性的add
和remove
方法来更新 CSS 类名。 -
获取上一个高亮的横纵坐标:vue 中的
watch
侦听器可以获取到上一个状态。就不用在额外添加逻辑进行状态保存了。 -
移出表格后取消所有高亮:通过给整个表格的
div
绑定mouseout
事件来完成。
开始动手!
ok, 确定了所有技术要点后我们就可以动手了,这里一共分四步,十字高亮的具体实现在最后一步。
1> 制作表格
首先搭建出页面的结构,使用v-for
循环生成一个表格,注意这里v-for
生成的索引是从1
开始的,使用的时候要加以小心:
.table
.row(v-for="rowIndex in 5" :key="rowIndex")
.col(v-for="colIndex in 12" :key="colIndex")
然后通过flex
布局制作出来表格的样式:
// 表格样式
.table
// 行样式
.row
display flex
flex-flow row
// 单元格样式
.col
height 100px
width 100px
margin 2px
transition background-color .2s
// 非高亮(默认)样式
.not-active
background-color #eee
// 高亮样式
.active
background-color #888
然后将.not-active
绑定到.col
上就可以了,下面是实现的效果:
2> 添加监听事件
首先我们在data
定义当前悬停的横纵坐标hoverIndex
:
data: () => ({
hoverIndex: {
col: null,
row: null
}
})
然后在methods
实现mouseout
和mouseover
两个方法:
methods: {
/**
* 鼠标悬停事件
* 将悬停的横纵轴索引值保存到 data
*
* @param {number} rowIndex 行索引
* @param {number} colIndex 列索引
*/
onMouseOver(rowIndex, colIndex) {
this.hoverIndex = {
col: colIndex,
row: rowIndex
}
},
/**
* 鼠标移出事件
* 绑定在整个表格上, 移出表格时取消所有高亮
*/
onMouseOut() {
this.hoverIndex = {
col: null,
row: null
}
}
}
最后我们绑定上对应的鼠标监听事件就可以了,在.table
上绑定mouseout
,在.col
上绑定mouseover
:
.table(@mouseout="onMouseOut")
.row(v-for="rowIndex in 5" :key="rowIndex")
.col.not-active(v-for="colIndex in 12" :key="colIndex" @mouseover="onMouseOver(rowIndex - 1, colIndex - 1)")
3> 封装样式修改方法
我们把样式修改的方法单独抽象出来来方便以后维护,这个方法接受一个HTML
元素和一个boolean
,然后通过修改classList
来更改样式,这里做个兜底,如果元素不存在则啥都不干:
/**
* 切换指定元素的样式
*
* @param {element} ele 要更新样式的状态
* @param {boolean} isActive 元素是高亮还是不高亮
*/
switchState(ele, isActive) {
if (!ele) return false
ele.classList.remove(isActive ? 'not-active' : 'active')
ele.classList.add(isActive ? 'active' : 'not-active')
}
4> 实现十字高亮
十字高亮最简单的方法就是遍历每一个单元格,然后判断它的坐标是否应该高亮,但是这样比较损耗性能,尤其是单元格比较多的时候。下面介绍种比较省性能的方法:
首先遍历每一个行,对行的处理有以下三种:
- 如果是选中行, 则该行单元格全部高亮
- 如果是之前选中的行, 则取消所有单元格高亮, 只保留对应列的那一个
- 如果都不是, 则取消之前选中列的单元格, 并高亮当前选中列的单元格
具体实现如下:
/**
* 高亮样式更新
*
* @param {object} newHover 新的高亮索引, 值为 this.hoverIndex
* @param {object} oldHover 上一个高亮索引, 值同上
*/
highLight(newHover, oldHover) {
let rows = document.getElementsByClassName('row')
// 遍历所有行
for (let i = 0; i < rows.length; i++) {
let cols = rows[i].getElementsByClassName('col')
// 如果是选中行, 则该行单元格全部高亮 (高亮当前行)
if (i == newHover.row) {
for (const col of cols) this.switchState(col, true)
}
// 如果是之前选中的行, 则取消所有单元格高亮, 只保留对应列的那一个 (取消上个行高亮))
else if (i == oldHover.row) {
for (const col of cols) this.switchState(col, false)
this.switchState(cols[newHover.col], true)
}
// 如果都不是, 则取消之前选中列的单元格, 并高亮当前选中列的单元格 (高亮当前列并取消高亮上个列)
else {
this.switchState(cols[oldHover.col], false)
this.switchState(cols[newHover.col], true)
}
}
}
做好了之后就可以通过watch
来触发这个方法:
watch: {
// 监听 hoverIndex 触发样式更新
hoverIndex (newData, oldData) { this.highLight(newData, oldData) }
}
这样,在 hoverIndex 发生变化时就可以进行渲染十字高亮。至此,实现完成。