TuBrief
구독 채널
비디오
커뮤니티

فصل دورة العرض التليفيزيوني لمنع انخفاض الإطارات عند استخدام React و WebGL معاً

TuBrief 편집팀
2026년 8월 7일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

العربية한국어EnglishEspañol中文हिन्दीDeutschFrançaisPortuguêsРусскийBahasa Indonesia日本語

관련 영상

مكتبة مكونات واجهة المستخدم هذه رهيبة حقاً (Canvas UI)7:27

مكتبة مكونات واجهة المستخدم هذه رهيبة حقاً (Canvas UI)

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

فصل دورة العرض التليفيزيوني لمنع انخفاض الإطارات عند استخدام React و WebGL معاً

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

تحدث هذه المشكلة عادةً عندما تصطدم طريقة إدارة الحالة في React مع طريقة العرض في WebGL. إذا لم تقم بفصل طريقتي عمل المحركين، فلن تستمر الشاشة في التقطع بغض النظر عن مدى جودة المكتبة المستخدمة.

مشكلة تضمين مكتبة الرسومات بالكامل في الحزمة الرئيسية

تأتي مكتبات مثل Three.js أو Deck.gl بحجم كبير نظراً لاحتوائها على محرك عمليات حسابية ومترجم Shaders بداخله. إذا قمت باستيراد الحزمة من الأعلى دون تفكير، فسيتم خلط الأكواد التي لا تستخدمها أيضاً في الحزمة الرئيسية.

إذا كنت في بيئة Next.js، فيجب عليك تخفيف هذا العبء من خلال الجمع بين الاستيراد المباشر للـ Subpath وتكوين التحميل الديناميكي dynamic load. يمكنك أيضاً منع خطأ window is not defined الذي يحدث أثناء العرض من جهة الخادم.

`javascript
// next.config.mjs
import withBundleAnalyzer from '@next/bundle-analyzer';

const bundleAnalyzer = withBundleAnalyzer({
enabled: process.env.ANALYZE === 'true',
});

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
experimental: {
optimizePackageImports: [
'@radix-ui/react-icons',
'lucide-react',
'three',
'deck.gl'
],
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
path: false,
};
}
return config;
},
};

export default bundleAnalyzer(nextConfig);

`

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

في غلاف مكون العميل (Client Component Wrapper)، قم بإيقاف تشغيل SSR واستيراده.

`typescript
// components/dashboard/CanvasDashboardWrapper.tsx
'use client';

import dynamic from 'next/dynamic';
import { Skeleton } from '@/components/ui/skeleton';

const DynamicCanvasRenderer = dynamic(
() => import('./CanvasRenderer').then((mod) => mod.CanvasRenderer),
{
ssr: false,
loading: () => (




),
}
);

export function CanvasDashboardWrapper() {
return (




);
}

`

من خلال التكوين بهذه الطريقة، ستحصل على ثلاثة تأثيرات فورية:

  1. تسجيل الحزمة في next.config.mjs لتحفيز حذف الوحدات غير الضرورية.
  2. إعطاء { ssr: false } إلى next/dynamic لمنع أخطاء وقت التشغيل في وقت العرض من جهة الخادم.
  3. عرض Skeleton بارتفاع ثابت لمنع حدوث اهتزاز في التخطيط قبل تدفق البيانات.

إذا كنت تستخدم Tailwind CSS، يجب عليك أيضاً التحقق من أحداث المؤشر (Pointer Events). قم بتطبيق pointer-events-none على غلاف اللوحة بالكامل لجعل الأحداث تمر إلى عناصر DOM الخلفية، وأعطِ pointer-events-auto فقط لطبقات الكائنات التي تحتاج إلى معالجة فعلية.

إبعاد useState عن حلقة اللوحة

تقوم React بإعادة رسم DOM الافتراضي عند تغير الحالة (useState، useContext). من ناحية أخرى، تقوم WebGL بإعادة رسم الشاشة 60 مرة في الثانية عبر requestAnimationFrame.

ماذا يحدث إذا قمت باستدعاء دالة تغيير حالة React داخل حلقة يتم تشغيلها 60 مرة في الثانية؟ ستعمل عملية Reconciliation الخاصة بReact في كل إطار مما يتسبب في تجميد الخيط الرئيسي (Main Thread). هذا هو السبب الحقيقي لتباطؤ الشاشة.

يجب وضع القيم المتغيرة في useRef، وترك مهمة رسم الشاشة لحلقة requestAnimationFrame المستقلة.

`typescript
// hooks/useAnimationFrame.ts
import { useEffect, useRef } from 'react';

type AnimationCallback = (deltaTime: number, timestamp: number) => void;

export const useAnimationFrame = (callback: AnimationCallback, isPaused: boolean = false) => {
const requestRef = useRef<number | null>(null);
const previousTimeRef = useRef<number | null>(null);
const callbackRef = useRef(callback);

useEffect(() => {
callbackRef.current = callback;
}, [callback]);

useEffect(() => {
if (isPaused) {
if (requestRef.current !== null) {
cancelAnimationFrame(requestRef.current);
}
return;
}

const animate = (timestamp: number) => {
  if (previousTimeRef.current !== null) {
    const deltaTime = timestamp - previousTimeRef.current;
    callbackRef.current(deltaTime, timestamp);
  }
  previousTimeRef.current = timestamp;
  requestRef.current = requestAnimationFrame(animate);
};

requestRef.current = requestAnimationFrame(animate);

return () => {
  if (requestRef.current !== null) {
    cancelAnimationFrame(requestRef.current);
  }
};

}, [isPaused]);
};

`

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

قم بتغيير الهيكل:

  1. يتم الكتابة فوق البيانات المتغيرة بشكل متكرر مثل موضع الماوس أو قيم الدوران في current الخاصة بـ useRef.
  2. أنشئ حلقة requestAnimationFrame فريدة باستخدام خطاف useAnimationFrame.
  3. تقرأ دالة العرض قيمة Ref هذه وترسمها على اللوحة فقط. وبما أنها لا تلمس حالة React، فل لن يحدث إعادة تقديم (Re-rendering) من الأساس.

معالجة التراجع (Fallback) للمتصفحات غير المدعومة

مثل مواصفات HTML in Canvas التي تتم مناقشتها في WICG، هناك محاولات لرسم DOM مباشرة على نقطية اللوحة (Canvas Bitmap) باستخدام طرق مثل drawElementImage. يعمل هذا بشكل جيد في بيئة إعلامات Chrome الحديثة، ولكن إذا اعتمدت عليها كما هي في بيئة العمل الفعلي، فستختفي الشاشة تماماً في المتصفح القديم أو بعض الأجهزة المحمولة.

قم بقراءة ما إذا كانت الميزة مدعومة أولاً، وقم بتضمينها في Error Boundary للاستعداد للحالات الاستثنائية.

`typescript
// components/canvas/CanvasErrorBoundary.tsx
'use client';

import React, { Component, ErrorInfo, ReactNode } from 'react';
import { detectCanvasCapabilities } from '@/utils/canvasFeatureDetection';

interface Props {
children: ReactNode;
fallbackUI: ReactNode;
}

interface State {
hasError: boolean;
isSupported: boolean;
}

export class CanvasErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false,
isSupported: true,
};

public componentDidMount() {
const capabilities = detectCanvasCapabilities();
if (!capabilities.webgl2) {
this.setState({ isSupported: false });
}
}

public static getDerivedStateFromError(_: Error): Partial {
return { hasError: true };
}

public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Canvas UI Rendering Engine Crashed:', error, errorInfo);
}

public render() {
if (this.state.hasError || !this.state.isSupported) {
return (



{this.props.fallbackUI}


);
}

return (
  <div className="relative w-full h-full min-h-[400px] overflow-hidden">
    {this.props.children}
  </div>
);

}
}

`

هذه هي طريقة إنشاء شبكة الأمان:

  1. التحقق من حالة دعم WebGL2 من خلال منطق التحقق من سياق اللوحة.
  2. التقاط أخطاء وقت التشغيل التي تحدث أثناء العرض باستخدام React Class Error Boundary.
  3. تثبيت الحد الأدنى للارتفاع مثل min-h-[400px] على صندوق الغلاف لمنع انهيار نسبة العرض إلى الارتفاع عند التحول إلى واجهة مستخدم بديلة (مثل مخططات HTML/SVG العادية).

لا يقوم جامعي القمامة في جافا سكريبت بتنظيف ذاكرة وحدة معالجة الرسومات (GPU)

يتعامل محرك V8 جيداً مع الكائنات الموجودة في كومة الجافا سكريبت (JavaScript Heap). لكنه لا يعرف شيئاً عن البيانات المرفوعة إلى ذاكرة وحدة معالجة الرسومات (GPU) مثل مخازن WebGL المؤقتة، والنسيج (Textures), وبرامج Shaders.

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

`typescript
// hooks/useWebGLCleanUp.ts
import { useEffect, useRef } from 'react';

export const useWebGLCleanUp = () => {
const glRef = useRef<WebGL2RenderingContext | null>(null);
const resourcesRef = useRef<{
buffers: WebGLBuffer[];
textures: WebGLTexture[];
programs: WebGLProgram[];
}>({
buffers: [],
textures: [],
programs: [],
});

const registerBuffer = (buffer: WebGLBuffer) => resourcesRef.current.buffers.push(buffer);
const registerTexture = (texture: WebGLTexture) => resourcesRef.current.textures.push(texture);
const registerProgram = (program: WebGLProgram) => resourcesRef.current.programs.push(program);

useEffect(() => {
return () => {
const gl = glRef.current;
if (!gl) return;

  resourcesRef.current.buffers.forEach((buffer) => gl.deleteBuffer(buffer));
  resourcesRef.current.textures.forEach((texture) => gl.deleteTexture(texture));

  resourcesRef.current.programs.forEach((program) => {
    const shaders = gl.getAttachedShaders(program);
    if (shaders) {
      shaders.forEach((shader) => {
        gl.detachShader(program, shader);
        gl.deleteShader(shader);
      });
    }
    gl.deleteProgram(program);
  });

  const loseContextExt = gl.getExtension('WEBGL_lose_context');
  if (loseContextExt) {
    loseContextExt.loseContext();
  }

  resourcesRef.current = { buffers: [], textures: [], programs: [] };
  glRef.current = null;
};

}, []);

return { glRef, registerBuffer, registerTexture, registerProgram };
};

`

إليك تسلسل التحقق مباشرة باستخدام أدوات مطوري Chrome لمعرفة ما إذا كانت الذاكرة قد تم تحريرها بشكل صحيح:

  1. افتح مكون لوحة التحكم والتقط لقطة الكومة الأولى (S1) من علامة تبويب Memory.
  2. كرر تحميل وتفكيك المكون (مثل الانتقال إلى علامة تبويب أخرى) أكثر من 10 مرات.
  3. اضغط على جمع القمامة اليدوي (أيقونة سلة المهملات) ثم التقط لقطة الكومة الثانية (S2).
  4. اجعل هدف مقارنة S2 هو S1 وتأكد من أن عدد Detached HTMLCanvasElement أو WebGLBuffer لا يتبقى منه شيء وينخفض إلى الصفر.

عند استخدام React و WebGL معاً، فإن المفتاح هو تركهما يعملان دون الإضرار بالمجالات التي تبرع فيها كل مكتبة. تقسيم الحزمة، وإزالة حالة React من حلقة العرض، وتفريغ ذاكرة GPU بشكل مؤكد عند اختفاء المكون. من خلال العناية بهاته الأمور الثلاثة وحدها، يمكنك إنشاء لوحة تحكم تعمل بسلاسة دون انخفاض الإطارات.