需求分析
- 获取鼠标位置,鼠标属于的容器。
mouseover
,mouseout
,mousedown
,mouseup
,mouseenter
,mouseleave
,mousemove
这么多事件太容易了。
a.但是因为 DOM 结构太多了,其实要的元素有可能是父级、祖先级,这里需要实现一个类似于 jquery 的 parants。
b.elementsFromPoint
获取坐标下的 DOM 元素
c.e.path
获取路径
- 区域外遮罩半透明。这个感觉类似于新手引导的那种效果。
a.方案:克隆节点,fixed定位
b.方案:box-shadow
c.方案:outline
d.方案: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 }) })