一、DEMO
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>selection</title>
<style>
* {
padding: 0px;
margin: 0px;
list-style: none;
}
html,body { height: 100%; width: 100%; overflow: hidden; }
#root {
height: 100%;
background: #000;
overflow-y: auto;
overflow-x: hidden;
}
ul li {
margin: 10px;
border: #ccc solid 1px;
display: inline-block;
width: 200px;
height: 200px;
text-align: center;
line-height: 200px;
color: #fff;
}
/* 框选盒子样式 */
.tempDiv {
border:1px dashed #fff;
background:#fff;
position:absolute;
width:0;
height:0;
opacity:0.5;
pointer-events: none;
}
</style>
<script src="selection.js"></script>
</head>
<body>
<div id="root">
<ul class="oul">
</ul>
</div>
<script>
// 创建 元素
const oul = document.querySelector('.oul')
const map = new Map()
Array(50).fill().forEach((item, index) => {
const oli = document.createElement('li')
const key = index + 1
oli.setAttribute('data-id', key)
oli.classList.add('target-node')
oli.innerText = key
map.set(key + '', oli)
oul.appendChild(oli)
})
// 调用函数
selection({
root: document.querySelector('#root'), // 盒子
target: '.target-node', // 框选元素类名
dataId: 'data-id', // 框选元素 data-xxx
callBack: (ids) => { // 框选元素回调 优先 dataId || Element
ids.forEach(id => {
const oli = map.get(id)
oli.style.borderColor = 'red'
})
}
})
</script>
</body>
</html>
二、selection.js
// 默认配置
const defaults = {
root: document,
target: '.target-node',
dataId: 'data-id'
}
// 事件阻止
function clearEventBubble(evt) {
if (evt.stopPropagation)
evt.stopPropagation();
else
evt.cancelBubble = true;
if (evt.preventDefault)
evt.preventDefault();
else
evt.returnValue = false;
}
// ---------------- 关键算法 ---------------------
function computed (pos, option) {
const Rect = option.root.getBoundingClientRect()
const targetList = Array.from(document.querySelectorAll(option.target))
const {x,y,w,h} = pos
let ids = []
const scrollTop = option.root.scrollTop
const scrollLeft = option.root.scrollLeft
targetList.forEach(node => {
const nodeRect = node.getBoundingClientRect()
let sleft = nodeRect.left + scrollLeft - Rect.left + node.offsetWidth
let sTop = nodeRect.top + scrollTop - Rect.top + node.offsetHeight
let offsetLeft = nodeRect.left - Rect.left + scrollLeft
let offsetTop = nodeRect.top - Rect.top + scrollTop
if (sleft > x && sTop > y && offsetLeft < x + w && offsetTop < y + h) {
ids.push(node.getAttribute(option.dataId) || node)
return true
} else {
return false
}
})
option.callBack && option.callBack(ids)
}
const selection = function (option = {}) {
for (let key in defaults) {
if (!option.hasOwnProperty(key)) {
option[key] = defaults[key]
}
}
const Root = option.root
Root.onmousedown = function (e) {
if (e.button !== 0) return false
const Rect = Root.getBoundingClientRect()
var posx = e.clientX - Rect.left + Root.scrollLeft
var posy = e.clientY - Rect.top + Root.scrollTop
var div = document.createElement('div')
div.className = 'tempDiv'
div.style.left = posx + 'px'
div.style.top = posy + 'px'
Root.style.position = 'relative'
Root.appendChild(div);
clearEventBubble(e)
Root.onmousemove = function(ev){
const clientX = ev.clientX - Rect.left
const clientY = ev.clientY - Rect.top
const left = Math.min(clientX + Root.scrollLeft, posx)
const top = Math.min(posy, clientY + Root.scrollTop)
const width = Math.abs(posx - clientX + Root.scrollLeft)
const height = Math.abs(posy - clientY - Root.scrollTop)
div.style.left = left+ 'px'
div.style.top = top + 'px'
div.style.width = width + 'px'
div.style.height = height + 'px'
if (width < 5 && height < 5 ) return false
computed({
x: left,
y: top,
w: width,
h: height
}, option)
clearEventBubble(ev)
}
document.onmouseup = function(){
Array.from(document.querySelectorAll('.tempDiv')).forEach(node => {
node.remove()
})
Root.style.position = ''
Root.onmousemove = null
document.onmouseup = null
}
}
}
// export default selection
三、CSS
.tempDiv {
border:1px dashed #fff;
background:#fff;
position:absolute;
width:0;
height:0;
opacity:0.5;
pointer-events: none;
}
四、使用
selection({
root: document.querySelector('#root'), // 盒子
target: '.target-node', // 框选元素类名
dataId: 'data-id', // 框选元素 data-xxx
callBack: (ids) => { // 框选元素回调 优先 dataId || Element
ids.forEach(id => {
const oli = map.get(id)
oli.style.borderColor = 'red'
})
}
})