diff --git a/index.html b/index.html
index ae8fe48..f561094 100644
--- a/index.html
+++ b/index.html
@@ -158,6 +158,125 @@
}
footer a{color:var(--accent);text-decoration:none}
footer a:hover{text-decoration:underline}
+
+ /* ==== Live Demo 样式 ==== */
+ .demo{
+ background:linear-gradient(135deg,#121215,#181820);
+ border:1px solid var(--border);border-radius:10px;
+ margin:1.5rem 0;overflow:hidden;
+ }
+ .demo-head{
+ display:flex;align-items:center;justify-content:space-between;
+ padding:.7rem 1rem;border-bottom:1px solid var(--border);
+ background:#0f0f13;
+ }
+ .demo-title{font-size:.82rem;color:var(--accent);font-weight:600;letter-spacing:.02em}
+ .demo-title::before{content:"▶ ";color:var(--accent2)}
+ .demo-btns{display:flex;gap:.4rem}
+ .demo-btn{
+ background:#26262d;color:var(--fg);border:1px solid var(--border);
+ padding:.3rem .75rem;border-radius:5px;font-size:.78rem;
+ font-family:var(--mono);cursor:pointer;transition:all .15s;
+ }
+ .demo-btn:hover{background:var(--accent);color:#1a1a1e;border-color:var(--accent)}
+ .demo-btn.primary{background:var(--accent);color:#1a1a1e;border-color:var(--accent)}
+ .demo-btn.primary:hover{background:var(--accent3);border-color:var(--accent3)}
+ .demo-stage{
+ padding:1.25rem;min-height:180px;display:flex;
+ align-items:center;justify-content:center;
+ position:relative;overflow:hidden;
+ }
+ .demo-stage.tall{min-height:260px}
+
+ /* stagger grid */
+ .stagger-grid{display:grid;grid-template-columns:repeat(15,1fr);gap:6px;max-width:480px;width:100%}
+ .stagger-grid > div{
+ aspect-ratio:1;background:var(--accent);border-radius:50%;
+ opacity:.3;will-change:transform,opacity;
+ }
+
+ /* timeline demo */
+ .tl-row{display:flex;gap:1rem;width:100%;max-width:520px;justify-content:space-around}
+ .tl-row .box{
+ width:60px;height:60px;border-radius:10px;
+ display:flex;align-items:center;justify-content:center;
+ font-family:var(--mono);font-size:.85rem;font-weight:700;color:#1a1a1e;
+ }
+ .tl-box-a{background:var(--accent)}
+ .tl-box-b{background:var(--accent2)}
+ .tl-box-c{background:var(--accent3)}
+
+ /* additive demo */
+ .additive-stage{display:flex;justify-content:center;align-items:center;gap:2rem}
+ .additive-btn{
+ width:120px;height:120px;border-radius:20px;background:var(--accent3);
+ border:0;cursor:pointer;color:#1a1a1e;font-weight:700;font-size:.9rem;
+ will-change:transform;
+ }
+ .additive-hint{color:var(--muted);font-size:.82rem;font-family:var(--mono);line-height:1.5}
+
+ /* SVG demos */
+ .svg-row{display:flex;gap:1rem;flex-wrap:wrap;justify-content:center;width:100%}
+ .svg-card{
+ flex:1;min-width:170px;max-width:240px;
+ background:#0d0d10;border:1px solid var(--border);border-radius:8px;
+ padding:1rem;text-align:center;
+ }
+ .svg-card svg{display:block;margin:0 auto}
+ .svg-card-label{
+ font-size:.72rem;color:var(--accent2);font-family:var(--mono);
+ margin-top:.5rem;letter-spacing:.04em;
+ }
+
+ /* text split */
+ .text-demo-target{
+ font-size:2rem;font-weight:700;line-height:1.3;
+ background:linear-gradient(135deg,var(--accent),var(--accent3),var(--accent2));
+ -webkit-background-clip:text;-webkit-text-fill-color:transparent;
+ text-align:center;
+ }
+ .text-demo-target .letter{display:inline-block;will-change:transform,opacity}
+
+ /* draggable */
+ .drag-stage{position:relative;height:220px;background:#0d0d10;border:1px dashed #333;border-radius:8px;width:100%}
+ .drag-box{
+ position:absolute;top:50%;left:50%;margin:-40px 0 0 -40px;
+ width:80px;height:80px;background:var(--accent);border-radius:12px;
+ cursor:grab;display:flex;align-items:center;justify-content:center;
+ color:#1a1a1e;font-weight:700;user-select:none;
+ }
+ .drag-box:active{cursor:grabbing}
+
+ /* animatable mouse-follow */
+ .follow-stage{position:relative;height:220px;background:#0d0d10;border:1px solid var(--border);border-radius:8px;width:100%;cursor:crosshair}
+ .follow-dot{
+ position:absolute;top:0;left:0;width:28px;height:28px;
+ margin:-14px 0 0 -14px;background:var(--accent2);border-radius:50%;
+ pointer-events:none;box-shadow:0 0 20px var(--accent2);
+ }
+ .follow-hint{
+ position:absolute;inset:auto 1rem 1rem auto;font-size:.75rem;
+ color:var(--muted);font-family:var(--mono);
+ }
+
+ /* engine basic translate */
+ .basic-row{display:flex;gap:.5rem;align-items:center;justify-content:center;width:100%}
+ .basic-row .dot{
+ width:50px;height:50px;border-radius:50%;background:var(--accent);
+ will-change:transform;
+ }
+
+ /* hero animated logo */
+ h1 .hero-letter{display:inline-block;will-change:transform,opacity,color}
+
+ /* online badge */
+ .live-badge{
+ display:inline-block;background:var(--accent2);color:#0b0b0d;
+ font-size:.7rem;font-weight:700;padding:.1rem .5rem;border-radius:999px;
+ margin-left:.5rem;font-family:var(--mono);letter-spacing:.04em;
+ animation:pulse 2s ease-in-out infinite;
+ }
+ @keyframes pulse{0%,100%{opacity:1}50%{opacity:.6}}
@@ -206,8 +325,8 @@
- anime.js v4.3.6 源码深度解析
- Julian Garnier 重写版动画引擎的 11,121 行内部拆解。覆盖 Engine / Tween 值系统 / Composition / Timeline / Draggable / Layout FLIP / Scroll 全栈。
+ anime.js v4.3.6 源码深度解析LIVE DEMOS
+ Julian Garnier 重写版动画引擎的 11,121 行内部拆解。覆盖 Engine / Tween 值系统 / Composition / Timeline / Draggable / Layout FLIP / Scroll 全栈。每个核心章节嵌入 live demo —— 边看代码边看效果。
上游 juliangarnier/anime
版本 v4.3.6
@@ -655,6 +774,22 @@ lookupTween._fromNumber = toNumber;
典型场景
鼠标 hover → scale up(+0.1)
持续 idle wobble → scale ±0.05
点击 → shake scale ±0.2
三个动画同时生效时,scale 自动累加,而非互相覆盖。这正是 Animatable 内部用 blend 的原因。
+
+
+
+
blend composition — idle wobble + click shake 叠加不打架
+
+
+
+
+
+
+
+
+ 按钮同时在跑
· scale idle wobble
· rotate idle wobble
点击时 shake 叠加不干扰
+
+
+
@@ -709,6 +844,24 @@ lookupTween._fromNumber = toNumber;
}, position);
}
timeline.js:L256-265。用补间外部对象 currentTime 的方式,把 WAAPI Animation 驱动进 anime Timeline。非常机智的统一。
+
+
+
+
Timeline 位置语法 — 三个 box 用 < += label 衔接
+
+
+
+
+
+
+
+
@@ -740,6 +893,21 @@ lookupTween._fromNumber = toNumber;
if (axis === 'x') value = -distanceX; // 单轴模式
if (axis === 'y') value = -distanceY;
utils/stagger.js:L117-126。values 数组懒初始化 + 缓存,第一次调用时算完整个距离 map。
+
+
+
+
stagger({ grid: [15,8], from: 'center' }) 波纹
+
+
+
+
+
+
+
+
+
@@ -759,6 +927,23 @@ document.addEventListener('mousemove'
配合 blend composition,可实现"多输入源推同一对象,平滑叠加"—— 典型如粒子系统有重力、鼠标吸引、风力三种力同时作用。
+
+
+
+
createAnimatable 鼠标跟随 — 不同 easing 对比
+
+
+
+
+
+
+
+
@@ -863,6 +1048,41 @@ document.addEventListener('mousemove'
svg/drawable.js:L54-95。精妙之处:不引入新 CSS 属性,而是用 Proxy 拦截 setAttribute('draw', '0 0.5'),转译成 stroke-dasharray + stroke-dashoffset。且强行设置 pathLength=1000(L47, L96-97),让 [0, 1000] 成为规范化空间 —— 不管 path 真实长度多少,画线 API 一致。
+
+
+
+
SVG 三件套同台 — morphTo / motionPath / drawable
+
+
+
+
+
+
+
+
+
morphTo ← 星/心/圆
+
+
+
+
motionPath 沿曲线跑
+
+
+
+
drawable 画线 0→1
+
+
+
+
@@ -875,6 +1095,20 @@ document.addEventListener('mousemove'Line 重排:换行逻辑用 filterLineElements 递归剔除不属于此行的元素 + 相邻空白 textNode(避免孤零零残留)。split.js:L109-126
模板占位:{value} / {i} 支持用户自定义包装 HTML。split.js:L42-43
+
+
+
+
text.split + stagger — 文字逐字浮现
+
+
+
+
+
+
+
+
@@ -935,6 +1169,22 @@ velocity = max(...velocityStack);
临时清空祖先 transform 测量
draggable.js:L593:transforms.remove() 临时清除父元素 transform 才能用 getBoundingClientRect 拿到准确边界,然后再 revert。这个技巧在 Layout(L386-L392)和 Scroll(L774-L781)里也重复使用。
+
+
+
+
createDraggable — 拖我、撞墙会回弹
+
+
+
+
+
+
+
+
@@ -1162,5 +1412,278 @@ stickys.forEach(s => s.revert
}, { rootMargin: '-20% 0px -70% 0px' });
sections.forEach(s => io.observe(s));
+
+
+
+