dsh-creation-mode 3.0.0 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,322 @@
1
+ # 运动与缓动
2
+
3
+ 动画好不好看,八成取决于运动曲线,不取决于画了什么。这一份讲清楚:帧怎么算、
4
+ 曲线怎么选、速度怎么控、什么时候该加运动模糊。
5
+
6
+ ---
7
+
8
+ ## 一、帧即函数
9
+
10
+ `window.renderAt(t)` 必须是纯函数:同一个 `t` 永远得到同一帧。
11
+
12
+ ```js
13
+ window.renderAt = function (t) {
14
+ ctx.clearRect(0, 0, W, H);
15
+ drawBackground(t);
16
+ drawTitle(t);
17
+ drawParticles(t);
18
+ };
19
+ ```
20
+
21
+ 三条禁止:
22
+
23
+ | 禁止 | 后果 |
24
+ |---|---|
25
+ | 读 `Date.now()` / `performance.now()` | 预览和导出不同步,导出时画面乱跳 |
26
+ | 用 `Math.random()` | 每一帧粒子位置都变,导出后像雪花屏 |
27
+ | 依赖上一帧状态(累加、缓存) | 导出是「跳帧取样」,跳过的那几帧状态全丢 |
28
+
29
+ 需要随机就写固定种子的伪随机:
30
+
31
+ ```js
32
+ function mulberry32(seed) {
33
+ return function () {
34
+ seed |= 0; seed = (seed + 0x6D2B79F5) | 0;
35
+ let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
36
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
37
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
38
+ };
39
+ }
40
+ const rand = mulberry32(20261103); // 固定种子,每帧重新创建也一样
41
+ ```
42
+
43
+ 需要「上一帧的位置」就把它算出来,不要存。比如粒子在 `t` 时刻的位置:
44
+
45
+ ```js
46
+ // 错:p.x += vx * dt
47
+ // 对:位置是 t 的解析函数
48
+ const x = x0 + vx * t + 0.5 * ax * t * t;
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 二、时间归一化
54
+
55
+ 所有动画参数都写成 `t` 的函数,用区间映射:
56
+
57
+ ```js
58
+ const clamp01 = (u) => (u < 0 ? 0 : u > 1 ? 1 : u);
59
+ /** 在 [start, start+dur] 内把时间映射到 0..1 */
60
+ const tween = (t, start, dur) => clamp01((t - start) / Math.max(dur, 1e-6));
61
+ ```
62
+
63
+ 有了 `tween`,任何属性都能这样写:
64
+
65
+ ```js
66
+ const u = tween(t, 1.2, 0.6); // 1.2s 开始,持续 0.6s
67
+ const opacity = easeOutCubic(u);
68
+ const y = 400 - 120 * easeOutBack(u);
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 三、缓动曲线
74
+
75
+ ### 3.1 为什么不能线性
76
+
77
+ 匀速运动看起来像机器在动。人眼对「起步」和「收尾」的加速度极其敏感,
78
+ 线性插值在这两端加速度突变,观感是「卡了一下」。
79
+
80
+ ### 3.2 常用曲线(附实现)
81
+
82
+ ```js
83
+ const easeOutCubic = (u) => 1 - Math.pow(1 - u, 3);
84
+ const easeInCubic = (u) => u * u * u;
85
+ const easeInOutCubic= (u) => u < 0.5 ? 4*u*u*u : 1 - Math.pow(-2*u + 2, 3) / 2;
86
+ const easeOutQuint = (u) => 1 - Math.pow(1 - u, 5);
87
+ const easeOutExpo = (u) => u === 1 ? 1 : 1 - Math.pow(2, -10 * u);
88
+ const easeOutBack = (u) => { const c1 = 1.70158, c3 = c1 + 1;
89
+ return 1 + c3 * Math.pow(u - 1, 3) + c1 * Math.pow(u - 1, 2); };
90
+ ```
91
+
92
+ ### 3.3 什么时候用哪条
93
+
94
+ | 场景 | 曲线 | 理由 |
95
+ |---|---|---|
96
+ | 元素进场(淡入、上浮) | ease-out | 快进慢停,像被甩进来又稳住 |
97
+ | 元素退场(淡出、下沉) | ease-in | 慢起快走,像被吸走 |
98
+ | 位置移动 | ease-in-out | 两头缓中间快,符合惯性 |
99
+ | 弹性 / 回弹 | ease-out-back | 过冲 10–15% 再落回 |
100
+ | 数字滚动 | ease-out-quint | 末尾几乎静止,读数清楚 |
101
+ | 遮罩揭开 | ease-in-out | 匀速揭开像机械 |
102
+ | 摄像机推近 | ease-in-out 或 ease-out | 推近用 ease-out,拉远用 ease-in |
103
+
104
+ **不要用 CSS 默认的 `ease`**(等价 cubic-bezier(0.25,0.1,0.25,1)),曲线太弱,
105
+ 在 1 秒以内的动画上几乎看不出区别。
106
+
107
+ ### 3.4 用 bezier 精确控制
108
+
109
+ ```js
110
+ function cubicBezier(p1x, p1y, p2x, p2y) {
111
+ // 牛顿迭代求 x 对应的 t,再算 y
112
+ const cx = 3 * p1x, bx = 3 * (p2x - p1x) - cx, ax = 1 - cx - bx;
113
+ const cy = 3 * p1y, by = 3 * (p2y - p1y) - cy, ay = 1 - cy - by;
114
+ const sampleX = (s) => ((ax * s + bx) * s + cx) * s;
115
+ const sampleY = (s) => ((ay * s + by) * s + cy) * s;
116
+ const solveX = (x) => {
117
+ let s = x;
118
+ for (let i = 0; i < 6; i++) {
119
+ const err = sampleX(s) - x;
120
+ if (Math.abs(err) < 1e-5) break;
121
+ const d = (3 * ax * s + 2 * bx) * s + cx;
122
+ if (Math.abs(d) < 1e-6) break;
123
+ s -= err / d;
124
+ }
125
+ return s;
126
+ };
127
+ return (x) => sampleY(solveX(clamp01(x)));
128
+ }
129
+ const easeOut = cubicBezier(0.16, 1, 0.3, 1);
130
+ ```
131
+
132
+ 常用值:
133
+
134
+ | 名字 | bezier |
135
+ |---|---|
136
+ | ease-out(强) | `(0.16, 1, 0.3, 1)` |
137
+ | ease-in(强) | `(0.7, 0, 0.84, 0)` |
138
+ | ease-in-out(标准) | `(0.65, 0, 0.35, 1)` |
139
+ | 快出慢收 | `(0.22, 1, 0.36, 1)` |
140
+ | 蓄力 | `(0.5, 0, 0.9, 0.2)` |
141
+
142
+ ### 3.5 弹簧
143
+
144
+ 弹簧比 bezier 更「活」,适合强调性动作(按钮弹入、图标落位)。解析解:
145
+
146
+ ```js
147
+ /** 阻尼弹簧:0 时刻从 0 出发,最终收敛到 1 */
148
+ function spring(t, { stiffness = 180, damping = 12, mass = 1 } = {}) {
149
+ if (t <= 0) return 0;
150
+ const w0 = Math.sqrt(stiffness / mass);
151
+ const zeta = damping / (2 * Math.sqrt(stiffness * mass));
152
+ if (zeta < 1) {
153
+ const wd = w0 * Math.sqrt(1 - zeta * zeta);
154
+ const A = -1, B = -zeta * w0 / wd;
155
+ return 1 + Math.exp(-zeta * w0 * t) * (A * Math.cos(wd * t) + B * Math.sin(wd * t));
156
+ }
157
+ return 1 - Math.exp(-w0 * t) * (1 + w0 * t);
158
+ }
159
+ ```
160
+
161
+ 参数直觉:
162
+
163
+ | 效果 | stiffness | damping |
164
+ |---|---|---|
165
+ | 轻微回弹 | 120 | 14 |
166
+ | 明显回弹 | 200 | 10 |
167
+ | 硬弹(像金属) | 300 | 8 |
168
+ | 几乎不弹(只减速) | 100 | 26 |
169
+
170
+ **弹簧只用在「结果需要强调」的地方**:一次落位、一次开关。全程弹簧会显得廉价。
171
+
172
+ ---
173
+
174
+ ## 四、夹紧与越界
175
+
176
+ `ease-out-back` 会过冲(返回值 > 1),`spring` 会来回震荡。这些是设计意图,
177
+ 但**位置、透明度、缩放必须夹紧到合法范围**:
178
+
179
+ ```js
180
+ const alpha = clamp01(easeOutBack(u));
181
+ const scale = Math.max(0.01, 1 + 0.15 * spring(t, ...));
182
+ ```
183
+
184
+ 不夹紧的典型事故:透明度变成负数(画不出来)、缩放变成 0(除零)、
185
+ 元素坐标飞到画布外(看起来像消失了)。
186
+
187
+ ---
188
+
189
+ ## 五、帧预算与性能
190
+
191
+ 30fps 下每帧 16.6ms,60fps 下 8.3ms。浏览器还要留时间给合成和录制。
192
+
193
+ | 操作 | 大致成本 |
194
+ |---|---|
195
+ | `fillRect` 小面积 | 0.01ms |
196
+ | `fillText` 单行 | 0.05–0.2ms |
197
+ | `drawImage` 同尺寸贴图 | 0.05ms |
198
+ | 径向渐变全屏 | 0.5–2ms |
199
+ | `shadowBlur` 大半径 | 1–5ms |
200
+ | 逐像素 `getImageData/putImageData` | 10–50ms(禁止每帧做) |
201
+ | 创建 `Path2D`(每帧新建) | 0.1–1ms |
202
+
203
+ 经验上限:**每帧 300 个绘制调用**以内比较稳。超过就合并、预渲染、降级。
204
+
205
+ 优化手段:
206
+
207
+ 1. **离屏预渲染**:把静态的复杂图形画一次到 `OffscreenCanvas`,之后 `drawImage`。
208
+ 2. **分层**:背景层只在变化时重画(但纯函数约束下通常是每帧重画,所以要缓存到离屏 canvas,按 `t` 的粗粒度失效)。
209
+ 3. **粒子用贴图**:不要每个粒子都 `beginPath + arc`,预生成 32×32 的圆点贴图。
210
+ 4. **避免每帧分配**:对象池、复用数组。
211
+
212
+ ```js
213
+ // 预生成粒子贴图
214
+ const dot = document.createElement('canvas');
215
+ dot.width = dot.height = 32;
216
+ const dctx = dot.getContext('2d');
217
+ const g = dctx.createRadialGradient(16, 16, 0, 16, 16, 16);
218
+ g.addColorStop(0, 'rgba(255,255,255,1)');
219
+ g.addColorStop(1, 'rgba(255,255,255,0)');
220
+ dctx.fillStyle = g; dctx.fillRect(0, 0, 32, 32);
221
+ // 绘制时
222
+ ctx.drawImage(dot, p.x - 16, p.y - 16, 32, 32);
223
+ ```
224
+
225
+ ---
226
+
227
+ ## 六、运动模糊
228
+
229
+ 真实运动模糊来自快门开合:180° 快门意味着曝光覆盖半帧。
230
+
231
+ ### 6.1 子帧叠加
232
+
233
+ ```js
234
+ function drawWithBlur(t, shutter = 0.5, samples = 6) {
235
+ // shutter 是帧的倍数:0.5 = 半帧曝光
236
+ const frameDur = 1 / 30;
237
+ const span = frameDur * shutter;
238
+ ctx.save();
239
+ ctx.globalAlpha = 1 / samples;
240
+ for (let i = 0; i < samples; i++) {
241
+ const sub = t - (i / (samples - 1 || 1)) * span;
242
+ drawScene(sub);
243
+ }
244
+ ctx.restore(); // 恢复 globalAlpha
245
+ }
246
+ ```
247
+
248
+ ### 6.2 什么时候用
249
+
250
+ | 条件 | 加不加 |
251
+ |---|---|
252
+ | 元素速度 > 40px/帧 | 加 |
253
+ | 画面整体推拉/摇移 | 加 |
254
+ | 静止画面、缓慢淡入 | 不加(只会变糊) |
255
+ | 文字 | 谨慎,小字加了会糊成一团 |
256
+
257
+ ### 6.3 成本
258
+
259
+ `samples` 直接乘上绘制成本。6 个样本 = 6 倍绘制时间。导出 120 秒 × 30fps × 6 样本
260
+ = 21600 次全场景绘制,会很慢。只在关键段落用,或者把 samples 降到 3。
261
+
262
+ ---
263
+
264
+ ## 七、动作设计
265
+
266
+ ### 7.1 预备—动作—跟随
267
+
268
+ 真实动作有三段,动画也一样:
269
+
270
+ | 阶段 | 时长占比 | 表现 |
271
+ |---|---|---|
272
+ | 预备(anticipation) | 15% | 反向蓄力,比如起跳前下蹲 |
273
+ | 动作(action) | 50% | 主运动,快 |
274
+ | 跟随(follow-through) | 35% | 过冲 + 回弹 + 附属物延迟 |
275
+
276
+ ```js
277
+ const total = 0.8;
278
+ const u = tween(t, 1.0, total);
279
+ let y;
280
+ if (u < 0.15) y = 20 * (u / 0.15); // 下蹲
281
+ else if (u < 0.65) y = 20 - 140 * easeOutQuint((u - 0.15) / 0.5); // 起跳
282
+ else y = -120 + 8 * Math.sin((u - 0.65) / 0.35 * Math.PI); // 回弹
283
+ ```
284
+
285
+ ### 7.2 只让一个东西当主角
286
+
287
+ 同一时刻三个元素在动,观众哪个都看不见。检查方法:每隔 0.5 秒问一次
288
+ 「现在观众的眼睛该看哪里」。答不上来就说明画面太挤。
289
+
290
+ ### 7.3 留白
291
+
292
+ 静止不是死亡。6–10 帧的静场比全程都在动更有力,尤其是重音之前。
293
+
294
+ ```js
295
+ // 重音落在 3.0s:2.9–3.0 之间画面完全静止
296
+ const holdStart = 2.9, holdEnd = 3.0;
297
+ if (t > holdStart && t < holdEnd) return; // 保持上一帧内容
298
+ ```
299
+
300
+ ### 7.4 次级动作
301
+
302
+ 主体到位后,附属元素延迟 2–4 帧跟上,画面立刻「活」:
303
+
304
+ ```js
305
+ const mainU = tween(t, 1.0, 0.4);
306
+ const subU = tween(t, 1.0 + 0.08, 0.4); // 延迟 80ms
307
+ ```
308
+
309
+ ---
310
+
311
+ ## 八、自检清单
312
+
313
+ 导出前逐条过:
314
+
315
+ - [ ] `renderAt(0)` 不是黑屏
316
+ - [ ] `renderAt(duration/2)` 有内容
317
+ - [ ] `renderAt(duration - 0.05)` 不是被截断的半截动作
318
+ - [ ] 没有任何 `Math.random()` 或 `Date.now()`
319
+ - [ ] 所有插值都夹紧了
320
+ - [ ] 同一时刻只有一个视觉主角
321
+ - [ ] 每个卡点前 2–3 帧画面是静的
322
+ - [ ] 逐帧拖动看,没有闪烁或跳变
@@ -0,0 +1,166 @@
1
+ # 粒子系统
2
+
3
+ 粒子是「量」的艺术:几十个是装饰,几千个是氛围,几万个是特效。
4
+
5
+ 这一份讲:怎么在纯函数约束下做粒子、怎么控性能、怎么让粒子「有性格」。
6
+
7
+ ---
8
+
9
+ ## 一、纯函数粒子
10
+
11
+ 粒子**不能**逐帧累加位置,因为导出是跳帧取样,跳过的帧状态全丢。
12
+
13
+ 正确做法:每个粒子的状态是 `t` 的解析函数。
14
+
15
+ ```js
16
+ function particle(i, t) {
17
+ const rand = mulberry32(1000 + i); // 每个粒子固定种子
18
+ const angle = rand() * Math.PI * 2;
19
+ const speed = 60 + rand() * 140;
20
+ const life = 1.2 + rand() * 1.8;
21
+ const born = rand() * 2; // 出生时间错开
22
+ const age = t - born;
23
+ if (age < 0 || age > life) return null; // 未出生或已消亡
24
+
25
+ const k = age / life;
26
+ const r = speed * age * (1 - 0.5 * k); // 带阻尼的位移
27
+ return {
28
+ x: cx + Math.cos(angle) * r,
29
+ y: cy + Math.sin(angle) * r + 40 * age * age, // 重力
30
+ alpha: (1 - k) * (1 - k), // 二次衰减
31
+ size: 2 + rand() * 3,
32
+ };
33
+ }
34
+ ```
35
+
36
+ 关键:`rand` 用粒子索引做种子,每个粒子的初始条件固定,位置由 `age` 算出。
37
+ 删掉累加,也就删掉了跳帧问题。
38
+
39
+ ---
40
+
41
+ ## 二、绘制
42
+
43
+ ### 2.1 用贴图,不要画路径
44
+
45
+ ```js
46
+ // 慢:每个粒子 4 次调用
47
+ for (const p of ps) { ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, 7); ctx.fill(); }
48
+
49
+ // 快:每个粒子 1 次
50
+ for (const p of ps) ctx.drawImage(dot, p.x - s, p.y - s, s * 2, s * 2);
51
+ ```
52
+
53
+ 预生成贴图(见 color.md 的 `makeNoise` / 光斑做法):
54
+
55
+ ```js
56
+ const dot = (() => {
57
+ const c = document.createElement('canvas');
58
+ c.width = c.height = 32;
59
+ const g = c.getContext('2d');
60
+ const rg = g.createRadialGradient(16, 16, 0, 16, 16, 16);
61
+ rg.addColorStop(0, 'rgba(255,255,255,1)');
62
+ rg.addColorStop(0.6, 'rgba(255,255,255,0.6)');
63
+ rg.addColorStop(1, 'rgba(255,255,255,0)');
64
+ g.fillStyle = rg; g.fillRect(0, 0, 32, 32);
65
+ return c;
66
+ })();
67
+ ```
68
+
69
+ ### 2.2 混合模式
70
+
71
+ | 模式 | 效果 | 适用 |
72
+ |---|---|---|
73
+ | `source-over` | 正常叠加 | 实心粒子 |
74
+ | `lighter` | 相加,越叠越亮 | 光点、火花 |
75
+ | `screen` | 柔和的提亮 | 烟雾、光晕 |
76
+
77
+ `lighter` 最容易出效果,但也最容易过曝。超过 500 个粒子时把单粒子 alpha 降到
78
+ 0.15 以下。
79
+
80
+ ---
81
+
82
+ ## 三、性能
83
+
84
+ | 粒子数 | 做法 |
85
+ |---|---|
86
+ | < 200 | 每帧直接画 |
87
+ | 200–2000 | 贴图 + `lighter`,避免 `save/restore` |
88
+ | 2000–20000 | 降到 16×16 贴图,关闭阴影,减少 alpha 变化 |
89
+ | > 20000 | 预渲染成序列图,或改用 `ImageData` 逐像素(一次性) |
90
+
91
+ 一帧 33ms 的预算下,2000 个 `drawImage` 约 6–10ms。
92
+
93
+ ### 3.1 减少状态切换
94
+
95
+ ```js
96
+ // 差:每个粒子都改 globalAlpha
97
+ for (const p of ps) { ctx.globalAlpha = p.alpha; ctx.drawImage(dot, ...); }
98
+
99
+ // 好:按 alpha 分桶,每桶设一次
100
+ const buckets = new Map();
101
+ for (const p of ps) {
102
+ const k = Math.round(p.alpha * 10);
103
+ (buckets.get(k) || buckets.set(k, []).get(k)).push(p);
104
+ }
105
+ for (const [k, arr] of buckets) {
106
+ ctx.globalAlpha = k / 10;
107
+ for (const p of arr) ctx.drawImage(dot, ...);
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## 四、有性格的粒子
114
+
115
+ ### 4.1 爆发
116
+
117
+ 从一点向外,速度有分布,前快后慢:
118
+
119
+ ```js
120
+ const r = speed * Math.pow(age, 0.6); // 指数 < 1:一开始冲得快
121
+ ```
122
+
123
+ ### 4.2 汇聚
124
+
125
+ 爆发反过来:从四周向中心收,`1 - pow(1 - k, 3)`。
126
+
127
+ ### 4.3 飘浮
128
+
129
+ 速度极低 + 正弦扰动,像尘埃:
130
+
131
+ ```js
132
+ x: x0 + Math.sin(t * 0.7 + seed) * 18,
133
+ y: y0 - t * 12 + Math.cos(t * 0.5 + seed) * 10,
134
+ ```
135
+
136
+ ### 4.4 拖尾
137
+
138
+ 每个粒子画最近 4 个历史位置(用 `t - k/30` 重算,不要存):
139
+
140
+ ```js
141
+ for (let k = 0; k < 4; k++) {
142
+ const p = particle(i, t - k / 30);
143
+ if (!p) continue;
144
+ ctx.globalAlpha = p.alpha * Math.pow(0.5, k);
145
+ ctx.drawImage(dot, p.x - s, p.y - s, s * 2, s * 2);
146
+ }
147
+ ```
148
+
149
+ ---
150
+
151
+ ## 五、和文字/主体的关系
152
+
153
+ 粒子是背景时,**必须压暗**:alpha ≤ 0.35,或者只在外围分布,别盖住中心。
154
+
155
+ 判断方法:把画面缩到 25%,如果粒子还在抢视线,就再压。
156
+
157
+ ---
158
+
159
+ ## 六、自检清单
160
+
161
+ - [ ] 每个粒子的初始条件用索引做种子?
162
+ - [ ] 位置是 `t` 的解析函数,没有累加?
163
+ - [ ] 用贴图而不是 `arc` 路径?
164
+ - [ ] 粒子数在预算内(< 2000 直接画)?
165
+ - [ ] 背景粒子压暗到不抢主体?
166
+ - [ ] 混合模式统一,没有逐粒子切换?
@@ -0,0 +1,143 @@
1
+ # 编排
2
+
3
+ 单帧好看不算片子。整条片子的节奏,靠的是场景之间的长短、轻重、疏密。
4
+
5
+ 这一份讲:时间轴怎么切、节奏怎么排、一场戏该有多长、什么时候该停。
6
+
7
+ ---
8
+
9
+ ## 一、先把时间轴画出来
10
+
11
+ 写代码之前,先在纸上(或脑子里)排一条时间轴:
12
+
13
+ ```
14
+ 0.0 ─ 2.4 开场:logo + 标题
15
+ 2.4 ─ 3.6 数据:三个数字滚动
16
+ 3.6 ─ 6.0 演示:界面操作
17
+ 6.0 ─ 7.2 结论:一句话
18
+ 7.2 ─ 8.0 收尾:logo 淡出
19
+ ```
20
+
21
+ 每段写清三件事:起止时间、这一场要说什么、观众该看哪里。
22
+
23
+ **没有这条时间轴就开始写代码,十有八九写到一半发现时长不够或节奏拖沓。**
24
+
25
+ ---
26
+
27
+ ## 二、一场戏多长
28
+
29
+ | 类型 | 时长 | 说明 |
30
+ |---|---|---|
31
+ | 卡点镜头 | 0.4–0.8s | 一闪而过,配合重音 |
32
+ | 信息镜头 | 2–4s | 读一句话 + 看一张图 |
33
+ | 演示镜头 | 3–6s | 操作流程,要有过程 |
34
+ | 情绪镜头 | 2–5s | 慢,留白 |
35
+ | 收尾 | 1–2s | logo / 口号 |
36
+
37
+ 一条 30 秒的片子,通常 6–10 个镜头;48 秒的片子 8–12 个。
38
+
39
+ 镜头少于 5 个会显得空,多于 15 个会显得碎。
40
+
41
+ ---
42
+
43
+ ## 三、节奏曲线
44
+
45
+ 好片子不是匀速的。典型的曲线:
46
+
47
+ ```
48
+ 强度
49
+ │ ╱╲
50
+ │ ╱╲ ╱ ╲╱╲
51
+ │ ╱ ╲╱ ╲
52
+ └────────────────── 时间
53
+ 开场 铺垫 高潮 收尾
54
+ ```
55
+
56
+ - **开场**(前 15%):抓注意力,节奏快
57
+ - **铺垫**(15%–45%):给信息,节奏中等
58
+ - **高潮**(45%–80%):最密、最亮、最响
59
+ - **收尾**(80%–100%):迅速降下来
60
+
61
+ 全程一个强度,观众 10 秒就腻了。
62
+
63
+ ---
64
+
65
+ ## 四、疏密
66
+
67
+ | 密度 | 表现 | 用在哪 |
68
+ |---|---|---|
69
+ | 密 | 多个元素同时动、信息多、切得快 | 高潮、卖点罗列 |
70
+ | 疏 | 一个元素、大片留白、慢 | 开场、转折、收尾 |
71
+
72
+ **密之后必须有疏**,否则观众疲劳。连续 10 秒高密度,后面再炸也没感觉。
73
+
74
+ ---
75
+
76
+ ## 五、转场的节奏
77
+
78
+ | 段落 | 转场 |
79
+ |---|---|
80
+ | 开场 → 铺垫 | 硬切或快速擦除 |
81
+ | 铺垫内部 | 硬切为主 |
82
+ | 铺垫 → 高潮 | 叠化或闪白(有仪式感) |
83
+ | 高潮内部 | 硬切,越快越好 |
84
+ | 高潮 → 收尾 | 慢叠化(0.6s) |
85
+
86
+ ---
87
+
88
+ ## 六、开头三秒
89
+
90
+ 观众决定要不要看下去,就看这三秒。
91
+
92
+ 三种有效开场:
93
+
94
+ 1. **直接给结果**:先放成品画面,再解释怎么来的
95
+ 2. **反常识提问**:一句话制造信息缺口
96
+ 3. **强视觉**:一个高密度的画面,先抓住眼睛
97
+
98
+ 三种无效开场:
99
+
100
+ 1. logo 旋转三秒
101
+ 2. 「大家好,今天我们来聊聊……」
102
+ 3. 缓慢的淡入 + 空镜
103
+
104
+ ---
105
+
106
+ ## 七、结尾
107
+
108
+ 结尾只做一件事:**把话说完**。
109
+
110
+ | 做法 | 效果 |
111
+ |---|---|
112
+ | 一句话 + 留白 1s | 干净 |
113
+ | logo 淡入 | 品牌感 |
114
+ | 回到开场画面 | 闭环 |
115
+ | 加二维码 / 链接 | 转化(但别超过 2s) |
116
+
117
+ **不要在结尾堆信息**。观众这时候已经准备划走了。
118
+
119
+ ---
120
+
121
+ ## 八、和配乐的关系
122
+
123
+ | 段落 | 配乐 |
124
+ |---|---|
125
+ | 开场 | 起势(rise),不要一上来就满 |
126
+ | 铺垫 | 铺底 + 稀疏节奏 |
127
+ | 高潮 | 最满,重音密集 |
128
+ | 收尾 | 只留和弦,散开 |
129
+
130
+ 配乐的密度曲线必须和画面的密度曲线一致。画面在收,音乐还在炸,就是「两张皮」。
131
+
132
+ ---
133
+
134
+ ## 九、自检清单
135
+
136
+ - [ ] 写代码前有完整时间轴?
137
+ - [ ] 镜头数在 6–12 之间?
138
+ - [ ] 强度曲线有起伏(不是全程一样)?
139
+ - [ ] 密之后有疏?
140
+ - [ ] 开头三秒有钩子?
141
+ - [ ] 结尾只做一件事?
142
+ - [ ] 配乐密度和画面密度一致?
143
+ - [ ] 总时长和分镜一致(叠化有没有偷走时间)?