小tips: 滚动容器尺寸变化子元素视觉上位置不变JS实现

2018-02-03 10:31:50来源:http://www.zhangxinxu.com/wordpress/2018/02/container-scroll作者:张鑫旭人点击

分享

这篇文章发布于 2018年02月2日,星期五,01:08,归类于js实例。 阅读 16 次, 今日 16 次


byzhangxinxu 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


(本篇完)


微信扫一扫

第七城市微信公众平台