结论先放这儿:把「脚钉死、上身侧闪」和「腰腹带动整体平移」放进同一个二维模型跑 61 个采样点,前者的重心投影偏移峰值是 18.00 cm,后者只有 1.19 cm,差 15.13 倍。代价也很明确------这是几何与静力学的量级估计,不含韧带受力、肌肉激活和三维扭转,不能拿来诊断任何人。
目录:1 为什么要拆一个舞蹈动作 · 2 核对 6 亿播放这件事 · 3 评估过的 3 条技术路 · 4 把 17 个点降到 3 个点 · 5 实际跑出来的 15 倍 · 6 不倒翁、钢丝和一双专利鞋 · 7 零依赖 Canvas demo · 8 这段逆解代码,游戏里天天在跑 · 9 写进检查清单的 6 个坑 · 10 边界与没解决的问题 · 11 小结与速查表
1. 为什么要拆一个舞蹈动作
写这篇的契机是一条反复出现在报道里的话:「脚钉死在地面、上半身强行扭转,膝关节半屈位承受剪切力」。我想弄明白这个「剪切」由什么决定,翻了几篇科普,得到的全是定性描述,没有一处给量级。
专业做法和模仿做法的差别,报道里也写得很一致:前者靠腰腹发力带动重心整体平移,重心始终跟着支撑脚走;后者把脚固定住,只让上半身侧闪,重心投影就跑出了支撑面。这个解释合理,但没法验证,也没法复用。
闪身步出自花鼓灯------发源于安徽淮河流域的民间歌舞,2006 年列入第一批国家级非物质文化遗产名录,因为集舞蹈、灯歌、锣鼓于一体,有「东方芭蕾」之称,起源常被追溯到宋代。灯班里女角叫「兰花」,男角叫「鼓架子」;闪身步是基础步法,讲究「溜得起、刹得住」,以腰腹发力带动重心闪动移位。这次爆火的二创视频,翻拍的就是其中的侧闪。
我手头没有真人视频素材,也没有测力台数据。能做的,就是把动作抽象成可计算的几何量,用代码跑出数量级,再把过程画出来。于是有了随文的一个 Python 复现脚本和一个零依赖的浏览器 demo。
丑话说在前面:我没有做人体实测。模型里 65 kg、股骨 0.45 m、胫骨 0.43 m、髋高 0.72 m 都是可调的示例参数,取自常规成年人近似值;所有数字都由脚本算出来,改参数就会变。它不能诊断任何人,也不该被当成训练建议。
2. 核对 6 亿播放这件事
写代码之前先核事实。我把能查到的公开报道按时间线理了一遍,口径对不上的地方直接标出来:
| 时间 | 事实 | 出处 |
|---|---|---|
| 非遗源流 | 闪身步是安徽花鼓灯的基础步法,讲究「溜得起、刹得住」,以腰腹发力带动重心闪动移位,分侧闪、前闪、后闪 | 公开百科「闪身步」词条 |
| 20 世纪 90 年代 | 赵铁春等录制花鼓灯教学视频,闪身步是其中一个教学动作 | 2026-09-24 报道及课程录制人回应 |
| 2026 年 9 月 | 教学视频切片被二创,明星接力翻拍,相关话题播放量「超 6 亿」 | 2026-09-24 媒体报道 |
| 2026 年 9 月底 | 另一篇报道写「超 7.5 亿」,未说明统计口径与截止时间 | 2026-09-28 媒体报道 |
| 2026 年 10 月 | 多人模仿后膝关节疼痛、肿胀,确诊交叉韧带拉伤乃至部分撕裂 | 2026-10-08 / 10-09 报道 |
| 2026 年 10 月 | 从网络热梗转向非遗科普,传播到海外 | 公开百科词条「社会影响」 |
这两个数没法合并成一个确定值:6 亿与 7.5 亿来自不同媒体、不同日期,统计的是话题还是单条视频、有没有去重,报道都没写。标题取 6 亿,用的是较早、口径相对明确的那条。
真正和代码有关的是动作要领那一行。「腰腹发力带动重心整体平移」可以直接翻译成几何约束:髋与踝的水平差近似为 0;「脚钉死地面」则翻译成踝的水平坐标固定、髋独自摆动。整篇计算就从这一句翻译开始。
受伤机制的公开表述只用到定性部分:膝关节半屈位承受剪切力,前交叉韧带负责稳住胫骨相对股骨的滑动。本文不据此推任何临床阈值;提到它,只是想说明为什么重心投影值得算。
参考来源(不带链接,按标题检索即可):
- 公开百科「花鼓灯」词条:国家级非遗名录、「东方芭蕾」之称、角色行当
- 公开百科「闪身步」词条:非遗源流、动作要领、社会影响
- 2026-09-24《「闪身步」爆火,视频播放量超 6 亿!课程录制人回应》
- 2026-10-09《全网爆火的「闪身步」别乱跳!多人中招膝关节交叉韧带损伤》
3. 评估过的 3 条技术路
一开始我想直接上姿态估计模型:给一段视频,出关键点,算角度,看起来最正规。把三条路摆到一起之后改了主意。
| 方案 | 能给我什么 | 为什么最后没选 |
|---|---|---|
| TensorFlow.js + MoveNet | 浏览器端输出 17 个关键点,含 x、y、置信度和名称;Lightning 用 192×192 输入,Thunder 用 256×256,官方标称多数设备 30+ FPS | 要装 tfjs-core、tfjs-converter 和一个 WebGL/WASM 后端;缺少可公开使用的真人视频素材,跑不出可信结果 |
| MediaPipe / BlazePose 系姿态估计 | 关键点更密,带深度相关信息 | 同样依赖真实素材与模型文件,且输出越多,越难把「哪一段导致偏移」讲清楚 |
| 二维二连杆几何模型(本文采用) | 只要 3 个点:踝、膝、髋;纯标准库 / 纯 Canvas,离线可跑,每个数字都能追溯到一行公式 | 丢掉三维扭转与肌肉发力,只能给量级,不能给真人结论 |
取舍看中的是可解释:我要回答的是「重心跟着脚走与脚不动,几何上差多少」,不是「这个人做得对不对」。前一个问题需要视频和标注,后一个问题只要 3 个点和余弦定理。
MoveNet 的参数按 TensorFlow 官方资料核对:2021 年 5 月的官方介绍给出 17 个关键点、Lightning / Thunder 两个变体、192×192 与 256×256 输入,在浏览器端由 TensorFlow.js 运行,首次加载后不需要服务端调用。这些均为官方标称值,只用于选型对照,本文没有做本机性能基准。
4. 把 17 个点降到 3 个点
模型只保留矢状面上的一条腿:踝 A、膝 K、髋 H。大腿长 L₁、小腿长 L₂ 固定,已知 A 与 H 的位置,用二连杆逆解求 K。
设 d = |H − A|,沿 A→H 的单位向量 u,法向 p = (−u_y, u_x)。膝到踝在 u 方向的投影 a = (L₂² − L₁² + d²) / (2d),垂距 h = √(L₂² − a²),于是两个候选解 K = A + a·u ± h·p。取 x 较大的那个作为「膝在前」,另一个是反关节解。
python
def solve_knee(ankle, hip, pick="front"):
ax, ay = ankle
hx, hy = hip
dx, dy = hx - ax, hy - ay
d = math.hypot(dx, dy)
if d == 0 or d > L_SHANK + L_THIGH or d < abs(L_THIGH - L_SHANK):
return None
a = (L_SHANK ** 2 - L_THIGH ** 2 + d ** 2) / (2 * d)
h = math.sqrt(max(0.0, L_SHANK ** 2 - a ** 2))
ux, uy = dx / d, dy / d
px, py = -uy, ux
k_plus = (ax + a * ux + h * px, ay + a * uy + h * py)
k_minus = (ax + a * ux - h * px, ay + a * uy - h * py)
front = k_plus if k_plus[0] >= k_minus[0] else k_minus
back = k_minus if k_plus[0] >= k_minus[0] else k_plus
return front if pick == "front" else back
真正要输出的量只有一个:重心投影偏移 |H.x − A.x|。髋以上重量 W = 体重 × g × 0.62,乘上这个偏移,得到膝关节处的近似弯矩 W × |H.x − A.x|。它是「重力作用线离支撑点多远」的静力学量级,不是韧带受力。
两种动作模式在同一个正弦周期里采样 61 个点:
clean:踝与髋同步侧移,ax = 0.18·sin(2πt),hx = ax + 0.012·sin(4πt),水平差始终很小。pinned:踝固定ax = 0,髋独自摆动hx = 0.18·sin(2πt),水平差在峰值达到 0.18 m。
髋高统一取 0.72 − 0.04·|sin(2πt)| m。这个 0.72 不是随手写的:我把 0.62 / 0.72 / 0.82 / 0.86 m 各跑了一遍,膝屈角范围分别是 90.4--93.1°、70.2--74.0°、42.6--49.1°、24.5--34.9°。报道说的是「膝关节半屈位」,0.86 m 那条腿几乎伸直,只有 0.72 m 落在半屈区间,所以定在 0.72。
5. 实际跑出来的 15 倍
Python 3.13.12,标准库,命令只有一行 python kinematics.py。61 个采样点、一个完整侧闪周期,输出原样如下:
| 模式 | 重心偏移峰值 | 平均偏移 | 弯矩峰值 | 膝屈角范围 | 膝在踝后方帧占比 |
|---|---|---|---|---|---|
| clean | 1.19 cm | 0.75 cm | 4.72 N·m | 70.2--78.8° | 0.0% |
| pinned | 18.00 cm | 11.26 cm | 71.16 N·m | 70.2--74.0° | 0.0% |
18.00 / 1.19 = 15.13 倍,71.16 / 4.72 = 15.08 倍,平均偏移也是 15.01 倍。标题里那个「15 倍」指的就是这三处一致的比值。偏移和弯矩之间是线性关系:W 固定为 395.34 N,偏移每多 1 cm,弯矩加约 3.95 N·m。
把同一组 61 个采样点画成曲线:pinned 是两个大鼓包,clean 全程贴着零线。

我又把侧闪幅度从 0.06 m 扫到 0.24 m,看线性关系会不会在边界上失效:
| 侧闪幅度 | 重心偏移峰值 | 弯矩峰值 |
|---|---|---|
| 0.06 m | 6.00 cm | 23.72 N·m |
| 0.12 m | 12.00 cm | 47.44 N·m |
| 0.18 m | 18.00 cm | 71.16 N·m |
| 0.24 m | 24.00 cm | 94.88 N·m |
四个点严格成正比,说明这个模型里没有别的变量在捣乱。模型干净得过了头也是问题:真实人体不会这么听话,肌肉会代偿,脚会离地,躯干会在水平面里扭转,这些统统没进公式。
失败记录 :solve_knee 有两个解。我把后侧解单独跑了一遍,动画里膝盖整段跑到身体后面;统计下来,前侧解让膝落在踝后方的帧占比是 0.0%,后侧解是 100.0%。不是「看着怪」,是每一帧都反关节。修法是固定取前侧解,并把这条写进脚本的 failure log,防止以后改回去。
clean 那一列也不是 0:我给了 0.012·sin(4πt) 一个小扰动,代表真人不可能完全同步。这个扰动是假设值,不是实测数据;若设为 0,比值会变成无穷大,失去参考意义。
6. 不倒翁、钢丝和一双专利鞋
pinned 峰值 18.00 cm------比一张 A4 纸的宽度(21 cm)略窄一点,这是重心投影跑出支撑脚的距离。说白了,这个量衡量的就是离摔倒有多远。体重必须通过支撑面传给地面,重心投影一旦跑出去,重力对踝关节就只剩「把你绕倒」的力矩,除非有什么东西把它拉回来。
把重心拉回来的办法,生活里一抓一大把:
- 不倒翁把这道题反着解:它把配重压到支点正下方,无论怎么晃,重力矩都把它转回竖直。闪身步「溜得起、刹得住」练的就是活人版------腰腹在移动中把重心拉回支撑脚上方,「刹得住」刹的就是这个偏移。
- 走钢丝的长杆常被当成平衡魔法,其实干的也是同一件事:把「人加杆」的合成重心挪回钢丝正上方,同时放大转动惯量,给身体留出纠偏时间。它的每一次微调,做的都是 clean 模式的活儿------让重心投影回到支撑点。
- Michael Jackson 的 45° 倾斜把「脚钉死地面」做到了极致。《Smooth Criminal》那个前倾,MV 版靠吊钢丝,现场版靠一双鞋跟能卡进舞台卡槽的特制鞋------1993 年获批的美国专利 5255452《Method and means for creating anti-gravity illusion》,发明人一栏里就有他本人。踝被机械地固定住,正是本文 pinned 模式的前提;鞋和卡槽替他承担了失衡,而被模仿视频带偏的普通人没有卡槽,只能让膝关节和韧带硬扛------这正是 2026 年 10 月那批受伤案例共有的动作。
|H.x − A.x| 大到 18 cm 这个程度,靠日常的平衡本能已经补不回来,要用专门技巧或者外部结构。花鼓灯艺人用几十年的功力补,本文用 1.19 和 18.00 的比值把这件事量了出来。
7. 零依赖 Canvas demo
表和曲线有了,动作是怎么一步步偏过去的还是看不见。我把同一套公式搬进浏览器,做成左右对照的动画------左边 clean,右边 pinned,同一个 t 驱动,共享一组常量。

环境是任意支持 Canvas 2D 的现代浏览器,ES2020,不依赖 npm、构建和网络。把 flash-step-demo.html 存到本地双击就能跑,CSS、HTML、JS 加起来一百多行。
核心是把逆解翻成 JS,** 幂运算和 Math.hypot 都是原生可用的:
js
function solveKnee(A, H) {
const dx = H.x - A.x, dy = H.y - A.y;
const d = Math.hypot(dx, dy);
if (!d || d > L_THIGH + L_SHANK || d < Math.abs(L_THIGH - L_SHANK)) return null;
const a = (L_SHANK ** 2 - L_THIGH ** 2 + d ** 2) / (2 * d);
const h = Math.sqrt(Math.max(0, L_SHANK ** 2 - a ** 2));
const ux = dx / d, uy = dy / d, px = -uy, py = ux;
const k1 = { x: A.x + a * ux + h * px, y: A.y + a * uy + h * py };
const k2 = { x: A.x + a * ux - h * px, y: A.y + a * uy - h * py };
return k1.x >= k2.x ? k1 : k2;
}
渲染里有两个坑。一个是坐标手性:物理坐标 y 轴向上,Canvas 的 y 轴向下,toPx 必须写成 y: ORIGIN.y - p.y * SCALE,直接加会把人画到地面以下。另一个是动画节奏:requestAnimationFrame 给的时间戳是毫秒,我用 (ts / 1600) % 1 归一化到周期,而不是每帧 t += 0.01,否则 144 Hz 屏上动作会快一倍多。
每个面板下方实时显示重心偏移与近似弯矩,脚下的短色条画出 |H.x − A.x| 的长度,青色是 clean,红色是 pinned。右侧跑到峰值时数值落在 18.00 cm / 71.16 N·m,和 Python 那张表对得上。这也是我校验两套实现的办法:同一组常量、同一个 t,两个环境输出必须逐位相同。

想换参数复现:kinematics.py 支持 --mass、--swing、--frames,改完体重或侧闪幅度,把浏览器里的常量同步改掉,两边再对一次即可。
8. 这段逆解代码,游戏里天天在跑
上文的 solveKnee 不是冷门公式,游戏行业管这类问题叫两段式逆运动学(IK):大腿、小腿两根连杆,已知脚的落点和髋的位置,解膝盖该弯向哪------同一个公式,同一个「前侧解还是后侧解」的抉择。
- 脚部 IK。你操作角色走上楼梯时,脚掌会贴住台阶面而不是悬浮或穿模,背后是引擎在逐帧解这个方程:Unreal 的 Foot IK、Unity 的 Animation Rigging 都是现成模块。你看到的「站稳」,是引擎逐帧算出来的,不是动画师画出来的。
- Rain World 的程序动画。这款独立游戏走得更远:生物没有预制走路动画,每只脚的落点都由程序实时求解,蜥蜴是真的把脚踩在管道壁上再蹬地。动效设计师 James Therrien 2019 年在游戏开发者大会(GDC)做过专场分享,标题《Designing Procedural Animation for "Rain World"》,按标题可检索。
- QWOP 与反关节。「取错解」的翻车大家多半见过:Bennett Foddy 2008 年上线的跑步游戏 QWOP,Q/W 控制两条大腿、O/P 控制两条小腿,二连杆的每个关节都交给你手动解------膝盖反向弯折、原地劈叉、脸先着地,正是本文第 5 节那个「后侧解 100% 反关节」的游戏化版本。区别在于,游戏故意把解留给你选,本文把它写成断言并修掉。
游戏里每一次稳稳的踩踏,背后都是这套逆解在逐帧运行------和第 7 节是同一套数学。
9. 写进检查清单的 6 个坑
| # | 现象 | 根因 | 修复 / 可复用检查项 |
|---|---|---|---|
| 1 | 骨架膝盖跑到身体后方,像反关节 | 二连杆逆解有对称的两个解,取到了后侧解 | 固定取 x 较大的解;加一条「膝在踝后方帧占比」断言,正常应为 0% |
| 2 | 髋高 0.86 m 时膝几乎伸直,和「半屈位」对不上 | 髋踝距离逼近 L₁ + L₂,膝角被顶到接近 180° | 先扫髋高看膝屈角区间再定值;本例 0.72 m 得 70.2--74.0° |
| 3 | 人画到地面以下 | Canvas 的 y 轴向下,物理坐标 y 轴向上 | toPx 里用减法翻转;先打印一个已知点的像素坐标核对 |
| # | 现象 | 根因 | 修复 / 检查项 |
|---|---|---|---|
| 4 | 高刷屏上动作明显变快 | 用固定步长累加 t,帧率参与了一周期时长 | 用时间戳归一化 (ts / 1600) % 1,周期与刷新率解耦 |
| 5 | 左右侧看起来互换 | 前置摄像头或镜像视频会翻转 x | 统一坐标手性;换成真人视频输入前先做一次镜像归一化 |
| 6 | 把 N·m 读成「韧带受力」 | 弯矩是重力 × 水平力臂,单位本就不是力 | 表里写「近似弯矩」,图注写明不含韧带受力与肌肉激活 |
第 3、5 条在这个纯几何 demo 里不会真的出现,是我把公式往真人视频上搬之前先记下的检查项。第 6 条最要紧:一个单位误读,就能把工程可视化讲成医学结论,这类错误比代码 bug 难收回。
10. 边界与没解决的问题
这套模型能回答的问题很窄:在二维矢状面、单腿支撑、连杆长度固定、重心近似落在髋的前提下,脚动与脚不动会让「重力作用线偏离支撑点」差多少。超出这个范围,数字就没有意义。
模型没算的东西,至少数得出四类:
- 水平面扭转。闪身步的「闪」包含躯干相对骨盆的旋转,二维侧视看不到;公开报道提到的剪切力与这个旋转强相关,本模型完全没算。
- 肌肉与韧带。没有肌肉激活、没有前交叉韧带受力、没有关节接触力,也没有任何临床阈值。4.72 和 71.16 这两个 N·m 只能互相比,不能和任何安全值比。
- 真实人体参数。65 kg、0.45 / 0.43 m、0.62 的上半身质量占比都是示例值,不同身高体脂的人代进去结果会变;除灵敏度扫描外我没做个体化验证。
- 真实动作数据。没有视频、没有关键点标注、没有测力台,也没和医学影像对照。本文不给出「谁做得对」的判断。
还有一处没解决:clean 模式那个 0.012 m 的同步扰动是手工设的,用来避免比值为无穷大,没有数据来源。它只影响分母,不影响「脚钉死会放大偏移」这个方向性结论;想拿具体倍数说事,就得换成真人动作捕捉数据。我没有这类数据,所以把它显式列成假设,读者改掉就能看到比值怎么变。
接着往下做的话,最值得补的是一段有授权的真人视频加 17 个关键点的时间序列,用它替换正弦假设,再看 clean / pinned 两条曲线还能不能分开。
11. 小结与速查表
- 热点里真正能翻译成代码的是「重心跟着脚走」这句动作要领,不是播放量。
- 同一个二连杆模型下,脚钉死让重心投影偏移峰值从 1.19 cm 变成 18.00 cm,近似弯矩从 4.72 N·m 变成 71.16 N·m,比值 15.1 倍。
- 这个 15 倍是几何量级,不是受伤概率,不能拿去劝人别跳或放心跳。
这一路踩的坑,压成一张速查表,下次做动作可视化之前照着过一遍:
| 检查项 | 通过标准 |
|---|---|
| 膝解 | 固定取前侧解;膝在踝后方帧占比 = 0% |
| 姿态 | 髋高先扫描,膝屈角落在目标区间再定值 |
| 坐标 | 物理 y 向上、Canvas y 向下,转换用减法 |
| 时间 | 用时间戳归一化,不用固定步长 |
| 单位 | 写「近似弯矩 N·m」,不写「韧带受力」 |
| 跨端一致 | JS 与 Python 同常量、同 t,输出逐位对齐 |
本文原创首发。完整复现脚本 kinematics.py 与零依赖浏览器 demo flash-step-demo.html 随文附上,保存到本地即可直接运行;公开报道的参考来源见第 2 节,MoveNet 参数的官方出处见第 3 节。
想听听你们的做法:做动作可视化,是先上关键点检测模型保住真实数据,还是像本文这样先用几何模型把因果讲清楚,再决定要不要引入真实素材?