这篇文章发布于 2018年02月2日,星期五,01:08,归类于 JS实例。 阅读 32497 次, 今日 4 次 8 条评论
by zhangxinxu from http://www.zhangxinxu.com/wordpress/?p=7351
本文可全文转载,但需要保留原作者和出处。
一、先从需求说起
对于一个宽度不固定的滚动容器,如果里面内容已经滚动到了一定的高度,这个时候滚动容器的宽度发生变化,则里面内容的位置会进行重定位,一不留神就不知道刚才的位置是哪里了。
尤其是看小说这种非常考验眼力的场景。
例如下面的GIF截图演示:
于是,就有需求。
当滚动容器尺寸发生变化的时候(如宽屏窄屏切换,或者默认尺寸变全屏时候),最上面元素位置要保持不变,这样视觉体验就很好。不会因为突然的尺寸变化而不知道刚才看到哪里了。
那么该如何实现呢?
需要借助JavaScript手动修正滚动位置。
二、滚动容器尺寸变化子元素视觉上位置不变的JS处理
我们先看实现后的截屏gif效果:
您可以狠狠地点击这里:滚动容器尺寸变化时候最上方元素位置不变demo
滚动容器到合适位置,然后改变浏览器窗口尺寸,可以体验到微丝不动的非常友好的交互体验效果。
JS实现的原理
- 获得最靠近滚动容器上边缘的元素;
- 获得最靠近滚动容器上边缘的元素距离上边缘的距离;
- 当滚动容器尺寸改变后,获得之前最靠近上边缘元素现在距离上边缘的距离,根据前后的差值修正此时的scrollTop大小;
原理第一步是最难点,如何获得最靠近滚动容器上边缘的元素呢?
我这里使用的是document.elementsFromPoint
方法,语法如下:
var elements = document.elementsFromPoint(x, y);
表示返回所有距离浏览器可视窗口x, y
坐标的DOM元素集合。因此,elements
是一个从最子元素开始,依次向上,一直到<body>
,<html>
元素的类数组集合。
在本例中,elements[0]
就是我们需要的元素。
假设滚动容器元素的id
是box
,则使用JavaScript代码表示就是:
var box = document.getElementById('box'); // x就取滚动容器水平中心点 var x = box.getBoundingClientRect().left + box.clientWidth / 2; // + 12为了让更靠下一点的元素作为边缘元素 var y = box.getBoundingClientRect().top + 12; // target就是我们获取的最接近滚动容器上边缘的元素 var target = document.elementsFromPoint(x, y)[0];
然后target
距离上边缘的距离也很好实现(容器scroll
事件时候执行):
var offsetTop = target.getBoundingClientRect().top - box.getBoundingClientRect().top
最后,根据滚动后的边缘距离进行滚动修正(窗体resize
事件时候执行):
var currentOffsetTop = box.getBoundingClientRect().top - target.getBoundingClientRect().top;
// 滚动修正,效果达成
box.scrollTop = box.scrollTop - currentOffsetTop - offsetTop;
更完整JS代码参见demo页面(就几十行),大家可以根据自己实际项目场景进行修改,这里不重复展示。
关于document.elementsFromPoint API的兼容性
根据MDN上的数据,document.elementsFromPoint
IE10+才支持,而且需要ms
私有前缀,因此,如果想要兼容IE浏览器,可以加一句:
if (!document.elementsFromPoint) { document.elementsFromPoint = document.msElementsFromPoint; }
由于本文实例属于体验增强的功能,因此,就算浏览器不支持也无伤大雅,能够让90+%浏览器有更好体验已经很棒了,因此是可以放心大胆使用的一个技术案例。
IE浏览器下更多细节
在IE浏览器下测试,resize
时候滚动内容有晃动,我猜测可能与边缘位置计算带小数有关,因此,offsetTop
的计算都是取整的,如下示意:
var currentOffsetTop = Math.round(box.getBoundingClientRect().top) - Math.round(target.getBoundingClientRect().top);
体验果然好了很多。
另外,在Chrome等浏览器下,target
的获取和偏移计算可以直接在window
resize
时候执行。但是IE浏览器不行,似乎IE浏览器先触发浏览器行为,再执行了resize
事件,所以,综合下来,还是建议最上边缘元素获取放在scroll
事件中处理。
三、结束语
一个JS体验优化实例,没什么其他好展开的。
感谢阅读!
本文为原创文章,会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,避免陈旧错误知识的误导,同时有更好的阅读体验。
本文地址:http://www.zhangxinxu.com/wordpress/?p=7351
(本篇完)
- CSSOM视图模式(CSSOM View Module)相关整理 (0.533)
- DOM基础小测27期答疑文字版-窗体滚动二三事 (0.533)
- 小tip: 子元素scroll父元素容器不跟随滚动JS实现 (0.318)
- CSS scroll-snap滚动事件停止及元素位置检测 (0.318)
- 如何实现页面刷新后不定位到之前的滚动位置? (0.318)
- 划词评论与Range开发若干经验分享 (0.266)
- 翻译 - 逐渐消失的Flash网站 (0.250)
- 有意思:textarea resize属性下纯CSS交互效果 (0.250)
- 小tips: 元素focus页面不滚动不定位的JS处理 (0.218)
- jQuery-马化腾产品设计与用户体验的一些技术实现 (0.166)
- CSS overscroll-behavior让滚动嵌套时父滚动不触发 (RANDOM - 0.051)
感谢作者的分享,这功能真的很细节,给了我很多启发。
在学习的过程中发现了一个点:
如果box视口内定位到的第一个元素内容很长,并且是内容中间区域在box视口顶部,那么在box宽度发生改变时,行数变化同样会导致内容移动。也许可以再通过 getClientRects完善一下。
于现在,浏览器自动支持了。https://www.zhangxinxu.com/wordpress/2020/08/css-overflow-anchor/
刚好在找,刚好找到,刚刚好
像前辈这样的男人,注定是不会单身的对不。
这个功能真的很实用很人性化啊!超赞的!
您这样不断的探索新的东西,你把前端真心才发挥到极致。 佩服佩服前辈的精神
感觉自己的思维真的不能被项目的兼容性所限制,还是应该尽可能多地将新技术用到项目中,起码可以优化高级浏览器的体验效果
强烈建议一些响应式前端框架的 wiki 文档加入这个!每次对照响应式效果时一调整浏览器大小就不知道滚动到哪去了~哈哈~
前端发挥到了极致啊,羡慕 佩服