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

这篇文章发布于 2018年02月2日,星期五,01:08,归类于 JS实例。 阅读 30438 次, 今日 3 次 8 条评论

 

一、先从需求说起

对于一个宽度不固定的滚动容器,如果里面内容已经滚动到了一定的高度,这个时候滚动容器的宽度发生变化,则里面内容的位置会进行重定位,一不留神就不知道刚才的位置是哪里了。

尤其是看小说这种非常考验眼力的场景。

例如下面的GIF截图演示:

位置发生明显变化

于是,就有需求。

当滚动容器尺寸发生变化的时候(如宽屏窄屏切换,或者默认尺寸变全屏时候),最上面元素位置要保持不变,这样视觉体验就很好。不会因为突然的尺寸变化而不知道刚才看到哪里了。

那么该如何实现呢?

需要借助JavaScript手动修正滚动位置。

二、滚动容器尺寸变化子元素视觉上位置不变的JS处理

我们先看实现后的截屏gif效果:

滚动位置特别处理后的gif截屏效果

您可以狠狠地点击这里:滚动容器尺寸变化时候最上方元素位置不变demo

滚动容器到合适位置,然后改变浏览器窗口尺寸,可以体验到微丝不动的非常友好的交互体验效果。

JS实现的原理

  1. 获得最靠近滚动容器上边缘的元素;
  2. 获得最靠近滚动容器上边缘的元素距离上边缘的距离;
  3. 当滚动容器尺寸改变后,获得之前最靠近上边缘元素现在距离上边缘的距离,根据前后的差值修正此时的scrollTop大小;

原理第一步是最难点,如何获得最靠近滚动容器上边缘的元素呢?

我这里使用的是document.elementsFromPoint方法,语法如下:

var elements = document.elementsFromPoint(x, y);

表示返回所有距离浏览器可视窗口x, y坐标的DOM元素集合。因此,elements是一个从最子元素开始,依次向上,一直到<body><html>元素的类数组集合。

在本例中,elements[0]就是我们需要的元素。

假设滚动容器元素的idbox,则使用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体验优化实例,没什么其他好展开的。

感谢阅读!

(本篇完)

分享到:


发表评论(目前8 条评论)

  1. jingguangyan说道:

    感谢作者的分享,这功能真的很细节,给了我很多启发。
    在学习的过程中发现了一个点:
    如果box视口内定位到的第一个元素内容很长,并且是内容中间区域在box视口顶部,那么在box宽度发生改变时,行数变化同样会导致内容移动。也许可以再通过 getClientRects完善一下。

    • 某某某说道:

      于现在,浏览器自动支持了。https://www.zhangxinxu.com/wordpress/2020/08/css-overflow-anchor/

  2. 前辈杠杠滴说道:

    刚好在找,刚好找到,刚刚好

    像前辈这样的男人,注定是不会单身的对不。

  3. half肥宅说道:

    这个功能真的很实用很人性化啊!超赞的!

  4. 仰慕前辈说道:

    您这样不断的探索新的东西,你把前端真心才发挥到极致。 佩服佩服前辈的精神

  5. 鎏金圣手火麒麟说道:

    感觉自己的思维真的不能被项目的兼容性所限制,还是应该尽可能多地将新技术用到项目中,起码可以优化高级浏览器的体验效果

  6. mengkun说道:

    强烈建议一些响应式前端框架的 wiki 文档加入这个!每次对照响应式效果时一调整浏览器大小就不知道滚动到哪去了~哈哈~

  7. webster说道:

    前端发挥到了极致啊,羡慕 佩服