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 衔接
+
+ + + +
+
+
+
+
A
+
B
+
C
+
+
+

@@ -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 — 文字逐字浮现
    +
    + + + +
    +
    +
    +
    anime.js · 每一个字都会动
    +
    +
    @@ -935,6 +1169,22 @@ velocity = max(...velocityStack);

    临时清空祖先 transform 测量

    draggable.js:L593transforms.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)); + + + +