HTML表格区域选中列选中-Vue & ElementUI
发布时间
阅读量:
阅读量
HTML 元素(主要是文本)能否被选中,是由 user-select css 属性控制的,若设置为 none 则不可选中,更多属性值参考 MDN.
HTML 页面的默认选中方式是行选择模式,即鼠标从按下到释放中间经过的所有行都会被选中。若要实现列选中模式或是任意选中模式,基本思路是:将表格所有单元格设置为不可选中,在鼠标经过时,将对应的单元格设置可选中,即可实现任意选择的模式。 以上思路有几点需要注意的:
- 浏览器适配:完整的设置不可选中的样式为:
-webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; - 不可选中的元素:不一定是给单元格
td设置不可选中,而应该给直接包裹文字的元素设置(如下例中是td中 class 为cell的div)。 - 框选模式:该思路只能直线涂抹选中,即鼠标经过的 cell 会被选中。若想实现画对角线进行框选,还需要添加逻辑。
- 事件:会涉及的事件:
mousedown,mousemove,mouseup。若使用 jquery 则可以很方便的进行事件注册和 DOM操作,若使用 vue 则可以通过自定义指令directives得到需要操作的 DOM元素。
示例代码(Vue + elementUI):
const selectDisableStyle = `-webkit-user-select:none; -moz-user-select: none; -ms-user-select: none; user-select: none;`
...
directives: {
areaSelect: { // 在需要自定义选择的元素上添加 v-areaSelect
inserted: (el, binding, vnode) => {
let randIds = new Map()
let mouseDownFlag = false
let mouseUpFlag = false
let cells = []
el.addEventListener('mousedown', function (event) {
mouseDownFlag = true
mouseUpFlag = false
cells = []
el.querySelectorAll('tr').forEach(tr => {
let row = tr.querySelectorAll('td div.cell')
row.length > 0 && cells.push(row)
})
cells.forEach((tdRow, idy) => {
tdRow.forEach((tdCol, idx) => {
const style = tdCol.getAttribute('style')
if (style.indexOf(selectDisableStyle) < 0) {
tdCol.setAttribute('style', style + selectDisableStyle)
}
// 若表格有 rowIndex ,cellIndex 则可不设 id
tdCol.setAttribute('id', `${idy + 1}_${idx + 1}`)
})
})
// 选中点击的 cell
removeStyle(event)
})
function mouseMove(evt) {
if (mouseUpFlag || !mouseDownFlag) {
return
}
// 缓存经过的 cell id
randIds.set(evt.target.id, evt.target.id)
// 选中
removeStyle(evt)
}
el.addEventListener('mousemove', mouseMove)
el.addEventListener('mouseup', function (evt) {
mouseUpFlag = true
mouseDownFlag = false
// 框选逻辑
let posList = Array.from(randIds).filter(v => v[0]).map(v => v[0]).map(v => v.split('_'))
let posYList = posList.map(v => v[0])
let posXList = posList.map(v => v[1])
let minX = Math.min(...posXList), minY = Math.min(...posYList)
let maxX = Math.max(...posXList), maxY = Math.max(...posYList)
cells.forEach(cellRow => {
cellRow.forEach(cell => {
let [idy, idx] = cell.id.split('_').map(v => Number(v))
if (idx >= minX && idx <= maxX && idy >= minY && idy <= maxY) {
removeStyle(cell)
}
})
})
// 重置
randIds = new Map()
cells = []
})
}
}
}
// 清除禁止选中的样式,同时选中
function removeStyle(evt) {
let target = evt.target || evt
let style = target.getAttribute('style') || selectDisableStyle
let reg = new RegExp(selectDisableStyle, 'g')
target.setAttribute('style', style.replace(reg, ''))
}
该方法虽然可实现任意区域框选,但复制的操作仍然不理想,复制到 Excel 中仍然会复制整行(可能是 ElementUI 的行为),复制到文本编辑器,多行多列的内容也会被合并为一列(单元格内容被换行或制表符分割)。
第二种思路 不去依赖浏览器的默认复制操作,而是自动将被复制内容写入剪切板。依然可以借鉴上一方法中对各种事件的监听,以及区域框选算法。只是对鼠标经过的单元格,不是设置 user-select:none 之类的样式,而是将单元格添加边框,以示选中。执行区域选中之后,程序是可以知道哪些单元格被选中的,此时可以将这些单元格的内容以想要的格式写入剪切板。
写入剪切板的思路:利用一个不可见 input 元素(若需要多行内容可以使用 textarea),将要复制的文本写入,再执行 setSelectionRange 选中,然后执行 document.execCommand('copy'),将 value 写入系统剪切板。
操作方式:按住 Ctrl 再使用鼠标选择,鼠标释放时自动框选,并将内容复制到剪切板。若不按 Ctrl 则仍旧可以使用浏览器自身的行选择模式。
const selectStyle = 'border: 1px solid rgb(51,144,255); box-shadow: 0px 0px 5px 1px rgb(51,144,255);'
...
mounted() {
// 按下 control 键
// isCtrlPressed => this.ctrlPress > 0
document.onkeydown = (e) => {
if (e.keyCode === 17) {
this.ctrlPress += 1
}
}
document.onkeyup = (e) => {
if (e.keyCode === 17) {
this.ctrlPress = 0
}
}
},
directives: {
areaSelect: {
inserted: (el, binding, vnode) => {
let randIds = new Map()
let mouseDownFlag = false
let mouseUpFlag = false
const vm = vnode.context // 获取当前组件的 Vue 实例
let cells = [] // 表格中所有 cell
let selectedCells = [] // 最终选中的 cell
// 复制之后清除选中样式,单击会与现有事件冲突,改为双击
document.addEventListener('dblclick', function () {
if (!el) { // 该事件不好注销,故加此判断
return
}
el.querySelectorAll('tr').forEach(tr => {
let row = tr.querySelectorAll('td div.cell')
row.forEach(tdCol => {
tdCol.setAttribute('style', "")
})
})
})
el.addEventListener('mousedown', function (event) {
if (!vm.isCtrlPressed) {
return
}
mouseDownFlag = true
mouseUpFlag = false
cells = []
el.querySelectorAll('tr').forEach(tr => {
let row = tr.querySelectorAll('td div.cell')
row.length > 0 && cells.push(row)
})
cells.forEach((tdRow, idy) => {
tdRow.forEach((tdCol, idx) => {
const style = tdCol.getAttribute('style')
// 为了界面简洁明了,选择过程中仍然禁止浏览器自身选中行为
if (style.indexOf(selectDisableStyle) < 0) {
tdCol.setAttribute('style', style + selectDisableStyle)
}
tdCol.setAttribute('id', `${idy + 1}_${idx + 1}`)
})
})
// 选中点击的 cell
selectCell(event)
})
el.addEventListener('mousemove', function mouseMove(evt) {
if (!vm.isCtrlPressed) {
return
}
if (mouseUpFlag || !mouseDownFlag) {
return
}
// 缓存经过的 cell id
randIds.set(evt.target.id, evt.target.id)
selectCell(evt)
})
el.addEventListener('mouseup', function (evt) {
if (!vm.isCtrlPressed) {
return
}
mouseUpFlag = true
mouseDownFlag = false
let posList = Array.from(randIds).filter(v => v[0]).map(v => v[0]).map(v => v.split('_'))
let posYList = posList.map(v => v[0])
let posXList = posList.map(v => v[1])
let minX = Math.min(...posXList), minY = Math.min(...posYList)
let maxX = Math.max(...posXList), maxY = Math.max(...posYList)
cells.forEach(cellRow => {
let selectedRow = []
cellRow.forEach(cell => {
let [idy, idx] = cell.id.split('_').map(v => Number(v))
if (idx >= minX && idx <= maxX && idy >= minY && idy <= maxY) {
selectCell(cell)
selectedRow.push(cell)
}
// 去除禁止选择的样式,仍然支持浏览器自身的行选择模式
removeStyle(cell)
})
selectedRow.length > 0 && selectedCells.push(selectedRow)
})
// WPS 默认单元格以 \t 分割,行以 \n 分割
copyToClipboard(selectedCells.map(v => v).map(row => row.map(cell => cell.innerText).join("\t")).join("\n"))
vm.$message.success("内容已复制到剪切板!")
selectedCells = []
randIds = new Map()
cells = []
})
}
}
}
function removeStyle(evt) {
let target = evt.target || evt
let style = target.getAttribute('style') || selectDisableStyle
let reg = new RegExp(selectDisableStyle, 'g')
target.setAttribute('style', style.replace(reg, ''))
}
function selectCell(evt) {
let target = evt.target || evt
// 可能会有其他元素进入,导致样式不美观
if (target.getAttribute('class').indexOf('cell') < 0) {
return
}
const style = target.getAttribute('style')
if (style.indexOf(selectStyle) < 0) {
target.setAttribute('style', style + ';' + selectStyle)
}
}
function copyToClipboard(text) {
const input = document.createElement('TEXTAREA');
input.style.opacity = 0;
input.style.position = 'absolute';
input.style.left = '-100000px';
document.body.appendChild(input);
input.value = text;
input.select();
input.setSelectionRange(0, text.length);
document.execCommand('copy');
document.body.removeChild(input);
}
效果:

全部评论 (0)
还没有任何评论哟~
