使用 OverlayScrollbars 优雅的自定义滚动条

三葉Leaves Author

官方网站:OverlayScrollbars

前言

我对 windows 系统中的浏览器滚动条样式一直很不满意,它巨丑,但我忍了。直到我在某天开发网站时候遇到了一个问题:

当网站在一个内容较多(会自动触发滚动条)和一个内容较少(没有滚动条)的两个页面之间切换的时候,由于滚动条占据的那十几像素的宽度的问题,导致两个页面的宽度不一样,因此出现了严重的整体布局偏移。这个问题在单页应用的现代网站,尤其是还使用了 famer motion 之类库来追求极致 C 端体验的网站下是难以忍受的。

我尝试了各种原生解决方案,均无法很好的解决这个问题,亦或者会产生新的问题。最终我选择使用 OverlayScrollbars,它完全抛弃了原生滚动条,而是利用 JS 自己实现了。

这个库除了能解决我上面提到的若干问题,而且还意外的十分强大,能让你自定义诸多内容。并且框架支持和优化也到位,适用于现代化网站。

安装和使用

我以 Nextjs 为例,其他框架请见官网 OverlayScrollbars

安装

1
2
pnpm install overlayscrollbars-react
pnpm install overlayscrollbars

创建 provider

我将其放在 src\providers\OverlayScrollbarsProvider.tsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
"use client";

import React, { useEffect, useRef } from "react";
import {
OverlayScrollbars,
ScrollbarsHidingPlugin,
SizeObserverPlugin,
ClickScrollPlugin,
} from "overlayscrollbars";

export default function OverlayScrollbarsProvider({
children,
}: {
children: React.ReactNode;
}) {
const osInstanceRef = useRef<ReturnType<typeof OverlayScrollbars> | null>(null);
const pluginsRegisteredRef = useRef(false);

useEffect(() => {
if (!pluginsRegisteredRef.current) {
OverlayScrollbars.plugin([
ScrollbarsHidingPlugin,
SizeObserverPlugin,
ClickScrollPlugin,
]);
pluginsRegisteredRef.current = true;
}

const resolveTheme = () =>
document.documentElement.classList.contains("dark")
? "os-theme-dark"
: "os-theme-light";

osInstanceRef.current = OverlayScrollbars(
{ target: document.body },
{
scrollbars: {
theme: resolveTheme(),
autoHide: "leave",
autoHideDelay: 100,
clickScroll: true,
},
},
);

const observer = new MutationObserver(() => {
const theme = resolveTheme();
osInstanceRef.current?.options({ scrollbars: { theme } });
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});

return () => {
observer.disconnect();
osInstanceRef.current?.destroy();
osInstanceRef.current = null;
};
}, []);

return <>{children}</>;
}

导入样式和 provider

src\app\layout.tsx

1
2
import "overlayscrollbars/styles/overlayscrollbars.css";
import OverlayScrollbarsProvider from "@/providers/OverlayScrollbarsProvider";

overlayscrollbars.css 要注意放在 global.css 上面

1
2
3
4
5
6
7
8
9
10
11
      <html lang="en" data-overlayscrollbars-initialize>
      ...
      <body
data-overlayscrollbars-initialize
className={`${geistSans.variable} ${geistMono.variable} relative antialiased`}
>
<OverlayScrollbarsProvider>
...
{children}
...
    </OverlayScrollbarsProvider>

比如我的 layout.tsx ,完整如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "overlayscrollbars/styles/overlayscrollbars.css";
import "./globals.css";
import QueryProvider from "@/providers/QueryProvider";
import OverlayScrollbarsProvider from "@/providers/OverlayScrollbarsProvider";
import { Toaster } from "@/components/ui/sonner";
import { ViewTransitions } from "next-view-transitions";

const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});

const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});

export const metadata: Metadata = {
title: "Mypapers",
description: "基于区块链技术的学术论文共享和交易平台",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<ViewTransitions>
<html lang="en" data-overlayscrollbars-initialize>
<body
data-overlayscrollbars-initialize
className={`${geistSans.variable} ${geistMono.variable} relative antialiased`}
>
<QueryProvider>
<OverlayScrollbarsProvider>
{children}
<Toaster position="top-center" richColors />
</OverlayScrollbarsProvider>
</QueryProvider>
</body>
</html>
</ViewTransitions>
);
}

自定义样式

最后就是改样式了,官方网站有比较详细的说明:

OverlayScrollbars

我这里展示一下我的:

src\app\global.css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/* OverlayScrollbars: 缩短滚动条滑块(thumb)长度 */
.os-scrollbar-horizontal .os-scrollbar-handle {
min-width: 4rem;
max-width: 4rem;
}

.os-scrollbar-vertical .os-scrollbar-handle {
min-height: 4rem;
max-height: 4rem;
}

/* OverlayScrollbars: reduce thickness and adjust opacity */
.os-scrollbar {
--os-size: 10px;
--os-track-border-radius: 9999px;
--os-handle-border-radius: 9999px;
/* max-height: 16px; */
}

.os-theme-light.os-scrollbar {
--os-track-bg: rgba(0, 0, 0, 0.04);
--os-track-bg-hover: rgba(0, 0, 0, 0.06);
--os-track-bg-active: rgba(0, 0, 0, 0.08);
--os-handle-bg: rgba(0, 0, 0, 0.42);
--os-handle-bg-hover: rgba(0, 0, 0, 0.5);
--os-handle-bg-active: rgba(0, 0, 0, 0.56);
}

.os-theme-dark.os-scrollbar {
--os-track-bg: rgba(255, 255, 255, 0.06);
--os-track-bg-hover: rgba(255, 255, 255, 0.08);
--os-track-bg-active: rgba(255, 255, 255, 0.1);
--os-handle-bg: rgba(255, 255, 255, 0.44);
--os-handle-bg-hover: rgba(255, 255, 255, 0.52);
--os-handle-bg-active: rgba(255, 255, 255, 0.6);
}
  • 标题: 使用 OverlayScrollbars 优雅的自定义滚动条
  • 作者: 三葉Leaves
  • 创建于 : 2025-10-11 00:00:00
  • 更新于 : 2026-03-16 12:05:06
  • 链接: https://blog.oksanye.com/fdaddaa348df/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论
目录
使用 OverlayScrollbars 优雅的自定义滚动条