Browse Source

基础animal 增强

robert 4 days ago
parent
commit
68caa151a8
2 changed files with 156 additions and 22 deletions
  1. 75 9
      animal.js
  2. 81 13
      doc/animal.html

+ 75 - 9
animal.js

@@ -82,6 +82,9 @@
         requestAnimationFrame(this.tick);
       }
     }
+    remove(anim) {
+      this.animations = this.animations.filter(a => a !== anim);
+    }
     tick(t) {
       const now = t;
       this.animations = this.animations.filter(anim => {
@@ -140,6 +143,9 @@
       }
     });
 
+    // Create animations but don't play if autoplay is false
+    const animations = [];
+
     const promises = elements.map(el => {
       // 1. WAAPI Animation
       let waapiAnim = null;
@@ -232,14 +238,21 @@
         };
 
         const animation = el.animate(finalFrames, opts);
+        if (!autoplay) animation.pause();
         waapiAnim = animation;
         waapiPromise = animation.finished;
+        animations.push(waapiAnim);
       }
 
       // 2. JS Animation (Fallback / Attributes)
       let jsPromise = Promise.resolve();
       if (Object.keys(jsProps).length > 0) {
           jsPromise = new Promise(resolve => {
+             // For JS engine to support manual seek/scroll, we need to structure it differently.
+             // But for this lite version, we focus on WAAPI for scroll linking.
+             // JS fallback will just run normally if autoplay.
+             if (!autoplay) return; // JS engine simplified doesn't support pause/seek easily yet
+
              const startTime = performance.now() + delay;
              const initialValues = {};
              
@@ -248,8 +261,7 @@
                  if (k === 'scrollTop' || k === 'scrollLeft') {
                      initialValues[k] = el[k];
                  } else if (k in el.style) {
-                     // For style props not in WAAPI whitelist (like strokeDashoffset)
-                     initialValues[k] = parseFloat(el.style[k]) || 0; // simplistic
+                     initialValues[k] = parseFloat(el.style[k]) || 0; 
                  } else if (isSVG(el) && el.hasAttribute(k)) {
                      initialValues[k] = parseFloat(el.getAttribute(k)) || 0;
                  } else {
@@ -273,9 +285,8 @@
                     if (k === 'scrollTop' || k === 'scrollLeft') {
                         el[k] = val;
                     } else if (k in el.style) {
-                        el.style[k] = val; // Set as style
+                        el.style[k] = val; 
                     } else if (isSVG(el)) {
-                        // Kebab-case conversion for setAttribute
                         const attr = k.replace(/[A-Z]/g, m => '-' + m.toLowerCase());
                         el.setAttribute(attr, val);
                     } else {
@@ -299,6 +310,9 @@
 
     const masterPromise = Promise.all(promises);
     masterPromise.then = (fn) => Promise.all(promises).then(fn);
+    
+    // Attach animations to promise for external control (like scroll)
+    masterPromise.animations = animations;
     return masterPromise;
   }
 
@@ -311,7 +325,6 @@
          el.style.strokeDasharray = len;
          el.style.strokeDashoffset = len; 
          
-         // Animate offset to 0
          animate(el, {
              strokeDashoffset: [len, 0],
              ...params
@@ -333,6 +346,61 @@
       
       elements.forEach(el => observer.observe(el));
   }
+  
+  // --- Scroll Linked ---
+  function scroll(animationPromise, options = {}) {
+      // options: container (default window), range [start, end] (default viewport logic)
+      const container = options.container || window;
+      const target = options.target || document.body; // Element to track for progress
+      // If passing an animation promise, we control its WAAPI animations
+      
+      const anims = animationPromise.animations || [];
+      if (!anims.length) return;
+
+      const updateScroll = () => {
+          let progress = 0;
+          
+          if (container === window) {
+              const scrollY = window.scrollY;
+              const winH = window.innerHeight;
+              const docH = document.body.scrollHeight;
+              
+              // Simple progress: how far down the page (0 to 1)
+              // Or element based?
+              // Motion One defaults to element entering view.
+              
+              if (options.target) {
+                  const rect = options.target.getBoundingClientRect();
+                  const start = winH; 
+                  const end = -rect.height;
+                  // progress 0 when rect.top == start (just entering)
+                  // progress 1 when rect.top == end (just left)
+                  
+                  const totalDistance = start - end;
+                  const currentDistance = start - rect.top;
+                  progress = currentDistance / totalDistance;
+              } else {
+                  // Whole page scroll
+                  progress = scrollY / (docH - winH);
+              }
+          }
+          
+          // Clamp
+          progress = Math.max(0, Math.min(1, progress));
+          
+          anims.forEach(anim => {
+             if (anim.effect) {
+                 const dur = anim.effect.getComputedTiming().duration;
+                 anim.currentTime = dur * progress;
+             }
+          });
+      };
+
+      window.addEventListener('scroll', updateScroll);
+      updateScroll(); // Initial
+      
+      return () => window.removeEventListener('scroll', updateScroll);
+  }
 
   // --- Timeline ---
   function timeline(defaults = {}) {
@@ -348,9 +416,6 @@
                   else if (typeof offset === 'number') start = offset; 
                }
                
-               // Use setTimeout to defer EXECUTION until the exact start time
-               // This ensures that "implicit from" values are read correctly from the DOM
-               // at the moment the animation is supposed to start.
                if (start <= 0) {
                    animate(targets, animParams);
                } else {
@@ -372,7 +437,8 @@
     timeline,
     svgDraw,
     inViewAnimate,
-    spring
+    spring,
+    scroll
   };
 
 })));

+ 81 - 13
doc/animal.html

@@ -11,9 +11,38 @@
         .circle { width: 50px; height: 50px; background: #e74c3c; border-radius: 50%; }
         .btn { padding: 8px 16px; cursor: pointer; background: #333; color: white; border: none; border-radius: 4px; margin-top: 10px; }
         svg { border: 1px solid #ddd; }
+
+        /* Styles for Section 5 */
+        .scroll-section {
+            padding: 50px 0;
+            display: flex;
+            flex-direction: column;
+            gap: 400px; /* Large gaps to force scrolling */
+            align-items: center;
+        }
+        .card { 
+            width: 150px; height: 200px; 
+            background: linear-gradient(135deg, #6e8efb, #a777e3); 
+            border-radius: 8px; 
+            box-shadow: 0 10px 20px rgba(0,0,0,0.1);
+            display: flex; align-items: center; justify-content: center;
+            color: white; font-weight: bold; font-size: 32px;
+            opacity: 0; 
+        }
+
+        /* Styles for Section 6 (Scroll Linked) */
+        .progress-bar {
+            position: fixed; top: 0; left: 0; height: 5px; background: red; width: 0%; z-index: 100;
+        }
+        .scroll-box {
+            width: 100px; height: 100px; background: #2c3e50; margin: 50px auto;
+            color: white; display: flex; align-items: center; justify-content: center;
+        }
     </style>
 </head>
 <body>
+    <div class="progress-bar"></div>
+
     <h1>Animal.js Demo</h1>
 
     <section>
@@ -47,12 +76,22 @@
     </section>
 
     <section>
-        <h2>5. In View Trigger</h2>
-        <p>Scroll down to see the animation...</p>
-        <div style="height: 110vh; background: linear-gradient(to bottom, #f9f9f9, #eee); display: flex; align-items: center; justify-content: center; color: #999;">
-            Scroll Spacer (Keep scrolling)
+        <h2>5. In View Trigger (Staggered Scroll)</h2>
+        <p>Scroll down slowly. Cards will appear as they enter the view.</p>
+        
+        <div class="scroll-section">
+            <div class="card card-1">1</div>
+            <div class="card card-2">2</div>
+            <div class="card card-3">3</div>
         </div>
-        <div class="box box-view" style="opacity: 0;"></div>
+        <p style="text-align: center; color: #aaa;">(End of trigger section)</p>
+    </section>
+
+    <section>
+        <h2>6. Scroll Linked Animation</h2>
+        <p>This box rotates and moves based on scroll position of the page.</p>
+        <div class="scroll-box">Rotate</div>
+        <div style="height: 50vh;"></div>
     </section>
 
     <script src="../animal.js"></script>
@@ -94,16 +133,45 @@
             animal.svgDraw('.path-1', { duration: 1500, easing: 'ease-in-out' });
         }
 
-        // 5. In View
-        // Use explicit from-to for spring to ensure correct start position
-        animal.inViewAnimate('.box-view', {
-            x: [-100, 0], 
-            opacity: [0, 1],
+        // 5. In View (Trigger)
+        // Staggered logic: observe each
+        const cards = document.querySelectorAll('.card');
+        cards.forEach((card, i) => {
+             // Alternate left/right entrance
+             const xStart = i % 2 === 0 ? -100 : 100;
+             animal.inViewAnimate(card, {
+                x: [xStart, 0],
+                opacity: [0, 1],
+                scale: [0.8, 1],
+                duration: 1000,
+                easing: { stiffness: 100, damping: 15 }
+             }, { threshold: 0.2 });
+        });
+
+        // 6. Scroll Linked
+        // 1. Progress Bar (Page Scroll)
+        const progressAnim = animal.animate('.progress-bar', {
+            width: ['0%', '100%'],
+            duration: 1000, // Duration doesn't matter much for scroll linked, just maps 0-1
+            autoplay: false
+        });
+        animal.scroll(progressAnim); // Default: window scroll
+
+        // 2. Box Rotation (Element Scroll Visibility)
+        const boxAnim = animal.animate('.scroll-box', {
+            rotate: 360,
+            x: 200,
+            backgroundColor: '#e74c3c',
             duration: 1000,
-            easing: { stiffness: 50, damping: 15 } // slow spring
-        }, { threshold: 0.5 });
+            autoplay: false
+        });
+        // Scroll linked to the entire page scroll (simplest demo)
+        // Or we can link it to when the section is in view if we implemented element-target logic in animal.js
+        // animal.js current `scroll` implementation supports `target` for element visibility progress!
+        
+        // Let's link it to the page scroll for clarity
+        animal.scroll(boxAnim);
 
     </script>
 </body>
 </html>
-