昨天朋友说要实现一个效果,获取鼠标所在区域的容器,区域外都遮罩半透明,起名为专注模式。我们今天来实现一下。
需求分析
获取鼠标位置,鼠标属于的容器。
mouseover
,mouseout
,mousedown
,mouseup
,mouseenter
,mouseleave
,mousemove
这么多事件太容易了。- 但是因为 DOM 结构太多了,其实要的元素有可能是父级、祖先级,这里需要实现一个类似于 jquery 的 parants。
elementsFromPoint
获取坐标下的 DOM 元素e.path
获取路径
区域外遮罩半透明。这个感觉类似于新手引导的那种效果。
- 方案:克隆节点,fixed定位
- 方案:
box-shadow
- 方案:
outline
- 方案:
border
,这个就不推荐使用了,上面两个不会影响位置。
功能实现
获取鼠标位置的DOM元素
简直不要太简单。通过事件对象直接拿 e.target
window.addEventListener('mousemove', function(e){
console.log(e.target)
})
向上递归查找符合条件的DOM元素
parentNode
可以获取到父节点,因为只有一个父节点,比找子节点还少了一个遍历。
实现了一个简单的选择器,只处理了单个的 class、id、tag,想深入的可以看 jquery 的实现。
function findParentAttribute(node, key = ''){
key = key.trim();
if(!node) return null;
if(!key) return node;
if(node == document) return null;
switch(key.slice(0,1)){
case '.': if(node.classList.contains(key.slice(1))){return node}
case '#': if(node.id == key.slice(1)){return node}
default: if(node.tagName.toLowerCase() == key){return node}
}
if(node.parentNode){
return findParentAttribute(node.parentNode, key)
}
return null;
}
通过 elementsFromPoint 来实现获取鼠标位置DOM 节点
经过评论区的哥们提醒,可以用 document.elementsFromPoint(e.clientX, e.clientY)
来获取所有的,就不用递归了
通过 e.path 来实现获取鼠标位置 DOM 节点
突然又想到可以使用 e.path 来直接获取触发路径,省去递归。
遮罩半透明实现
outline 实现
box-shadow
JS 克隆 DOM
因为原始的 DOM 不能直接修改为 fixed,会造成布局变化,所以我们直接克隆一个,然后将克隆的 fixed 定位。类似于模拟拖拽效果代码。
这个懒得写了,有没有大佬评论区留言呀。
完整代码
shadowClass = ['.stream-list__item','div','#app'];
shadowEl = null;
shadowStyleTimeout = 0;
shadowStyle = `.lilnong-shadow{outline: 9999px solid rgba(0,0,0,.5);z-index: 9999999999;transform: translate3d(0px,0px,1px);position: relative;}`;
if(!window.styleEl){
var styleEl = document.createElement('style');
styleEl.id = styleEl;
}
styleEl.innerHTML = shadowStyle;
if(!styleEl.parentNode){
document.head.appendChild(styleEl)
}
window.addEventListener('mouseover', function(e){
var el = e.target;
var newEl = null;
for(let i = 0,l = shadowClass.length; i < l; i++){
newEl = findParentAttribute(el, shadowClass[i]);
if(newEl) break;
}
if(shadowEl) shadowEl.classList.remove('lilnong-shadow')
clearTimeout(shadowStyleTimeout);
shadowStyleTimeout = setTimeout(v=>{
if(newEl){
newEl.classList.add('lilnong-shadow')
shadowEl = newEl;
}
},50)
})
function findParentAttribute(node, key = ''){
//console.log(node, key)
key = key.trim() || '';
if(!node) return null;
if(!key) return node;
if(node == document) return null;
switch(key.slice(0,1)){
case '.': if(node.classList.contains(key.slice(1))){return node}
case '#': if(node.id == key.slice(1)){return node}
default: if(node.tagName.toLowerCase() == key){return node}
}
if(node.parentNode){
return findParentAttribute(node.parentNode, key)
}
return null;
}
基于 elementsFromPoint 来获取 dom
评论区一哥们提醒还有 elementsFromPoint
和 elementFromPoint
来实现,获取鼠标位置的 DOM 元素
shadowClass = ['.stream-list__item','div','#app'];
shadowEl = null;
shadowStyleTimeout = 0;
shadowStyle = `.lilnong-shadow{outline: 9999px solid rgba(0,0,0,.5);z-index: 9999999999;transform: translate3d(0px,0px,1px);position: relative;}`;
if(!window.styleEl){
var styleEl = document.createElement('style');
styleEl.id = styleEl;
}
styleEl.innerHTML = shadowStyle;
if(!styleEl.parentNode){
document.head.appendChild(styleEl)
}
window.addEventListener('mouseover', function(e){
if(shadowEl) shadowEl.classList.remove('lilnong-shadow')
var els = document.elementsFromPoint(e.clientX, e.clientY);
shadowClass.every(selectorKey=>{
var el = els.find(el=>{
keySlice = [selectorKey.slice(0,1),selectorKey.slice(1)]
switch(keySlice[0]){
case '.': if(el.classList.contains(keySlice[1])){return el}
case '#': if(el.id == keySlice[1]){return el}
default: if(el.tagName.toLowerCase() == selectorKey){return el}
}
return false;
})
if(el){
el.classList.add('lilnong-shadow')
shadowEl = el;
return false
}
return true
})
})
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。