页面滚动(场景比较复杂)
代码
dom:
<div class="scroll-container"> <div class="scroll-content"> <div class="scroll-inner"> 内部滚动测试 </div> </div> </div>
css:
body { height: 1800px; } .scroll-container { max-height: 500px; background: pink; overflow: auto; } .scroll-content { height: 800px; } .scroll-inner { width: 300px; height: 300px; background-color: purple; }
js:
let mouseX = 0; let mouseY = 0; let scrollStartEl = null; //用于记录滚动的起始元素,为了保证重现操作时为元素设置scrollTop时不出现偏差 let scrollRecordList = []; let scrollElementSet = new Set(); // 通用节流方法 const throttle = function (cb, delay = 100) { let timer = null; return (ev) => { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { cb && cb(ev); }, delay) }; } // 绑定滚动事件 setScrollWatcher = function (ev) { mouseX = ev && ev.clientX || mouseX; mouseY = ev && ev.clientY || mouseY; scrollStartEl = document.elementFromPoint(mouseX, mouseY); let el = scrollStartEl; while (el) { if (scrollElementSet.has(el)) { el = null; } else { el.onscroll = throttle(recordScrollInfo); scrollElementSet.add(el); el = el.parentNode; } } }; // 记录滚动信息 recordScrollInfo = function (ev) { let el = scrollStartEl; // 单纯的滚动也可能引起鼠标对应的dom的变化,滚动结束也需要setScrollWatcher setScrollWatcher(); let scrollRecordInfo = { mouseX: mouseX, mouseY: mouseY, scrollTopList: [] } while (el) { scrollRecordInfo.scrollTopList.push(el.scrollTop); el = el.parentNode; } scrollRecordList.push(scrollRecordInfo); console.log(scrollRecordList) } // 绑定鼠标移动事件 document.onmousemove = throttle(setScrollWatcher);
讲解
这里大家请跟着我的思路一步一步来:
- 首先我给
document
绑定了鼠标移动事件,并且为其设置了节流
// 绑定鼠标移动事件 document.onmousemove = throttle(setScrollWatcher);
我现在得到了
鼠标最后停止的位置
,我的思路是,如果现在用户开始滚动鼠标滚轮,那么页面可能发生的滚动可能就出现在,鼠标悬停位置对应的元素及其所有祖先元素
上!
- 正如前面的介绍,我现在要做的就是给
鼠标悬停位置对应的元素及其所有祖先元素
绑定滚动事件,即setScrollWatcher
方法所做的事情。
let mouseX = 0; let mouseY = 0; let scrollStartEl = null; //用于记录滚动的起始元素,为了保证重现操作时为元素设置scrollTop时不出现偏差 let scrollElementSet = new Set(); // 绑定滚动事件 setScrollWatcher = function (ev) { mouseX = ev && ev.clientX || mouseX; mouseY = ev && ev.clientY || mouseY; scrollStartEl = document.elementFromPoint(mouseX, mouseY); let el = scrollStartEl; while (el) { if (scrollElementSet.has(el)) { el = null; } else { el.onscroll = throttle(recordScrollInfo); scrollElementSet.add(el); el = el.parentNode; } } };
大家看的应该也不是很费劲,首先鼠标停下来了,我就记录一下现在鼠标所在的元素存在
scrollStartEl
中,之后为它以及他的所有祖先元素设置滚动事件(当然滚动事件也做了节流处理
~)。
优化
:其中为了防止重复绑定滚动事件,也做了一定的优化,引入了一个scrollElementSet
,如果set中存在这个元素,那么直接结束循环。(tip:因为一个元素只会有一个直接父元素
)
- 下一步就显而易见了,就是要在滚动事件结束之后对其进行记录~
recordScrollInfo = function (ev) { let el = scrollStartEl; // 单纯的滚动也可能引起鼠标对应的dom的变化,滚动结束也需要setScrollWatcher setScrollWatcher(); let scrollRecordInfo = { mouseX: mouseX, mouseY: mouseY, scrollTopList: [] } while (el) { scrollRecordInfo.scrollTopList.push(el.scrollTop); el = el.parentNode; } scrollRecordList.push(scrollRecordInfo); console.log(scrollRecordList) }
我只需要记录滚动开始鼠标所在元素
scrollStartEl
及其所有祖先元素的scrollTop
就可以了,之后在复线操作的过程中,根据记录的mouseX
,mouseY
找到滚动开始元素,并为其以及其所有祖先元素设置scrollTop即可~
注意
:
- 单纯的滚动也可能引起鼠标对应的dom的变化,滚动结束也需要setScrollWatcher
- 因为滚动结束后鼠标位置对应的元素可能会变,所以确实需要在滚动前记录滚动开始元素,防止复现操作时出现元素层级不一致问题。
我感觉这个滚动的记录还是有点骚的,哈哈哈,我就是这么臭不要脸,哈哈哈~
完整代码【方便查看】
完整的测试代码在此,如果上github不方便,可以直接复制粘贴试一下~
<!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>Document</title> <style type="text/css"> body { height: 1800px; } .scroll-container { max-height: 500px; background: pink; overflow: auto; } .scroll-content { height: 800px; } .scroll-inner { width: 300px; height: 300px; background-color: purple; } .btn { margin-top: 1000px; } </style> </head> <body> <!-用于测试多种类型的点击事件可否派发--> <label><input name="Fruit" type="radio" value="" class="aaa" />苹果 </label> <label><input name="Fruit" type="radio" value="" class="bbb" />桃子 </label> <label><input name="Fruit" type="radio" value="" />香蕉 </label> <label><input name="Fruit" type="radio" value="" />梨 </label> <label><input name="Fruit" type="radio" value="" />其它 </label> <!-用于测试keydown事件--> <input class ="inputTest" /> <button class="btn"> nihao </button> <div class="scroll-container"> <div class="scroll-content"> <div class="scroll-inner"> 内部滚动测试 </div> </div> </div> <script> /* * 第一步:完成点击事件,滚动事件,键盘输入事件的记录和重现。 * TODO:待办:拖拽事件? */ const clickEvent = new MouseEvent('click'); const focusEvent = new FocusEvent('focus',{ view: window }); // 通用节流方法 const throttle = function (cb, delay = 100) { let timer = null; return (ev) => { if (timer) { clearTimeout(timer) } timer = setTimeout(() => { cb && cb(ev); }, delay) }; } //TODO: 所有点击事件,滚动事件,键盘输入事件都需要记录时间点,相对最开始时间偏移,以正确重现 // 测试点击事件 document.onclick = function (ev) { const x = ev.clientX; const y = ev.clientY; setTimeout(() => { document.elementFromPoint(x, y).dispatchEvent(clickEvent); }, 2000) } document.getElementsByClassName('btn')[0].onclick = function () { console.log(11, 1) } // TODO:(待验证)测试重现滚动事件 let mouseX = 0; let mouseY = 0; let scrollStartEl = null; //用于记录滚动的起始元素,为了保证重现操作时为元素设置scrollTop时不出现偏差 let scrollRecordList = []; let scrollElementSet = new Set(); setScrollWatcher = function (ev) { mouseX = ev && ev.clientX || mouseX; mouseY = ev && ev.clientY || mouseY; scrollStartEl = document.elementFromPoint(mouseX, mouseY); let el = scrollStartEl; while (el) { if (scrollElementSet.has(el)) { el = null; } else { el.onscroll = throttle(recordScrollInfo); scrollElementSet.add(el); el = el.parentNode; } } }; recordScrollInfo = function (ev) { let el = scrollStartEl; // 单纯的滚动也可能引起鼠标对应的dom的变化,滚动结束也需要setScrollWatcher setScrollWatcher(); let scrollRecordInfo = { mouseX: mouseX, mouseY: mouseY, scrollTopList: [] } while (el) { scrollRecordInfo.scrollTopList.push(el.scrollTop); el = el.parentNode; } scrollRecordList.push(scrollRecordInfo); console.log(scrollRecordList) } // 绑定鼠标移动事件 document.onmousemove = throttle(setScrollWatcher); // 测试键盘输入事件 // 做法:假定,持续的键盘输入都是对同一个 input的输入, // 那我需要做的就是保存输入顺序,并记录上一刻点击的元素,并改变其value!!判断是不是input(有没有value属性来判断),如果不是用innerHtml/innerText去塞!!!为了支持富文本 document.onkeydown = function (ev) { console.log(ev) } const keyTestEl = document.getElementsByClassName('inputTest')[0]; keyTestEl.dispatchEvent(clickEvent) console.log(keyTestEl, focusEvent) keyTestEl.dispatchEvent(focusEvent) // const keyEvent = new KeyboardEvent('keypress',{'key':'a'}) //keyTestEl.dispatchEvent(keyEvent) //keyTestEl.value = '11111' // 测试radio的点击事件是否可以派发 document.getElementsByClassName('bbb')[0].dispatchEvent(clickEvent) document.getElementsByClassName('aaa')[0].dispatchEvent(focusEvent) </script> </body> </html>
结束语
网络异常,图片无法展示
|
眼里要有小星星,生活才能亮晶晶~
感谢大家阅读,都要快乐生活哦~
下一篇文章,故事继续哈