شاشة التحميل

Starter pack

في تجارب ثلاثية الأبعاد، من الشائع وجود نماذج معقدة وملمس كبير. يمكن أن يؤدي هذا إلى فترات تحميل طويلة. لتجنب الحصول على شاشة فارغة والانطباعات الأولى السيئة، يمكننا إضافة شاشة تحميل.

أعتقد شخصياً أنه عندما يكون ذلك ممكنا، يجب تجنب شاشة التحميل (إلا إذا كان اختيار تصميم). يجب أن تؤخذ تحسين النماذج والقوام في الاعتبار أولاً.

ولكن في بعض الأحيان ليس لديك خيار، وحتى النماذج المحسنة يمكن أن تبقى كبيرة أو لديك العديد منها. في هذه الحالة، يمكن أن تكون شاشة التحميل حلاً جيدًا.

Suspense

Suspense هو مكوّن تغليف في React يسمح لك بعرض مكوّن مؤقت أثناء انتظار تحميل البيانات.

تُشير تلك الـبيانات في التطبيقات التقليدية عادة إلى جلب البيانات أو تقسيم الكود. ولكن في React Three Fiber، يمكننا استخدامه للانتظار حتى تحميل النماذج والقوام.

useLoader هوك يستخدم الوعود لـتحميل النماذج والقوام. لذا يمكننا استخدام Suspense للانتظار حتى يتم حلها.

في starter pack لقد أضفت 4 نماذج في public/models/ كل منها بحجم +3MB. (وهو الكثير لتطبيق ويب حيث أنها تحتوي على نفس الرسوم المتحركة ويمكن/ينبغي تحسينها) سيمكننا هذا من رؤية كيفية عمل Suspense.

عندما أقوم بإعادة التحميل، لدينا شاشة فارغة طويلة قبل أن يتم تحميل النماذج. هذا ليس تجربة مستخدم جيدة، يمكن للمستخدمين أن يعتقدوا أن التطبيق معطل ويغادرون.

إذا كنت تعمل محليًا أو لديك النماذج مخبأة في متصفحك، قد لا ترى الشاشة الفارغة.

لإجبار إعادة تحميل الموارد، يمكنك تعطيل التخزين المؤقت في علامة تبويب الشبكة في أدوات المطور الخاصة بالمتصفح.

يمكنك أيضًا إبطاء الشبكة مع خيار Throttling.

تعطيل التخزين المؤقت وإبطاء الشبكة

الآن يجب أن نرى كلنا الشاشة الفارغة، لنضيف مكوّن Suspense مع خاصية fallback:

import { Canvas } from "@react-three/fiber";
import { Experience } from "./components/Experience";
import { Suspense } from "react";

const CubeLoader = () => {
  return (
    <mesh>
      <boxGeometry />
      <meshNormalMaterial />
    </mesh>
  );
};

function App() {
  return (
    <>
      <Canvas camera={{ position: [-4, 4, 12], fov: 30 }}>
        <Suspense fallback={<CubeLoader />}>
          <group position-y={-1}>
            <Experience />
          </group>
        </Suspense>
      </Canvas>
    </>
  );
}

export default App;

مكوّن fallback الخاص بنا هو مكعب بسيط، ليس أفضل شاشة تحميل، ولكنه كافٍ لفهم كيفية عمل Suspense.

عندما نعيد التحميل، يمكننا أن نرى أن المكعب يُعرض أثناء تحميل النماذج. عندما يتم تحميلها، يتم استبدال المكعب بمكوّن Experience.

Preload

مشكلة شائعة يمكن أن تواجهها هي أن الموارد الخاصة بك ليست مطلوبة فورًا. على سبيل المثال، قد يكون لديك نموذج يتم تحميله فقط عندما ينقر المستخدم على زر. لذا، لن يتم أخذه بعين الاعتبار من قبل Suspense.

يمكن أن يؤدي ذلك إلى عرض مكون fallback مرة أخرى عند الحاجة إلى المورد. لتجنب ذلك، يمكننا استخدام وظيفة preload من Drei loaders المختلفة.

دعونا نؤخر عرض الـ King لثانية واحدة:

export const Experience = () => {
  const [kingVisible, setKingVisible] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      setKingVisible(true);
    }, 1000);
  }, []);

  return (
    <>
      {/* ... */}
      {kingVisible && <King position-x={-3} rotation-y={-Math.PI / 4} />}
      {/* ... */}
    </>
  );
};

عند إعادة التحميل، يمكننا أن نرى أن الـ King لم يتم عرضه فورًا. لذا، Suspense لن ينتظر تحميله مما يؤدي إلى عرض مكون fallback مرة أخرى عندما يتطلب الـ King. (حتى وإن كان لفترة قصيرة جدًا كما أنني أعمل محليًا)

يمكننا أيضًا أن نرى في علامة تبويب Network أن الـ King يتم تحميله فقط بعد مرور delay.

لمنع ذلك، يمكننا استخدام وظيفة preload من الـ useGLTF hook. في نهاية ملف King.jsx، دعونا نضيف:

useGLTF.preload("/models/King.gltf");

انتبه لاستخدام المسار نفسه بالضبط كما هو مستخدم في الـ useGLTF hook.

الآن النموذج يتم تحميله من البداية، وSuspense سينتظر تحميله.

هذه غالبًا ما تكون حلًا جيدًا، ولكن استنادًا إلى المشروع، قد تقرر تحميل الموارد عند الطلب.

على سبيل المثال، إذا كان موردًا كبيرًا يصل إليه 5% فقط من المستخدمين، فقد يكون من الأفضل تحميله فقط عند الحاجة.

ملاحظة: بشكل افتراضي، gltfjsx تلقائيًا سيضيف كود preload في نهاية الملف.

يمكنك إزالة التأخير على الـ King ومكون CubeLoader fallback كان ذلك لأغراض التوضيح.

useProgress

الآن نفهم كيف يعمل Suspense مع React Three Fiber، دعونا نكتشف hook useProgress من Drei للحصول على التقدم في تحميل الموارد لدينا.

يُعيد هذا hook كائنًا يحتوي على الخصائص التالية:

  • active: boolean - true إذا كان المحمل نشطًا
  • progress: number - النسبة المئوية للتحميل
  • errors: any[] - مصفوفة من الأخطاء التي حدثت أثناء التحميل
  • item: any - العنصر الحالي الذي يتم تحميله
  • loaded: number - عدد العناصر المحملة
  • total: number - العدد الكلي للعناصر لتحميلها

لنقم بإنشاء مكون LoadingScreen باستخدام useProgress:

// ...
import { useProgress } from "@react-three/drei";

const LoadingScreen = () => {
  const { progress } = useProgress();

  return (
    <div className="loading-screen">
      <div className="loading-screen__container">
        <h1 className="loading-screen__title">3D Web Agency</h1>
        <p>Loading... ({parseInt(progress)}%)</p>
      </div>
    </div>
  );
};

function App() {
  return (
    <>
      <LoadingScreen />
      <Canvas camera={{ position: [-4, 4, 12], fov: 30 }}>{/* ... */}</Canvas>
    </>
  );
}

export default App;

في الوقت الحالي سنبقيه بسيطًا جدًا، سنعرض فقط التقدم في وسم p.

Loading screen html

يمكننا أن نرى أن progress يتم تحديثه أثناء التحميل ليصل إلى 100٪، لكن يحتاج إلى بعض التنسيق...

كما فعلت في درس HTML، سأكتب CSS عادي لكن استخدم حل التنسيق المفضل لديك. للحفاظ على الهيكلة سأتبع منهجية BEM (Block Element Modifier).

في index.css، دعونا نجعل شاشة التحميل كاملة الشاشة، نضيف خلفية متدرجة رمادي/أبيض، ونقوم بـ مركزة المحتوى:

.loading-screen {
  position: fixed;
  top: 0;
  left: 0;
  padding: 4rem;
  width: 100vw;
  height: 100vh;
  z-index: 1;
  display: grid;
  place-items: center;
  background-color: #b8c6db;
  background-image: linear-gradient(0deg, #b8c6db 0%, #f5f7fa 74%);
}

لجعلها تبدو أكثر جاذبية، دعونا نضيف خط Inter من Google Fonts:

@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;900&display=swap");

/* ... */

body {
  margin: 0;
  font-family: "Inter", sans-serif;
}

/* ... */

واجعل title أكبر:

/* ... */

.loading-screen__title {
  font-size: 4rem;
  font-weight: 900;
  text-transform: uppercase;
  color: #1a202c;
  margin: 0;
}

Loading screen css

الآن شاشتنا الكاملة مغطاة بشاشة التحميل الخاصة بنا

بدلاً من عرض progress كنص، دعونا نعرضه كـ شريط تقدم. للقيام بذلك، سنستخدم div مع background-color الذي سيتم تحريكه بقيمة progress.

// ...
<div className="loading-screen__container">
  <h1 className="loading-screen__title">3D Web Agency</h1>
  <div className="progress__container">
    <div className="progress__bar" style={{ width: `${progress}%` }}></div>
  </div>
</div>
// ...

نقوم بتعيين width لـ progress__bar لقيمة progress.

.progress__container {
  width: 100%;
  height: 1rem;
  background-color: rgb(102 106 113 / 42%);
  position: relative;
  overflow: hidden;
  border-radius: 4px;
}

.progress__bar {
  width: 0;
  height: 100%;
  background-color: #1a202c;
  transition: width 0.5s ease-in-out;
}

Loading screen progress bar

شريط progress__bar يتم تحريكه عندما يتم تحميل الموارد

عندما يكون progress عند 100%، نريد إخفاء loading-screen وعرض الـ Canvas. للقيام بذلك، سنستخدم الخاصية active من useProgress:

const LoadingScreen = () => {
  const { progress, active } = useProgress();

  return (
    <div className={`loading-screen ${active ? "" : "loading-screen--hidden"}`}>
      <div className="loading-screen__container">
        <h1 className="loading-screen__title">3D Web Agency</h1>
        <div className="progress__container">
          <div
            className="progress__bar"
            style={{ width: `${progress}%` }}
          ></div>
        </div>
      </div>
    </div>
  );
};

نضيف فئة loading-screen--hidden عندما تكون الخاصية active تساوي false

.loading-screen {
  /* ... */
  opacity: 1;
}

.loading-screen--hidden {
  animation: fade-out 0.5s ease-in-out forwards 1s;
}

@keyframes fade-out {
  0% {
    opacity: 1;
  }
  100% {
    opacity: 0;
    visibility: hidden;
  }
}

نشغل الرسوم المتحركة fade-out عندما تتم إضافة الفئة loading-screen--hidden. نضيف أيضًا visibility: hidden عند اكتمال الرسوم المتحركة لمنع المستخدم من التفاعل مع loading-screen عندما تكون مخفية وتتداخل مع أحداث Canvas.

Et voilà! لدينا شاشة تحميل تعرض التقدم بينما الموارد تُحمل وتتلاشى لعرض المشهد ثلاثي الأبعاد عندما يكون كل شيء محمل.

الخاتمة

في هذا الدرس، غطينا الموضوع المهم المتعلق بـ تحميل الموارد. لديك أداتان إضافيتان في حزام أدواتك لبناء تجارب ثلاثية الأبعاد مذهلة.

قرار استخدام شاشة تحميل، أو حل احتياطي، أو كليهما، أو لا شيء، يعود لك. يعتمد الأمر على السياق وتجربة المستخدم التي تريد تقديمها.

يوفر مكتبة Drei أيضًا Loader component يمكنك استخدامه لعرض شاشة تحميل. لم أغطيها في هذا الدرس لأنه أعتقد أنها غير قابلة للتخصيص بما يكفي لتطبيق production.

Three.js logoReact logo

React Three Fiber: The Ultimate Guide to 3D Web Development

✨ You have reached the end of the preview ✨

Go to the next level with Three.js and React Three Fiber!

Get full access to this lesson and the complete course when you enroll:

  • 🔓 Full lesson videos with no limits
  • 💻 Access to the final source code
  • 🎓 Course progress tracking & completion
  • 💬 Invite to our private Discord community
Unlock the Full Course – Just $85

One-time payment. Lifetime updates included.