You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ScrollView滚动时顶部按钮抖动问题:点击按钮跳转至指定位置异常

顶部按钮滚动抖动问题

点击顶部按钮后,按钮区域在滚动过程中出现轻微抖动,滚动体验不流畅(效果如下):

Top Btns are shaking whenever there's some scrolling

现有实现代码

const [sectionHeights, setSectionHeights] = useState([]);
const [aboveComponentHeight, setAboveComponentHeight] = useState(0);
const debouncedScrollToComponent = debounce((index, txt) => {
  setActiveButton(txt);
}, 100);

const scrollToComponent = (index, txt) => {
  if (scrollViewRef.current && sectionRefs.current[index]) {
    const buttonSectionHeight = sectionHeights
      .slice(0, index)
      .reduce((acc, height) => acc + height, 0);

    const offsetY = buttonSectionHeight + aboveComponentHeight;

    scrollViewRef.current.scrollTo({ y: offsetY, animated: true });
    debouncedScrollToComponent(index, txt);
  }
};

const onSectionLayout = (event, index) => {
  const { height } = event.nativeEvent.layout;
  setSectionHeights((prevHeights) => {
    const updatedHeights = [...prevHeights];
    updatedHeights[index] = height;
    return updatedHeights;
  });
};

const onAboveComponentLayout = (event) => {
  const { height } = event.nativeEvent.layout;
  console.log('aboveComponentHeight', height);
  setAboveComponentHeight(height);
};

<ScrollView
  ref={scrollViewRef}
  showsVerticalScrollIndicator={false}
  contentContainerStyle={{ paddingBottom: 60 }}
  stickyHeaderIndices={[1]}
>
  <View ref={aboveComponentRef} onLayout={onAboveComponentLayout}>
    //other code
  </View>
  {/* Section Buttons */}
  <View>
    <View style={styles.btnContainer}>
      {Btns.map((txt, index) => (
        <TouchableOpacity
          activeOpacity={0.7}
          onPress={() => scrollToComponent(index, txt)}
          key={index}
          style={txt == activeButton ? styles.activeBtn : styles.inactiveBtn}
        >
          <Text style={activeButton == txt ? styles.activeText : styles.inactiveText}>
            {txt}
          </Text>
        </TouchableOpacity>
      ))}
    </View>
  </View>
  {/* Suppliers */}
  <View
    style={styles.supplierContainer}
    ref={(ref) => (sectionRefs.current[0] = ref)}
    onLayout={(event) => onSectionLayout(event, 0)}
  >
    //other code
  </View>
  {/* Description */}
  <View
    ref={(ref) => (sectionRefs.current[1] = ref)}
    style={{ padding: 16 }}
    onLayout={(event) => onSectionLayout(event, 1)}
  >
    //other code
  </View>

  {/* Product Specs */}
  <View
    ref={(ref) => (sectionRefs.current[2] = ref)}
    style={{ paddingHorizontal: 16 }}
    onLayout={(event) => onSectionLayout(event, 2)}
  >
    //Other code
  </View>
  <Divider bgColor={'#EFEFEF'} my={3} />
  {/* Reviews */}
  <View style={{ paddingHorizontal: 16 }}>
    //other code
  </View>
</ScrollView>;

已尝试使用setTimeout和防抖函数,但问题仍未解决,核心是点击按钮后按钮区域抖动,滚动不流畅。

解决方案

1. 修正滚动偏移计算,排除粘性头部高度

设置stickyHeaderIndices={[1]}后,按钮区域会固定在顶部,但原代码计算滚动偏移时未排除其自身高度,导致滚动目标位置偏差,触发额外滚动调整引发抖动。修改scrollToComponent函数:

const scrollToComponent = (index, txt) => {
  if (scrollViewRef.current && sectionRefs.current[index]) {
    // 提前通过ref获取按钮区域的高度
    const headerHeight = btnContainerRef.current?.getBoundingClientRect().height || 0;
    const buttonSectionHeight = sectionHeights
      .slice(0, index)
      .reduce((acc, height) => acc + height, 0);

    // 偏移量减去粘性头部高度,适配固定头部的滚动逻辑
    const offsetY = buttonSectionHeight + aboveComponentHeight - headerHeight;

    scrollViewRef.current.scrollTo({ y: offsetY, animated: true });
    // 移除防抖,直接设置激活状态,避免状态延迟导致视觉不一致
    setActiveButton(txt);
  }
};

2. 避免布局事件重复触发状态更新

onSectionLayout和onAboveComponentLayout可能多次触发,导致状态频繁更新影响滚动稳定性,添加高度变化判断:

const onSectionLayout = (event, index) => {
  const { height } = event.nativeEvent.layout;
  setSectionHeights((prevHeights) => {
    if (prevHeights[index] === height) return prevHeights;
    const updatedHeights = [...prevHeights];
    updatedHeights[index] = height;
    return updatedHeights;
  });
};

const onAboveComponentLayout = (event) => {
  const { height } = event.nativeEvent.layout;
  if (height === aboveComponentHeight) return;
  console.log('aboveComponentHeight', height);
  setAboveComponentHeight(height);
};

3. 直接使用元素布局信息计算滚动位置

手动累加高度易出错,改用measure方法获取目标元素的实际偏移:

const scrollToComponent = (index, txt) => {
  if (scrollViewRef.current && sectionRefs.current[index]) {
    sectionRefs.current[index].measure((x, y, width, height, pageX, pageY) => {
      const headerHeight = btnContainerRef.current?.getBoundingClientRect().height || 0;
      scrollViewRef.current.scrollTo({ y: pageY - headerHeight, animated: true });
      setActiveButton(txt);
    });
  }
};

4. 确保按钮样式切换不改变元素尺寸

按钮激活样式若改变宽高,会引发布局重排导致抖动,统一按钮宽高:

const styles = StyleSheet.create({
  btnContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#fff',
  },
  activeBtn: {
    paddingVertical: 10,
    paddingHorizontal: 15,
    borderBottomWidth: 2,
    borderBottomColor: '#007AFF',
    width: 80,
    alignItems: 'center',
  },
  inactiveBtn: {
    paddingVertical: 10,
    paddingHorizontal: 15,
    borderBottomWidth: 2,
    borderBottomColor: 'transparent',
    width: 80,
    alignItems: 'center',
  },
  // ...其他样式
});

内容的提问来源于stack exchange,提问作者Salman Khalid

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 06:51:58