使用 React 完成 Airbnb 的导航滚动条

项目笔记

最近在使用 React18 做 Airbnb 项目的时候看到首页选择的滚动条非常喜欢。想着就动手练习一下。
可以封装这个组件,以后其他地方也可以用到

大概预览图就是长这个样

实现的几个功能有

  • 内容超出了本身的宽度才显示按钮,如果未超出则隐藏
  • 需要动态根据内容按钮的宽度来实现滑动动画

注意事项

开始前需要注意

  • 动画效果是因为 transition 在作用,必须先理清楚
  • 改变的只是位置,而不是自己在控制动画

按钮滚动逻辑

如果要做到按钮超出内容区域显示,不超出隐藏的效果。(这里按照右按钮演示)
首先可以拿到滚动条内容的总长度,减去显示区域的长度,得到的就是可以滚动的长度

1
2
3
4
5
6
7
useEffect(() => {
const clientWidth = barRef.current.clientWidth; //控制条的总长度
const scrollWidth = barRef.current.scrollWidth; //控制条可以滚动的长度(总长+超出区域的长度)
const allowScroll = scrollWidth - clientWidth; // 控制条超出区域的长度
allowScrollRef.current = allowScroll; //持久化记录
setIsrightShow(allowScroll > 0);
}, []);

这里需要使用 ref 记录下 allowScroll 的长度,如果直接用 const 或 let,下次会因为重新渲染导致重置

1
const allowScrollRef = useRef(0);

要控制按钮的显示和隐藏。就得先设置一个 state,记录当前的状态。

1
2
const [isRightShow, setIsrightShow] = useState(false);
const [isLeftShow, setIsleftShow] = useState(false);

上核心代码硬菜

拿到移动的距离,如果想控制按钮向右移动,也就是 bar 向左滑动,移动的距离也就是
当前按钮的(等会要被移动走) +1 个按钮左上角 距离父元素的距离。直接用 API offsetLeft 获取

如果想控制按钮向左移动,也就是 bar 向右滑动,移动的距离也就是
当前按钮的(等会要被移动走) -1 个按钮左上角 距离父元素的距离。直接用 API offsetLeft 获取

最后控制按钮是否显示的逻辑:
如果是向右的按钮,需要保证 allowScrollRef.current 也就是控制条超出区域的长度需要大于移动的长度,不然就是还有可以移动的区域,还是得继续移动呢
如果是向左的按钮,只需要控制 moveDistance 移动的距离大于 0 即可,左侧就是有移动的区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function changeIndex(isLeft) {
const doIndex = isLeft ? currentIndex - 1 : currentIndex + 1; //判断按钮是向左还是向右
const moveDistance = barRef.current.children[doIndex].offsetLeft;

barRef.current.style.transform = `translate(-${moveDistance}px)`;
setCurrentIndex(doIndex);
//检查是否继续显示按钮
setIsrightShow(allowScrollRef.current > moveDistance);
setIsleftShow(moveDistance > 0);
}

return (
<ScrollWrapper>
{isLeftShow && <button onClick={e => changeIndex(true)}>点我向左</button>}
{isRightShow && <button onClick={e => changeIndex(false)}>点我向右</button>}

<div className="bar" ref={barRef}>
{props.children}
</div>
</ScrollWrapper>
);