问题描述:
最近遇到一个很棘手的bug,最初做需求的时候没考虑到通用,认为前端不会经常改架构,就写死了只对某个标签设置scrollbar的值,导致后面前端html架构改了之后,找不到相应的标签,触屏滑动失效。
先说环境和需求:
SUSE linux, qt5.15.0(内置Chrome浏览器内核), Elo触屏显示器。
用QWebEngineView加载网页,希望做到像在手机上一样,通过手指上下滑动达到屏幕滚动效果
最开始做的时候,前端把滚动条放在IFRAME中,通过document.activeElement可以得到IFRAME,所以,只需要识别到IFRAME 标签,然后调用document.activeElement.contentWindow.scrollBy(0,yOffet)就能实现屏幕滚动。
现在前端修改了html架构, 把IFRAME标签去掉了,这就导致当初的逻辑完全失效,屏幕无法滚动。
问题分析
由于Chrome浏览器的特殊性,无论点击网页什么位置(除了输入框之类),document.activeElement.tagName始终返回BODY,无法得到BODY内部实际active的元素。Google各种资料,发现可以通过event获取当前事件的target。当前event事件的target可能是一个TD,DIV或者其他自定义标签,滚动条一般在其父节点上。尝试通过当前事件的target往上依次查找其父节点,直到得到有滚动条的元素。js脚本如下:
QString load = QStringLiteral(
" function f(evt) {" \
"if(evt && evt.target) { " \
"var element = evt.target;" \
"while(element)" \
"{"
"element = element.parentElement;" \ //依次得到父节点
"if(element)" \
"{" \ //判断当前元素是否带滚动条
"element.scrollBy(0,2);"
"if(element.scrollTop > 0)"
"{"
"element.scrollBy(0,-2);"
"window.scrollElt = element;" //保存在一个全局变量里,以便在后面设置滚动条位置。
"break; "
"}"
"else {"
"element.scrollTo(0,0);}"
"}" \
"}" \
"}" \
"}" \
" function f2(evt) {" \
"window.scrollElt = null;" \
"}" \
"document.addEventListener('mousedown',f,false);" \
"document.addEventListener('mouseup',f2,false);" \
);
有了带滚动条的元素对象,就能在Qt代码中的mouseMoveEvent() 中通过j调用脚本执行window.scrollElt.scrollBy()来设置滚动条位置了。
尝试过程:
- 原代码调用window.scrollBy()不起作用,查资料发现通过window执行的是全局的滚动条,如果滚动条刚好设在全局,会起作用,反之,如果滚动条设在了body内部的某个或者某些标签上,就不会起作用。
- 最开始单纯地通过在html中找到当前这个带滚动条的tag,然后调用它的scrollBy方法document.querySelector(‘.table-scroll’).scrollBy(0,%1); 或者 document.getElementById(‘table-scrollTo’).scrollBy(0,%1),发现只对当前这个html页面起作用,其他页面因为没有指定的标签Id,所以无效。
- 通过暴力地遍历整个html页面的div标签,然后通过visibility overflow-y scrollHeight clientHeight这些参数过滤出带滚动条的div。先不说性能,对于个别网页上看上去起作用,但是会发现,无论在哪个位置拖动鼠标,屏幕都会滚动,很明显,是因为没有定位到鼠标mousedown位置的元素。另外,有些div元素(或者其他元素)不能通过这几个条件判断出是否scrollbar,原因未知。所以这种思路也不行。
QString frameScrollCode = QStringLiteral("function frameScroll(){" \
"var divList = document.getElementsByTagName('div');" \
"var div;" \
"for(var index=0; index < divList.length; index++) {" \
"var bVisible = window.getComputedStyle(divList[index]).getPropertyValue('visibility');" \
"var isScroll =window.getComputedStyle(divList[index]).getPropertyValue('overflow-y');" \
"var height = window.getComputedStyle(divList[index]).getPropertyValue('height');" \
"var sHeight = divList[index].scrollHeight;" \
"var cHeight = divList[index].clientHeight;" \
"if((bVisible == 'visible') && (isScroll == 'auto' || isScroll=='scroll') && (sHeight>cHeight))" \
"{" \
"alert(divList[index]);" \
"divList[index].scrollBy(0,%1);" \
" break; " \
"}" \
"}" \
"}" \
"frameScroll();" \
).arg(iGapy);
- stackoverflow上有个方法,通过监听焦点事件,修改window.activeElement, 发现不起作用,focus和blur事件捕捉不到。但是发现‘mousedown’ 和‘mouseup’ 事件可以捕捉到,让问题有了新的转机。
function _dom_trackActiveElement(evt) {
if (evt && evt.target) {
document.activeElement = evt.target == document ? null : evt.target;
}
}
function _dom_trackActiveElementLost(evt) {
document.activeElement = null;
}
if (!document.activeElement) {
document.addEventListener("focus",_dom_trackActiveElement,true);
document.addEventListener("blur",_dom_trackActiveElementLost,true);
}
- 尝试了各种方法判断某个元素是否有滚动条均失败(不能cover到所有的case)。最后通过其他项目组做前端的同事建议,找到一种非常规的方法:https://www.feiniaomy.com/post/986.html, 先移动一下位置,再判断是否有偏移,如果偏移了,说明有滚动条,反之,没有。
"element.scrollBy(0,2);"
"if(element.scrollTop > 0)"
"{"
"element.scrollBy(0,-2);"
"window.scrollElt = element;" //保存在一个全局变量里,以便在后面设置滚动条位置。
"break; "
"}"
"else {"
"element.scrollTo(0,0);}"