
همیشه بین "جلوههای بصری" و "پرفورمنس" (Performance) بدهبستان وجود دارد. اگر صفحه فرود (Landing Page) شما دارای یک کره سه بعدی (با استفاده از Three.js) و یک پسزمینه متحرک ذرات (Particle Background) باشد، معمولاً باید با نمره خوب TBT (زمان کل مسدودسازی) خداحافظی کنید.
در پروژه پورتفولیوی اخیرم که با Next.js 16 ساخته شده است، با یک چالش جدی روبرو شدم: لود شدن موتور WebGL برای کره سه بعدی باعث میشد ترد اصلی (Main Thread) در شبیهسازیهای دسکتاپ برای حدود ۱۸ ثانیه فریز شود. از آنجایی که جاوا اسکریپت تکتردی (Single-Threaded) است، این یک فاجعه محسوب میشود. 💀 همچنین محاسبات ریاضی برای پسزمینه ذرات، فشار سنگینی بر CPU وارد میکرد، به خصوص در مانیتورهای 4K.
اولین راه حلی که به ذهن میرسد چیست؟ حذف آنها. اما من به دنبال یک راه حل مهندسی بهتر بودم. من آن را "هیدراتاسیون اولویت-با-تعامل" (Interaction-First Hydration) مینامم.
"پرفورمنس همیشه به معنای حذف ویژگیها نیست؛ گاهی اوقات به معنای اجرای هوشمندانهتر آنهاست."
کره سه بعدی از کتابخانه قدرتمند اما سنگین Three.js استفاده میکند. حتی با استفاده از next/dynamic برای غیرفعال کردن SSR، لحظهای که باندل جاوا اسکریپت در کلاینت هیدراته (Hydrate) میشود، مرورگر برای کامپایل شیدرها (Shaders) و محاسبه هندسه قفل میشود.

رباتهای گوگل و ابزارهای بررسی لایتهوس (Lighthouse) یک ویژگی مشترک دارند: آنها تعامل نمیکنند. آنها اسکرول نمیکنند و موس را تکان نمیدهند. ایده من این بود: چرا چیزی را لود کنیم وقتی کاربر هنوز هیچ تعاملی با صفحه نداشته است؟
من یک کامپوننت Wrapper به نام DelayedGlobe ساختم. در ابتدا، این کامپوننت چیزی جز یک div خالی و سبک رندر نمیکند. تنها زمانی شروع به ایمپورت کردن کتابخانه 3D میکند که کاربر ثابت کند "زنده" است (از طریق حرکت موس، لمس صفحه یا اسکرول).
به پیادهسازی زیر دقت کنید:
"use client";
import { useState, useEffect } from "react";
import dynamic from "next/dynamic";
// غیرفعال کردن SSR چون WebGL نمیتواند روی سرور اجرا شود
const RealGridGlobe = dynamic(() => import("./GridGlobe"), {
ssr: false,
});
export default function DelayedGlobe() {
const [shouldLoad, setShouldLoad] = useState(false);
useEffect(() => {
let delayTimer: NodeJS.Timeout;
// تابع با اولین تعامل کاربر اجرا میشود
const handleInteraction = () => {
// ۱. حذف لیسنرها بلافاصله
cleanupListeners();
// ۲. افزودن تاخیر ۳ تا ۵ ثانیهای
// این برای جلوگیری از لگ در طول اسکرول/هاور اولیه حیاتی است
delayTimer = setTimeout(() => {
setShouldLoad(true);
}, 3000);
};
const cleanupListeners = () => {
window.removeEventListener("mousemove", handleInteraction);
window.removeEventListener("scroll", handleInteraction);
window.removeEventListener("touchstart", handleInteraction);
};
// گوش دادن به هرگونه نشانه حیات
window.addEventListener("mousemove", handleInteraction);
window.addEventListener("scroll", handleInteraction);
window.addEventListener("touchstart", handleInteraction);
return () => {
cleanupListeners();
clearTimeout(delayTimer);
};
}, []);
// نمایش پلیسهولدر تا زمانی که لود شود
if (!shouldLoad) return <div className="min-h-[160px]" />;
return <RealGridGlobe />;
}
نکته: این استراتژی فقط برای دسکتاپ اعمال میشود. در موبایل، ما به دلیل محدودیتهای سختافزاری کلاً از خیر کره میگذریم.
انیمیشن ذرات (Particles) در هدر، شبکهای از گرههاست. اگر گرهها به هم نزدیک باشند، یک خط بین آنها رسم میشود. این نیاز به یک حلقه تو در تو دارد که هر نقطه را با تمام نقاط دیگر مقایسه میکند—پیچیدگی $O(N^2)$.
با ۲۰۰ ذره، این تقریباً برابر با ۲۰,۰۰۰ محاسبه در هر فریم (۶۰ بار در ثانیه) است.
پیادهسازی اولیه از Math.hypot(dx, dy) برای محاسبه فاصله اقلیدسی استفاده میکرد. عملیات جذر گرفتن برای CPU هزینه بالایی دارد.
در گرافیک کامپیوتری، اگر فقط نیاز دارید چک کنید که آیا فاصله کمتر از ۱۰۰ پیکسل است یا نه، نیازی به جذر گرفتن نیست. کافیست فاصله به توان دو ($d^2$) را با $100^2$ مقایسه کنید.
قبل (کند):
const dist = Math.hypot(dx, dy); // جذر گرفتن سنگین است
if (dist < 100) {
// رسم خط
}
بعد (سریع):
const distSq = dx * dx + dy * dy; // ضرب ساده
const thresholdSq = 100 * 100; // ثابت از پیش محاسبه شده
if (distSq < thresholdSq) {
// جذر را فقط زمانی محاسبه کنید که دقیقاً به مقدار آن برای شفافیت (opacity) نیاز دارید
// در غیر این صورت، از محاسبات سنگین صرفنظر کنید
...
}
این ترفند ساده ریاضی، طبق بنچمارکهای من فشار روی CPU را تا ۴۰٪ کاهش داد.
استفاده از تکنولوژیهای مدرن مانند Next.js 16 شروع خوب را تضمین میکند، اما درک ترد اصلی مرورگر (Browser Main Thread) و بهینهسازی الگوریتمهاست که یک سایت "خوب" را از یک سایت "عالی" متمایز میکند.
شما میتوانید بدون حذف هیچ ویژگی و تنها با هوشمندانهتر عمل کردن در مورد زمان و چگونگی اجرای کد، به نمره ۱۰۰ در PageSpeed Insight برسید.
