
UI 动效 004:Rubber Band,拖到边界为什么会「弹」回来
这期拆解 Rubber Band / Elastic Overscroll 回弹边界动效:它适合列表、抽屉和拖拽面板的边界反馈,关键是先压缩越界位移,再用带速度的 spring 回到 0。

动效档案
| 项目 | 说明 |
|---|---|
| 中文叫法 | 回弹边界、橡皮筋回弹、弹性越界 |
| 英文叫法 | Rubber Band、Elastic Overscroll、Bounce |
| 典型位置 | 列表顶部 / 底部、抽屉面板边界、可拖拽卡片、轮播末端 |
| 适合传达 | 「已经到头了,但你的手势被系统接住了」 |
| 不适合 | 表单提交、危险操作确认、需要稳定阅读的长文本区域 |
UIScrollView.bounces 控制滚动视图是否能越过内容边缘再回到边缘;Apple 对这个属性的描述就是「bounces past the edge of content and back again」。1 Flutter 也把同类行为做成 BouncingScrollPhysics:滚动偏移可以超过内容边界,然后再弹回边界,文档还说明这是 iOS 上常见的滚动行为。2视觉上发生了什么
- 正常滚动:内容跟着手指移动,滚动偏移还在合法范围内。
- 撞到边界:顶部或底部已经到头,继续拖拽只进入 overscroll 区域。
- 位移压缩:手指继续走 100px,内容可能只多移动 40px;越往后越难拉开。
- 松手回弹:手指离开后,内容不直接闪回,而是按弹簧曲线回到边界。
frictionFactor 说得很直白:它会把 overscroll 时的输出位移相对于手势输入位移压小,让「拖过边界」看起来更难。2 这就是橡皮筋感的来源。不是列表真的被拉长,而是视觉位移被做了非线性压缩。什么时候该用它
| 场景 | 为什么适合 | 注意点 |
|---|---|---|
| 列表到顶 / 到底 | 用户还在拖,系统用回弹承接手势 | 不要把回弹做得太远,否则像页面坏了 |
| 下拉刷新前段 | 先用橡皮筋感提示「还没触发」 | 触发刷新后要换成明确的 loading 状态 |
| 可拖拽面板 | 面板撞到最大 / 最小高度时,需要软边界 | 边界必须稳定,不能松手后停在奇怪位置 |
| 轮播末端 | 告诉用户已经到第一张或最后一张 | 如果有循环轮播,就不要再做末端回弹 |
overscroll-behavior 这个 CSS 属性就是用来控制滚动区域到达边界时浏览器该怎么处理;MDN 明确写到,它会影响 scroll chaining、bounce 以及 pull-to-refresh 这类边界行为。3 如果你在弹窗、抽屉、嵌套列表里做自定义 Rubber Band,先处理好滚动传递,否则手指一拉,背后的页面也跟着跑。实现关键:先压缩,再弹回
1. 边界检测
const atTop = scrollTop <= 0;
const atBottom = scrollTop + viewportHeight >= scrollHeight;
const enteringOverscroll = (atTop && dragY > 0) || (atBottom && dragY < 0);2. 把手指位移映射成视觉位移
function rubber(distance, limit = 120) {
const sign = Math.sign(distance);
const x = Math.abs(distance);
return sign * limit * (1 - Math.exp(-x / limit));
}
// 手指拉了 140px,视觉上可能只移动 82px
content.style.transform = `translateY(${rubber(dragY)}px)`;distance * 0.5 更像橡皮筋,因为它有一个软上限:用户能感觉到「还能拉」,但不会无限拉开。3. 松手后用 spring 回到 0
transition: transform .3s ease-out。Rubber Band 的手感来自速度和阻尼:松手速度越大,回弹的第一段越有力;阻尼越大,来回抖动越少。stiffness、damping、mass,并且物理型 spring 会把已有手势或动画的 velocity 纳入计算。4 Android 的 SpringForce 也把 stiffness、damping ratio 和 final position 当作弹簧动画的核心参数。5const y = motionValue(0);
onDrag((dragY) => {
if (!enteringOverscroll) return;
y.set(rubber(dragY));
});
onRelease(({ velocityY }) => {
animate(y, 0, {
type: "spring",
stiffness: 420,
damping: 32,
velocity: velocityY,
});
});CSS 曲线能不能凑一个?
.overscroll-release {
transition: transform 360ms cubic-bezier(.18, 1.28, .22, 1);
transform: translateY(0);
}设计时别踩这 4 个坑
- 回弹距离太大:边界反馈会变成页面漂移。手机列表一般让视觉位移停在一个有限范围内就够了。
- 松手后停不回原位:Rubber Band 的最终状态必须是边界,不要让内容卡在 overscroll 区。
- 嵌套滚动没处理:弹窗里的列表到顶后,背后页面继续滚,会让用户觉得手势被抢走。Web 场景优先检查
overscroll-behavior。3 - 把它用在确认动作上:删除、支付、提交这类动作需要明确反馈,不适合用软绵绵的回弹来表达结果。
一句话记住它
参考ソース
- 1bounces
- 2BouncingScrollPhysics class - Flutter API
- 3overscroll-behavior CSS property - MDN
- 4React transitions
- 5Animate movement using spring physics
- 6cubic-bezier() CSS function - MDN
関連コンテンツ
コンテンツの類似度に基づいて他のチャンネルから選びました。新しいフォロー先を見つけましょう。
画像投稿·Day 13 · Slider 滑块:范围可拖,精确另填
Slider 滑块用于在一个范围内拖动取值,适合音量、亮度、滤镜强度和价格区间等相对调节。本期 4 张图讲清 Slider 与 InputNumber / Progress Bar 的边界、4 种常见形态,以及使用口诀:范围可拖,精确另填。
UI/UX 组件每日一讲
記事·六月前端创意精选:Glass Blob、WebGL 漫画与 GSAP 全家桶
精选 7 个 2026 年 6 月上半月值得拆解的前端创意 Demo:来自 Awwwards 的 Glass Blob AI 品牌站、可翻阅的 WebGL 漫画、机器人 3D 微交互、GSAP 动效教科书,以及 CodePen 上的 ScrollTrigger 批量动画和纯 CSS 沙堡艺术。每个 Demo 都附有技术栈、创意亮点和可复用思路。
前端创意 Demo 双周刊
画像投稿·Day 03 · Snackbar:有时候,反馈要带一个动作
Snackbar 是操作级反馈条,和 Toast 最关键的区别是:它可以带一个操作按钮(撤销、重试)。本期4张图讲清 Snackbar 的定义、与 Toast 的核心区别、3 种变体类型,以及最重要的使用判断标准:当用户可能后悔、需要一条退路时,选 Snackbar 而不是 Toast。
UI/UX 组件每日一讲
画像投稿·Day 07 · Skeleton Screen:内容加载时的占位幽灵
Skeleton Screen 是 Loading Spinner 的进化版:Spinner 只说「在转」,骨架屏进一步告诉用户即将出现什么形状的内容。本期 4 张图讲清骨架屏的定义、与 Spinner/Progress Bar 的三兄弟对比、4 种常用模板(卡片/列表/文章/个人资料),以及最关键的判断口诀:能量化 → Progress Bar,不能量化 → Spinner,加载内容列表 → Skeleton Screen。
UI/UX 组件每日一讲
画像投稿·Day 06 · Progress Bar:从 0% 到 100%,进度可见才有掌控感
Progress Bar 是等待三件套的最后一块:只要进度可量化,就该用它而非 Spinner。本期 4 张图讲清进度条的定义、与 Spinner/Skeleton Screen 的对比、4 种变体(确定型/不确定型/圆形/分步),以及最关键的一句判断口诀:能量化 → Progress Bar,不能量化 → Spinner,加载内容 → Skeleton Screen。
UI/UX 组件每日一讲
画像投稿·Day 05 · Loading Spinner:等待时的视觉锚点
Loading Spinner 是进度未知时的等待指示器——只告诉用户「系统在处理」,不告诉剩余时长。本期 4 张图讲清 Spinner 的定义、与 Progress Bar / Skeleton Screen 的核心区别、4 种变体类型(Inline / Overlay / Section / Icon),以及最关键的使用判断:等待超过 3 秒换 Progress Bar,加载内容列表换 Skeleton Screen,全屏 Spinner 慎用。
UI/UX 组件每日一讲

このコンテンツについて、さらに観点や背景を補足しましょう。