Skip to content

[Performance] Motion Components Bundle Size and Runtime Optimization #253

@andrewck24

Description

@andrewck24

⚡ 效能類型

打包大小

📊 問題描述

根據 motion.dev 官方文件分析和程式碼審查,目前專案中的 motion components 實作存在以下優化機會:

Primary Issues (主要問題)

  1. Bundle Size 過大: 使用完整的 motion 組件 (34kb) 而非輕量版本
  2. Feature Loading: 未使用 LazyMotion 進行功能分割和延遲載入
  3. Tree Shaking: 未充分利用 bundler 的 tree-shaking 能力

Minor Issues Identified (次要問題)

  1. Potential Motion Warnings (Low Priority)

    • Location: src/components/landing/cta-section.tsx:47-92
    • Issue: 三個浮動元素可能觸發 React 關於 motion props 的警告
    <motion.div
      className="absolute top-10 left-10..."
      animate={{ /* complex animation */ }}
    />
    • Impact: 開發環境中的裝飾性 console 警告
    • Recommendation: 監控 React warnings about motion props on DOM elements
  2. Animation Performance on Low-End Devices (Low Priority)

    • Location: Background blur effects with multiple simultaneous animations
    • Issue: 多重模糊效果與同時動畫可能在低階裝置造成效能問題
    • Recommendation: 考慮新增 prefers-reduced-motion 媒體查詢支援以提升無障礙性

當前實作問題:

// src/components/landing/cta-section.tsx
import { motion } from "motion/react";  // 34kb 完整版本

// 多重複雜動畫同時執行 - 可能觸發警告
<motion.div animate={{ scale: [1, 1.2, 1], opacity: [0.3, 0.6, 0.3] }} />

📈 效能數據

當前 Bundle 影響:

  • motion 組件: ~34kb (未 tree-shake)
  • 實際使用功能: 僅動畫和基礎手勢
  • 建議優化後: ~4.6kb (初始) + 15kb (domAnimation 延遲載入)
  • 潛在節省: ~14.4kb (-42% bundle size)

Runtime 數據:

  • 同時動畫元件: 3個浮動元素
  • 動畫屬性: scale, opacity, x, y 軸變換
  • 模糊效果: blur-3xl, blur-2xl 同時渲染
  • Console warnings: 開發環境中可能出現 motion props 相關警告

建議測量工具:

  • Bundlephobia
  • Webpack Bundle Analyzer
  • Chrome DevTools Performance tab
  • React DevTools Profiler

🖥️ 測試環境

  • OS: macOS 15.6.1
  • Node.js: 當前專案版本
  • 瀏覽器: Chrome, Safari, Firefox
  • Bundler: Webpack (Next.js 內建)

🔄 重現方式

1. Bundle Size 分析

npm run build
npm install -g webpack-bundle-analyzer
npx webpack-bundle-analyzer .next/static/chunks/

2. Motion Warnings 檢查

npm run dev
# 開啟瀏覽器 DevTools > Console
# 導航至 landing page
# 查看是否有 motion props 相關警告

3. 低階裝置效能測試

# 啟動開發伺服器
npm run dev

# Chrome DevTools > Performance
# 設定 CPU throttling 為 4x slowdown
# 記錄 Landing page 載入和滾動互動
# 觀察 CTASection 動畫的 frame rate

4. 載入效能測試

npm run build
npx lighthouse http://localhost:3000 --only-categories=performance

💡 優化建議

1. 採用 LazyMotion + m 組件 (高優先級)

目標: 減少 42% bundle size (34kb → 19.6kb)

// 當前實作 - 完整 motion 組件 (34kb)
import { motion } from "motion/react";

// 建議實作 - LazyMotion + m 組件 (4.6kb 初始)
import { LazyMotion, domAnimation } from "motion/react";
import * as m from "motion/react-m";

// 1. App 層級設定 LazyMotion
function App({ children }) {
  return (
    <LazyMotion features={domAnimation} strict>
      {children}
    </LazyMotion>
  );
}

// 2. 組件中使用 m 替代 motion
export const CTASection = () => {
  return (
    <m.section
      data-testid="cta-section"
      className="relative mx-6 overflow-hidden rounded-lg bg-card px-4 py-24 text-center lg:mx-12"
      initial={{ opacity: 0, y: 20 }}
      whileInView={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.6 }}
      viewport={{ once: true }}
    >
      {/* 其餘實作相同 */}
    </m.section>
  );
};

2. 修正 Motion Props 警告 (中優先級)

// 問題位置: src/components/landing/cta-section.tsx:47-92
// 當前可能觸發警告的實作
<motion.div
  className="absolute top-10 left-10..."
  animate={{ scale: [1, 1.2, 1], opacity: [0.3, 0.6, 0.3] }}
/>

// 建議修正: 確保正確使用 motion components
<m.div
  className="absolute top-10 left-10 will-change-transform"
  animate={{ 
    scale: [1, 1.1, 1], // 減少變化幅度
    opacity: [0.4, 0.6, 0.4] 
  }}
  transition={{ 
    duration: 6, // 延長動畫減少 CPU 負擔
    repeat: Infinity,
    ease: "easeInOut"
  }}
/>

3. 新增 prefers-reduced-motion 支援 (高優先級)

// 解決低階裝置效能問題
import { useReducedMotion } from "motion/react";

const CTABackgroundEffects = () => {
  const shouldReduceMotion = useReducedMotion();
  
  if (shouldReduceMotion) {
    return (
      <div
        data-testid="cta-background-effects"
        className="pointer-events-none absolute inset-0 z-10"
      >
        {/* 靜態背景效果,無動畫 */}
        <div className="absolute top-10 left-10 h-64 w-64 rounded-full bg-primary/20 blur-3xl" />
        <div className="absolute right-10 bottom-10 h-48 w-48 rounded-full bg-secondary/30 blur-3xl" />
      </div>
    );
  }
  
  return (
    // 原始動畫實作
    <div
      data-testid="cta-background-effects" 
      className="pointer-events-none absolute inset-0 z-10"
    >
      {/* 動畫版本 */}
    </div>
  );
};

4. 延遲載入進階功能 (中優先級)

// features.js - 分離功能載入
import { domAnimation } from "motion/react";
export default domAnimation;

// App.tsx - 延遲載入
import { LazyMotion } from "motion/react";

const loadFeatures = () =>
  import("./features.js").then(res => res.default);

function App() {
  return (
    <LazyMotion features={loadFeatures} strict>
      {/* 動畫將在 loadFeatures 解析後執行 */}
      {children}
    </LazyMotion>
  );
}

5. 功能包選擇最佳化 (中優先級)

// 當前需求分析:
// ✅ 需要: animations, variants, tap/hover/focus gestures
// ❌ 不需要: pan/drag gestures, layout animations

// 建議使用 domAnimation (15kb) 而非 domMax (25kb)
import { domAnimation } from "motion/react";  // +15kb
// 避免: import { domMax } from "motion/react";     // +25kb

6. 嚴格模式防護 (高優先級)

// 防止意外使用完整 motion 組件
<LazyMotion features={domAnimation} strict>
  {/* 這將拋出錯誤提醒 */}
  {/* <motion.div /> */}
  
  {/* 正確使用 */}
  <m.div />
</LazyMotion>

7. 測試環境相容性更新 (必須)

// jest.setup.ts - Mock 更新
jest.mock("motion/react", () => ({
  LazyMotion: ({ children }: any) => children,
  domAnimation: {},
  useReducedMotion: () => false, // 測試環境預設啟用動畫
}));

jest.mock("motion/react-m", () => ({
  m: {
    section: ({ children, className, ...props }: any) => (
      <section className={className} {...props}>
        {children}
      </section>
    ),
    div: ({ children, className, ...props }: any) => (
      <div className={className} {...props}>
        {children}
      </div>
    ),
  },
}));

8. 動畫效能優化 (低優先級)

// 優化多重模糊效果的渲染
const optimizedEffects = `
  .floating-effect {
    transform: translateZ(0); /* 強制硬體加速 */
    backface-visibility: hidden; /* 避免背面渲染 */
    perspective: 1000px; /* 3D 加速 */
    will-change: transform; /* 預告瀏覽器即將變換 */
  }
`;

// 錯開動畫啟動時間,避免同時計算
const variants = {
  hidden: { opacity: 0, scale: 0.8 },
  visible: (i: number) => ({
    opacity: [0.3, 0.6, 0.3],
    scale: [1, 1.1, 1],
    transition: {
      delay: i * 0.5, // 錯開 0.5s
      duration: 4,
      repeat: Infinity,
    }
  })
};

🚀 實作時程建議

Phase 1 (本週): 核心優化

  • 在 App 層級加入 LazyMotion wrapper
  • 將 CTASection 的 motion 改為 m
  • 使用 domAnimation 功能包
  • 更新測試 mock
  • 新增 prefers-reduced-motion 支援

Phase 2 (下週): 進階優化

  • 實作 features.js 分離載入
  • 啟用 strict mode
  • 修正 motion props 警告
  • Bundle size 驗證測試

Phase 3 (後續): 監控與維護

  • 設定 bundle 大小 CI 檢查
  • Lighthouse 效能回歸測試
  • 低階裝置效能監控
  • 動畫效能深度優化

✅ 成功指標

效能指標

  • Bundle Size: 減少 14.4kb (-42%)
  • 初始載入: 4.6kb motion 相關程式碼
  • 延遲載入: 15kb domAnimation 功能
  • 載入時間: Lighthouse Performance score 提升

品質指標

  • Console 清潔: 開發環境無 motion warnings
  • 無障礙性: 支援 prefers-reduced-motion 使用者
  • 效能: 低階裝置保持 30+ FPS
  • 開發體驗: strict mode 防止回歸

測試指標

  • 測試通過: 所有現有測試繼續通過
  • Mock 更新: 新的 motion 架構正確模擬
  • 覆蓋率: 維持現有的測試覆蓋率

🔧 驗證方法

# 1. Bundle 分析
npm run build
du -sh .next/static/chunks/pages/_app-*.js

# 2. Bundlephobia 檢查
# Before: motion/react (~34kb)  
# After: motion/react-m + domAnimation (~19.6kb)

# 3. Lighthouse 效能測試
npx lighthouse http://localhost:3000 --only-categories=performance

# 4. 測試環境驗證
npm test -- src/components/landing/__tests__/features.test.tsx

# 5. Console 警告檢查
npm run dev
# 檢查瀏覽器 Console 是否有 motion 相關警告

📈 預期結果

根據官方文件,預期可達成:

  • Initial Bundle: 4.6kb (m + LazyMotion)
  • Feature Bundle: 15kb (domAnimation)
  • Total Impact: 19.6kb vs 目前 34kb
  • 節省比例: 42% bundle size 減少
  • 載入改善: 首次載入時間顯著降低
  • 使用體驗: 在行動裝置和慢速網路環境下的體驗提升

🔗 相關資源

📝 後續行動

此優化將顯著改善首次載入效能,同時解決開發環境警告和低階裝置效能問題。建議優先處理 LazyMotion 實作和 prefers-reduced-motion 支援,因為這些直接影響使用者體驗和無障礙性。

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions