WinForms 彻底隐藏 滚动条的终极解决方案
Winfrom怎么隐藏原生滚动条!!!
Winfrom怎么隐藏原生滚动条!!!
Winfrom怎么隐藏原生滚动条!!!
前言
在开发 WinForms 应用时,我们经常需要使用 Panel 的 AutoScroll 功能来处理超出显示区域的内容。但是,系统默认的滚动条样式往往与我们的 UI 设计格格不入,特别是在使用深色主题时。本文将分享一个彻底隐藏滚动条同时保留滚动功能的完整解决方案,以及我在实现过程中踩过的所有坑。
一、实现方案
1.1 需求分析
我们的目标是:
- ✅ 完全隐藏 Panel 的滚动条(包括初始状态和滚动时)
- ✅ 保留
AutoScroll功能(通过鼠标滚轮滚动) - ✅ 自定义滚动指示器(可选,使用渐变阴影提示滚动状态)
1.2 核心技术原理
WinForms 的滚动条本质上是窗口样式的一部分,要彻底隐藏它,需要从两个层面入手:
第一层:窗口创建时移除滚动条样式
通过重写 CreateParams 属性,在窗口创建时就移除滚动条的样式标志:
csharp
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style &= ~0x00200000; // WS_VSCROLL (垂直滚动条)
cp.Style &= ~0x00100000; // WS_HSCROLL (水平滚动条)
return cp;
}
}
Windows 样式标志说明:
WS_VSCROLL (0x00200000):垂直滚动条样式WS_HSCROLL (0x00100000):水平滚动条样式&= ~操作:按位清除指定标志
第二层:拦截非客户区计算消息
重写 WndProc 方法,拦截 WM_NCCALCSIZE 消息,阻止系统为滚动条分配显示空间:
csharp
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCCALCSIZE) // 0x0083
{
// 返回 IntPtr.Zero 告诉系统不需要为滚动条预留空间
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
为什么需要第二层防护?
即使在窗口创建时移除了滚动条样式,Windows 系统在运行时仍可能重新计算非客户区(包括滚动条)的大小。通过拦截 WM_NCCALCSIZE 消息,我们可以彻底阻止滚动条占用任何显示区域。
1.3 完整实现代码
csharp
using System;
using System.Windows.Forms;
/// <summary>
/// 无滚动条的 Panel 控件
/// 完全隐藏滚动条,但保留 AutoScroll 功能
/// </summary>
public class NoScrollBarPanel : Panel
{
// Windows 消息常量
private const int WM_NCCALCSIZE = 0x0083; // 非客户区大小计算
private const int WM_HSCROLL = 0x0114; // 水平滚动消息
private const int WM_VSCROLL = 0x0115; // 垂直滚动消息
/// <summary>
/// 重写创建参数,移除滚动条样式
/// </summary>
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
// 移除垂直滚动条样式
cp.Style &= ~0x00200000; // WS_VSCROLL
// 移除水平滚动条样式
cp.Style &= ~0x00100000; // WS_HSCROLL
return cp;
}
}
/// <summary>
/// 重写窗口消息处理,拦截非客户区计算
/// </summary>
protected override void WndProc(ref Message m)
{
// 拦截非客户区大小计算消息
if (m.Msg == WM_NCCALCSIZE)
{
// 返回 0 表示不需要为滚动条预留空间
m.Result = IntPtr.Zero;
return;
}
// 其他消息正常处理
base.WndProc(ref m);
}
}
1.4 使用方法
方式一:直接使用自定义类
csharp
// 创建无滚动条的 Panel
NoScrollBarPanel panel = new NoScrollBarPanel();
panel.AutoScroll = true; // 启用自动滚动(通过鼠标滚轮)
panel.BackColor = Color.FromArgb(44, 44, 44); // 深色背景
panel.Dock = DockStyle.Fill;
// 添加到窗体
this.Controls.Add(panel);
// 添加子控件
for (int i = 0; i < 50; i++)
{
Button btn = new Button();
btn.Text = $"按钮 {i + 1}";
btn.Location = new Point(10, i * 40);
panel.Controls.Add(btn);
}
方式二:在 Designer 中使用
- 编译包含
NoScrollBarPanel的项目 - 在工具箱中会自动出现该控件
- 拖拽到窗体上使用
- 设置
AutoScroll = true
1.5 可选增强:自定义滚动指示器
由于滚动条隐藏了,用户可能不知道内容可以滚动。可以添加渐变阴影作为滚动指示器:
csharp
// 创建顶部指示器
Panel scrollIndicatorTop = new Panel();
scrollIndicatorTop.Height = 30;
scrollIndicatorTop.Dock = DockStyle.Top;
scrollIndicatorTop.BackColor = Color.Transparent;
scrollIndicatorTop.Paint += (s, e) =>
{
using (var brush = new System.Drawing.Drawing2D.LinearGradientBrush(
e.ClipRectangle,
Color.FromArgb(150, 0, 0, 0), // 顶部深色
Color.FromArgb(0, 0, 0, 0), // 底部透明
System.Drawing.Drawing2D.LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
};
// 创建底部指示器
Panel scrollIndicatorBottom = new Panel();
scrollIndicatorBottom.Height = 30;
scrollIndicatorBottom.Dock = DockStyle.Bottom;
scrollIndicatorBottom.BackColor = Color.Transparent;
scrollIndicatorBottom.Paint += (s, e) =>
{
using (var brush = new System.Drawing.Drawing2D.LinearGradientBrush(
e.ClipRectangle,
Color.FromArgb(0, 0, 0, 0), // 顶部透明
Color.FromArgb(150, 0, 0, 0), // 底部深色
System.Drawing.Drawing2D.LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
};
// 根据滚动位置动态显示/隐藏指示器
panel.Scroll += (s, e) =>
{
scrollIndicatorTop.Visible = panel.VerticalScroll.Value > 0;
int maxScroll = panel.VerticalScroll.Maximum -
panel.VerticalScroll.LargeChange + 1;
scrollIndicatorBottom.Visible = panel.VerticalScroll.Value < maxScroll;
};
二、踩坑指南
坑 #1:使用 ShowScrollBar API
错误做法:
csharp
[DllImport("user32.dll")]
private static extern int ShowScrollBar(IntPtr hWnd, int wBar, int bShow);
private const int SB_BOTH = 3;
// 尝试隐藏滚动条
ShowScrollBar(panel.Handle, SB_BOTH, 0);
问题:
- ❌ 初始状态可以隐藏滚动条
- ❌ 但是一滚动,滚动条就重新出现了
- ❌ 即使在
Scroll事件中重复调用也无法彻底解决
原因:
ShowScrollBar 只是隐藏了滚动条的显示 ,但没有移除滚动条的样式。当系统处理滚动消息时,会根据窗口样式重新绘制滚动条。
教训:
不要使用
ShowScrollBarAPI 来隐藏滚动条,它治标不治本!
坑 #2:只重写 CreateParams
不完整做法:
csharp
public class NoScrollBarPanel : Panel
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style &= ~0x00200000; // WS_VSCROLL
cp.Style &= ~0x00100000; // WS_HSCROLL
return cp;
}
}
}
问题:
- ✅ 可以阻止初始创建滚动条
- ❌ 但在某些情况下,系统仍可能在运行时重新添加滚动条样式
- ❌ 滚动条可能会"闪现"然后消失
原因:
Windows 系统在处理窗口大小变化、滚动内容变化时,会重新计算非客户区(包括滚动条)的大小。如果不拦截 WM_NCCALCSIZE 消息,系统仍可能为滚动条预留空间。
教训:
只重写
CreateParams是不够的,必须配合WndProc拦截消息!
坑 #3:调用时机问题
错误做法:
csharp
// 在构造函数中立即调用
toolboxPanel = new Panel();
toolboxPanel.AutoScroll = true;
ShowScrollBar(toolboxPanel.Handle, SB_BOTH, 0); // ❌ 可能无效
问题:
- 控件还没有完全创建和显示
Handle可能还没有分配- 调用可能无效或抛出异常
正确做法:
csharp
// 使用自定义类,在窗口创建时就设置好样式
toolboxPanel = new NoScrollBarPanel();
toolboxPanel.AutoScroll = true;
// 不需要额外的 API 调用
教训:
不要依赖运行时调用 API,在窗口创建时就设置好样式!
坑 #4:忘记保留滚动功能
错误理解:
"隐藏滚动条 = 禁用滚动功能"
正确理解:
我们只是隐藏了滚动条的视觉显示 ,但滚动功能仍然保留。
验证方法:
csharp
NoScrollBarPanel panel = new NoScrollBarPanel();
panel.AutoScroll = true; // ✅ 必须设置为 true
// 滚动功能验证
Console.WriteLine($"VerticalScroll.Visible: {panel.VerticalScroll.Visible}"); // true
Console.WriteLine($"VerticalScroll.Maximum: {panel.VerticalScroll.Maximum}"); // 有值
// 可以通过鼠标滚轮滚动 ✅
// 可以通过代码滚动 ✅
panel.AutoScrollPosition = new Point(0, 100);
教训:
AutoScroll = true必须保留,只是滚动条不可见而已!
坑 #5:子控件鼠标事件被拦截
问题场景:
在无滚动条的 Panel 中添加了很多子控件,鼠标滚轮在子控件上时无法滚动。
原因:
子控件捕获了鼠标滚轮事件,没有传递给父容器。
解决方案:
csharp
public class NoScrollBarPanel : Panel
{
// ... 前面的代码 ...
/// <summary>
/// 确保子控件上的滚轮事件也能滚动面板
/// </summary>
protected override void OnMouseWheel(MouseEventArgs e)
{
// 如果鼠标在子控件上,手动处理滚动
if (this.AutoScroll)
{
int newValue = this.VerticalScroll.Value - e.Delta;
if (newValue < 0)
newValue = 0;
if (newValue > this.VerticalScroll.Maximum)
newValue = this.VerticalScroll.Maximum;
this.VerticalScroll.Value = newValue;
}
base.OnMouseWheel(e);
}
}
教训:
如果发现滚动不灵敏,检查是否需要重写
OnMouseWheel!
三、可直接使用的工具类
3.1 基础版:NoScrollBarPanel
csharp
using System;
using System.Windows.Forms;
namespace CustomControls
{
/// <summary>
/// 无滚动条的 Panel 控件(基础版)
/// 完全隐藏滚动条,但保留 AutoScroll 功能
/// </summary>
public class NoScrollBarPanel : Panel
{
private const int WM_NCCALCSIZE = 0x0083;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style &= ~0x00200000; // WS_VSCROLL
cp.Style &= ~0x00100000; // WS_HSCROLL
return cp;
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCCALCSIZE)
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
}
}
使用示例:
csharp
NoScrollBarPanel panel = new NoScrollBarPanel
{
AutoScroll = true,
Dock = DockStyle.Fill,
BackColor = Color.FromArgb(44, 44, 44)
};
this.Controls.Add(panel);
3.2 增强版:NoScrollBarPanelEx(带滚动指示器)
csharp
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace CustomControls
{
/// <summary>
/// 无滚动条的 Panel 控件(增强版)
/// 隐藏滚动条,但提供渐变阴影作为滚动指示器
/// </summary>
public class NoScrollBarPanelEx : Panel
{
private const int WM_NCCALCSIZE = 0x0083;
private Panel topIndicator;
private Panel bottomIndicator;
private bool showScrollIndicators = true;
/// <summary>
/// 是否显示滚动指示器
/// </summary>
public bool ShowScrollIndicators
{
get => showScrollIndicators;
set
{
showScrollIndicators = value;
if (topIndicator != null)
topIndicator.Visible = value;
if (bottomIndicator != null)
bottomIndicator.Visible = value;
}
}
/// <summary>
/// 滚动指示器高度(像素)
/// </summary>
public int IndicatorHeight { get; set; } = 30;
/// <summary>
/// 滚动指示器颜色(使用半透明黑色)
/// </summary>
public Color IndicatorColor { get; set; } = Color.FromArgb(150, 0, 0, 0);
public NoScrollBarPanelEx()
{
InitializeIndicators();
}
private void InitializeIndicators()
{
// 顶部指示器
topIndicator = new Panel
{
Height = IndicatorHeight,
Dock = DockStyle.Top,
BackColor = Color.Transparent,
Visible = false
};
topIndicator.Paint += TopIndicator_Paint;
this.Controls.Add(topIndicator);
topIndicator.BringToFront();
// 底部指示器
bottomIndicator = new Panel
{
Height = IndicatorHeight,
Dock = DockStyle.Bottom,
BackColor = Color.Transparent,
Visible = true
};
bottomIndicator.Paint += BottomIndicator_Paint;
this.Controls.Add(bottomIndicator);
bottomIndicator.BringToFront();
// 监听滚动事件
this.Scroll += NoScrollBarPanelEx_Scroll;
}
private void TopIndicator_Paint(object sender, PaintEventArgs e)
{
if (!showScrollIndicators) return;
using (var brush = new LinearGradientBrush(
e.ClipRectangle,
IndicatorColor,
Color.FromArgb(0, IndicatorColor),
LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
private void BottomIndicator_Paint(object sender, PaintEventArgs e)
{
if (!showScrollIndicators) return;
using (var brush = new LinearGradientBrush(
e.ClipRectangle,
Color.FromArgb(0, IndicatorColor),
IndicatorColor,
LinearGradientMode.Vertical))
{
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
private void NoScrollBarPanelEx_Scroll(object sender, ScrollEventArgs e)
{
UpdateIndicators();
}
private void UpdateIndicators()
{
if (!showScrollIndicators || topIndicator == null || bottomIndicator == null)
return;
if (this.VerticalScroll.Visible)
{
// 不在顶部时显示顶部指示器
topIndicator.Visible = this.VerticalScroll.Value > 0;
// 不在底部时显示底部指示器
int maxScroll = this.VerticalScroll.Maximum -
this.VerticalScroll.LargeChange + 1;
bottomIndicator.Visible = this.VerticalScroll.Value < maxScroll;
}
else
{
topIndicator.Visible = false;
bottomIndicator.Visible = false;
}
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style &= ~0x00200000; // WS_VSCROLL
cp.Style &= ~0x00100000; // WS_HSCROLL
return cp;
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCCALCSIZE)
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
// 控件添加后更新指示器
this.BeginInvoke(new Action(UpdateIndicators));
}
}
}
使用示例:
csharp
NoScrollBarPanelEx panel = new NoScrollBarPanelEx
{
AutoScroll = true,
Dock = DockStyle.Fill,
BackColor = Color.FromArgb(44, 44, 44),
ShowScrollIndicators = true,
IndicatorHeight = 30,
IndicatorColor = Color.FromArgb(150, 0, 0, 0)
};
this.Controls.Add(panel);
// 添加大量子控件测试
for (int i = 0; i < 50; i++)
{
Button btn = new Button
{
Text = $"按钮 {i + 1}",
Width = 180,
Height = 35,
Location = new Point(10, i * 40 + 10)
};
panel.Controls.Add(btn);
}
3.3 终极版:SmartScrollPanel(智能滚动面板)
csharp
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
namespace CustomControls
{
/// <summary>
/// 智能滚动面板(终极版)
/// 集成所有功能:隐藏滚动条、滚动指示器、平滑滚动
/// </summary>
public class SmartScrollPanel : Panel
{
private const int WM_NCCALCSIZE = 0x0083;
private const int WM_MOUSEWHEEL = 0x020A;
private Panel topIndicator;
private Panel bottomIndicator;
private Timer smoothScrollTimer;
private int targetScrollValue;
private int currentScrollValue;
/// <summary>
/// 是否启用平滑滚动
/// </summary>
public bool SmoothScrollEnabled { get; set; } = true;
/// <summary>
/// 平滑滚动速度(1-10,数值越大越快)
/// </summary>
public int SmoothScrollSpeed { get; set; } = 5;
/// <summary>
/// 是否显示滚动指示器
/// </summary>
public bool ShowScrollIndicators { get; set; } = true;
/// <summary>
/// 滚动指示器高度
/// </summary>
public int IndicatorHeight { get; set; } = 30;
/// <summary>
/// 滚动指示器颜色
/// </summary>
public Color IndicatorColor { get; set; } = Color.FromArgb(150, 0, 0, 0);
public SmartScrollPanel()
{
InitializeIndicators();
InitializeSmoothScroll();
}
private void InitializeIndicators()
{
topIndicator = new Panel
{
Height = IndicatorHeight,
Dock = DockStyle.Top,
BackColor = Color.Transparent,
Visible = false
};
topIndicator.Paint += TopIndicator_Paint;
this.Controls.Add(topIndicator);
topIndicator.BringToFront();
bottomIndicator = new Panel
{
Height = IndicatorHeight,
Dock = DockStyle.Bottom,
BackColor = Color.Transparent,
Visible = true
};
bottomIndicator.Paint += BottomIndicator_Paint;
this.Controls.Add(bottomIndicator);
bottomIndicator.BringToFront();
this.Scroll += SmartScrollPanel_Scroll;
}
private void InitializeSmoothScroll()
{
smoothScrollTimer = new Timer { Interval = 16 }; // 约60fps
smoothScrollTimer.Tick += SmoothScrollTimer_Tick;
}
private void TopIndicator_Paint(object sender, PaintEventArgs e)
{
if (!ShowScrollIndicators) return;
using (var brush = new LinearGradientBrush(
e.ClipRectangle,
IndicatorColor,
Color.FromArgb(0, IndicatorColor),
LinearGradientMode.Vertical))
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
private void BottomIndicator_Paint(object sender, PaintEventArgs e)
{
if (!ShowScrollIndicators) return;
using (var brush = new LinearGradientBrush(
e.ClipRectangle,
Color.FromArgb(0, IndicatorColor),
IndicatorColor,
LinearGradientMode.Vertical))
{
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.FillRectangle(brush, e.ClipRectangle);
}
}
private void SmartScrollPanel_Scroll(object sender, ScrollEventArgs e)
{
UpdateIndicators();
}
private void UpdateIndicators()
{
if (!ShowScrollIndicators || topIndicator == null || bottomIndicator == null)
return;
if (this.VerticalScroll.Visible)
{
topIndicator.Visible = this.VerticalScroll.Value > 0;
int maxScroll = this.VerticalScroll.Maximum -
this.VerticalScroll.LargeChange + 1;
bottomIndicator.Visible = this.VerticalScroll.Value < maxScroll;
}
else
{
topIndicator.Visible = false;
bottomIndicator.Visible = false;
}
}
protected override void OnMouseWheel(MouseEventArgs e)
{
if (SmoothScrollEnabled && this.AutoScroll)
{
// 计算目标滚动位置
targetScrollValue = this.VerticalScroll.Value - e.Delta;
// 限制在有效范围内
if (targetScrollValue < 0)
targetScrollValue = 0;
int maxScroll = this.VerticalScroll.Maximum -
this.VerticalScroll.LargeChange + 1;
if (targetScrollValue > maxScroll)
targetScrollValue = maxScroll;
// 启动平滑滚动
if (!smoothScrollTimer.Enabled)
{
currentScrollValue = this.VerticalScroll.Value;
smoothScrollTimer.Start();
}
}
else
{
base.OnMouseWheel(e);
}
}
private void SmoothScrollTimer_Tick(object sender, EventArgs e)
{
int diff = targetScrollValue - currentScrollValue;
if (Math.Abs(diff) < 2)
{
// 到达目标位置
currentScrollValue = targetScrollValue;
smoothScrollTimer.Stop();
}
else
{
// 缓动移动
currentScrollValue += diff / SmoothScrollSpeed;
}
this.VerticalScroll.Value = currentScrollValue;
UpdateIndicators();
}
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style &= ~0x00200000; // WS_VSCROLL
cp.Style &= ~0x00100000; // WS_HSCROLL
return cp;
}
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCCALCSIZE)
{
m.Result = IntPtr.Zero;
return;
}
base.WndProc(ref m);
}
protected override void OnControlAdded(ControlEventArgs e)
{
base.OnControlAdded(e);
this.BeginInvoke(new Action(UpdateIndicators));
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
smoothScrollTimer?.Dispose();
}
base.Dispose(disposing);
}
}
}
使用示例:
csharp
SmartScrollPanel panel = new SmartScrollPanel
{
AutoScroll = true,
Dock = DockStyle.Fill,
BackColor = Color.FromArgb(44, 44, 44),
SmoothScrollEnabled = true,
SmoothScrollSpeed = 5,
ShowScrollIndicators = true,
IndicatorHeight = 30
};
this.Controls.Add(panel);
四、性能优化建议
4.1 避免频繁重绘
csharp
// 添加大量控件时,暂停布局
panel.SuspendLayout();
for (int i = 0; i < 1000; i++)
{
Button btn = new Button { /* ... */ };
panel.Controls.Add(btn);
}
panel.ResumeLayout();
4.2 启用双缓冲
csharp
public class NoScrollBarPanel : Panel
{
public NoScrollBarPanel()
{
this.DoubleBuffered = true; // 减少闪烁
this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
}
}
4.3 虚拟化长列表
如果需要显示成百上千个项目,考虑使用虚拟化技术,只渲染可见区域的控件。
五、常见问题 FAQ
Q1:为什么鼠标滚轮不起作用?
A: 检查以下几点:
- 确认
AutoScroll = true - 确认内容高度超过了 Panel 高度
- 尝试重写
OnMouseWheel方法手动处理
Q2:可以同时隐藏水平和垂直滚动条吗?
A: 可以,代码中已经同时移除了两个样式标志:
csharp
cp.Style &= ~0x00200000; // 垂直
cp.Style &= ~0x00100000; // 水平
Q3:在 Designer 中看不到自定义控件?
A: 确保:
- 项目编译成功
- 重新生成解决方案
- 关闭并重新打开设计器
- 查看工具箱是否需要手动添加
Q4:滚动指示器位置不对?
A: 使用 BringToFront() 确保指示器在最上层:
csharp
topIndicator.BringToFront();
bottomIndicator.BringToFront();
Q5:如何禁用滚动功能?
A: 设置 AutoScroll = false 即可:
csharp
panel.AutoScroll = false;
六、总结
本文介绍了在 WinForms 中彻底隐藏 Panel 滚动条的完整解决方案:
✅ 核心要点
- 双重防护机制 :
CreateParams+WndProc - 保留滚动功能:只隐藏视觉显示,不影响功能
- 自定义指示器:提供更好的用户体验
✅ 避免的坑
- 不要使用
ShowScrollBarAPI - 不要只重写
CreateParams - 注意调用时机和子控件事件
✅ 三个工具类
- NoScrollBarPanel:基础版,简单隐藏滚动条
- NoScrollBarPanelEx:增强版,带滚动指示器
- SmartScrollPanel:终极版,平滑滚动 + 指示器
📦 源码下载
🎯 适用场景
- 自定义 UI 设计(深色主题、扁平化设计)
- 需要隐藏滚动条但保留滚动功能
- 提供自定义的滚动指示器
- 优化用户体验的场景
作者: 行止 日期: 2026-01-11
版本: 1.0
如果这篇文章对你有帮助,欢迎分享给更多的开发者! 🎉