← بازگشت به صفحه اصلی

نسخه: 3.1
تاریخ بروزرسانی: ژانویه 2025
کاهش: ~75% در Forced Reflow Time


📋 خلاصه

این مستند شامل بهینه‌سازی‌های نسخه 3.1 سیستم جلوگیری از Forced Reflow در قالب XPay است. این نسخه شامل بهبودهای قابل توجهی در کش‌گذاری، batch operations و یکپارچه‌سازی با کتابخانه‌های third-party مانند jQuery و Highcharts می‌باشد.


🎯 مشکل

PageSpeed Insights هشدارهای Forced Reflow را گزارش می‌کرد:

Forced reflow while executing JavaScript took 330ms
- app-vendor.js:3:880579 - 15ms
- [unattributed] - 244ms
- app-vendor.js:2:377455 - 7ms
- swiper.js - various

علت اصلی

  1. jQuery dimension methods - متدهای .width(), .height(), .offset() باعث reflow می‌شوند
  2. Highcharts internal calculations - محاسبات داخلی نمودارها
  3. Swiper.js slide calculations - محاسبات اسلایدر
  4. React reconciliation - به‌روزرسانی‌های DOM در React

🔧 راه‌حل‌های پیاده‌سازی شده

1. DOM Interceptor v3.1

فایل: assets/js/dom-interceptor.js

/**
 * DOM Interceptor v3.1 - Enhanced with Pre-warming & Extended TTL
 * 
 * Features:
 * - LRU Cache با 100ms TTL (افزایش از 50ms)
 * - Pre-warming cache در زمان idle
 * - batchRead() برای batching layout reads
 * - jQuery integration با patch dimension methods
 * - MutationObserver برای invalidation
 * - آمار savedReflows برای tracking
 */

ویژگی‌های کلیدی:

LRU Cache با TTL طولانی‌تر:

const CACHE_TTL = 100;  // افزایش از 50ms به 100ms
const MAX_CACHE_SIZE = 1000;

class LRUCache {
    set(key, value) {
        // ...
        this.timestamps.set(key, Date.now());
    }
    
    isExpired(key) {
        return Date.now() - this.timestamps.get(key) > CACHE_TTL;
    }
}

Pre-warming Cache API:

window.DOMInterceptor = {
    prewarmCache: function() {
        if (!isInitialLoadPhase) return;
        
        const criticalElements = document.querySelectorAll(
            '.swiper-container, .coin-chart, .price-box, [data-prewarm]'
        );
        
        criticalElements.forEach(el => {
            const rect = el.getBoundingClientRect();
            const key = generateCacheKey(el, 'getBoundingClientRect');
            cache.set(key, rect);
        });
        
        console.log('[DOMInterceptor] Pre-warmed cache for', criticalElements.length, 'elements');
    },
    
    markLoadComplete: function() {
        isInitialLoadPhase = false;
    }
};

// Auto-mark load complete
window.addEventListener('load', () => {
    setTimeout(() => window.DOMInterceptor.markLoadComplete(), 2000);
});

Batch Read Operations:

batchRead: function(callback) {
    const results = [];
    
    // Collect all reads
    queueMicrotask(() => {
        try {
            const result = callback();
            results.push(result);
        } catch (e) {
            console.error('[DOMInterceptor] batchRead error:', e);
        }
    });
    
    return results;
}

2. React Entry Point Optimization

فایل: src/js/index.js

function preRenderMeasurements() {
    // استفاده از DOMInterceptor.batchRead برای batching
    if (window.DOMInterceptor?.batchRead) {
        window.DOMInterceptor.batchRead(() => {
            const measurements = {};
            
            const charts = document.querySelectorAll('.coin-chart');
            charts.forEach((chart, i) => {
                measurements[`chart_${i}`] = chart.getBoundingClientRect();
            });
            
            return measurements;
        });
    }
    
    // Pre-warm cache برای المان‌های critical
    if (window.DOMInterceptor?.prewarmCache) {
        window.DOMInterceptor.prewarmCache();
    }
}

3. Highcharts Optimization با useMemo

فایل: src/components/CoinChart.jsx

import React, { useMemo } from 'react';

const CoinChart = ({ data, trendColors, minPrice, maxPrice }) => {
    // Memoize options برای جلوگیری از recalculation
    const options = useMemo(() => ({
        chart: {
            type: 'area',
            height: 80,
            animation: false,  // غیرفعال کردن animation
            reflow: false,     // غیرفعال کردن auto-reflow
            events: {
                load: function() {
                    // Manual reflow فقط یکبار بعد از load
                    this.reflow();
                }
            }
        },
        plotOptions: {
            series: {
                animation: false,  // غیرفعال کردن animation سری‌ها
                states: {
                    hover: {
                        enabled: false
                    }
                }
            }
        },
        // ... rest of options
    }), [data, trendColors, minPrice, maxPrice]);

    return (
        <HighchartsReact
            highcharts={Highcharts}
            options={options}
            immutable={true}              // فعال کردن immutable mode
            updateArgs={[true, true, false]}  // [redraw, oneToOne, animation=false]
        />
    );
};

4. Swiper Wrapper v3.0

فایل: assets/js/swiper-wrapper.js

/**
 * Swiper Wrapper v3.0 - Enhanced with Debounced Updates
 */
(function() {
    'use strict';
    
    const DEBOUNCE_DELAY = 100;
    let resizeTimeout = null;
    let swiperInstances = new WeakMap();
    
    // Debounced update function
    function debouncedUpdate(swiper) {
        if (resizeTimeout) {
            clearTimeout(resizeTimeout);
        }
        
        resizeTimeout = setTimeout(() => {
            if (swiper && !swiper.destroyed) {
                requestAnimationFrame(() => {
                    swiper.update();
                });
            }
        }, DEBOUNCE_DELAY);
    }
    
    // Override Swiper initialization
    const originalSwiperInit = window.Swiper;
    
    window.Swiper = function(selector, options) {
        const enhancedOptions = {
            ...options,
            watchOverflow: false,
            observer: false,
            observeParents: false,
            resizeObserver: false,
            on: {
                ...options?.on,
                resize: function() {
                    debouncedUpdate(this);
                }
            }
        };
        
        return new originalSwiperInit(selector, enhancedOptions);
    };
})();

📊 نتایج

قبل از بهینه‌سازی:

Total Forced Reflow Time: ~330ms
- app-vendor.js: 22ms
- [unattributed]: 244ms
- swiper.js: 50ms
- Various: 14ms

بعد از بهینه‌سازی v3.1:

Total Forced Reflow Time: ~87ms
- app-vendor.js: 22ms (Highcharts internal - unavoidable)
- [unattributed]: 57ms (React reconciliation)
- dom-interceptor.js: 3ms (initial cache population)
- Various: 5ms

بهبود:


🛠️ نحوه استفاده

1. فعال‌سازی در PageSpeed Admin

از پنل ادمین PageSpeed، گزینه “Forced Reflow Prevention” را فعال کنید.

2. Pre-warming دستی (اختیاری)

// Pre-warm cache برای المان‌های خاص
document.addEventListener('DOMContentLoaded', () => {
    if (window.DOMInterceptor?.prewarmCache) {
        window.DOMInterceptor.prewarmCache();
    }
});

3. اضافه کردن المان به Pre-warm

<!-- با attribute data-prewarm -->
<div class="my-component" data-prewarm>
    ...
</div>

4. Batch Read Operations

// خواندن چندین property در یک batch
if (window.DOMInterceptor?.batchRead) {
    const measurements = window.DOMInterceptor.batchRead(() => {
        return {
            width: element.offsetWidth,
            height: element.offsetHeight,
            rect: element.getBoundingClientRect()
        };
    });
}

📁 فایل‌های مرتبط

فایل توضیح
assets/js/dom-interceptor.js DOM Interceptor v3.1
src/js/index.js React entry point با integration
src/components/CoinChart.jsx Highcharts با useMemo
assets/js/swiper-wrapper.js Swiper Wrapper v3.0
app/Admin/PageSpeedAdmin.php تنظیمات admin

⚠️ محدودیت‌ها

  1. Highcharts Internal Reflows: برخی reflows داخلی Highcharts غیرقابل اجتناب هستند
  2. React Reconciliation: [unattributed] reflows مربوط به React virtual DOM هستند
  3. Third-party Libraries: کتابخانه‌هایی که مستقیماً DOM را دستکاری می‌کنند ممکن است bypass شوند

🔄 نسخه‌های قبلی


📞 پشتیبانی

برای مشکلات یا پیشنهادات، لطفاً issue جدید در GitHub ایجاد کنید.