预览滚动下滑不同步的问题

166 阅读4分钟

背景

同事在React项目中开发预览功能的时候,遇到了修改方案后,内容区滚动导致预览区的滚动卡顿的问题。帮忙看了下是一个跟React原理相关的问题,记录下来整体的排查记录。

预览图方案

先介绍一下同事预览图的方案。

功能

预览图的功能可见下图。用户在内容区滚动时,左侧预览区对应的视区也会随之滚动;用户在左侧拖拽时,右侧的内容区也会滚动。非视区部分会有蒙层遮罩。

image.png

方案

先说预览区。
预览区共分成四个DOM:底层渲染内容、非视区A、视区、非视区B。各自作用很明显,底层渲染页面内容,非视区用于遮罩渲染,视区按照屏幕比例透过底层内容。非视区和视区部分以absolute定位在在底层内容区之上,内部以flex布局,JS设置非视区A的高度,根据屏占比固定视区高度,非视区B自动占满剩余高度。
再说说事件处理。
两侧元素都设置为可滚动,并且监听滚动事件;内容区滚动时,设置非视区A的高度。通过计算视区部分是否超出屏幕,设置预览区的滚动距离,让视区保持在屏幕中。预览区类似,不赘述。

问题概述

在内容区滚动,预览区跟随滚动时,预览区滚动卡顿。

排查过程

JS卡顿的原因太多了,长任务、React hooks使用不当、事件监听频繁触发等等,不好排查,所以由表及里,从预览区开始排查。

  1. 由于整体滚动时是由非视区A的高度控制滚动,并且高度是由JS设置的,如果这里设置的不连续,滚动起来也会卡顿。所以优先打印非视区A的高度,观察打印结果。
    结果:在慢滚动的时候,打印结果连续,页面轻微卡顿;在快速滚动的时候,打印结果差别很大,页面卡顿明显。可以看到二者是相关的,可以从这里入手,排查问题。
  2. 通过代码可以发现,在内容区组件监听页面滚动,调用在预览区组件传入的回调函数,保存滚动距离到组件中的state中。在内容区调用函数的位置打印了下,观察调用频次是否过低。如果低的话需要排查什么原因导致,而找到的原因就是根因。
    结果:打印结果表示,滚动回调函数调用频次比非视区A的高度打印频次低很多。看起来这里的频次解决的话就可以解决滚动不连续的问题了。
  3. 通过代码可以看到,非视区A的高度是在useEffect中监听预览区保存的state中内容区的滚动高度,然后做计算设置预览图的高度的state。这里就就是所有逻辑了,看起来没有什么太大的问题。

问题根源

问题本身是在React的时间切片之中。React的时间切片为5ms,执行函数长短不同时间可能有差异,但是没有太大的性能问题的话,大致是稳定在这个数值。

image.png 任务调度优先级中,最高的就是浏览器的事件了。在滚动过程中,滚动事件一直触发,所以监听内容区滚动的日志打印频率很高;但是在useEffect中,是需要state变更,所有树处理完成需要条件到DOM时触发,所以调用频次相对低一些。
打印的问题解释完了,页面上的卡顿也好解释了。由于useEffect调用频次不够高,导致设置高度的state更新频次低,所以卡顿。这里增加一个滚动高度变化的动画,就可以会让滚动顺畅很多。

总结

问题本身并不是一个大问题,但是如果可以了解内部原理,解决问题也能多一个思路。随着自己工作时间的增长,现在也更倾向阅读源码来理解使用的代码库。相比于现在AI的文档总结能力,这也会是我们的一个优势所在吧。