Skip to content

أنبوب عرض المتصفح (Browser Rendering Pipeline)

🎯 السؤال الأساسي

لماذا تكون بعض صفحات الويب سلسة كالحرير، بينما تكون أخرى متقطعة كعرض PowerPoint؟ كيف يحول المتصفح كومة من أكواد HTML وCSS وJavaScript إلى صفحة الويب التي تراها أمامك؟ سيأخذك هذا الفصل في جولة داخل "ورشة عمل" المتصفح، لتفهم آلية عمله، ومن ثم تكتب صفحات ويب ذات أداء أفضل.

ماذا ستتعلم من هذه المقالة؟

الفصلالمحتوىماذا ستستفيد بعد دراسته؟
الفصل 1لماذا نفهم أنبوب العرضفهم ضرورة تحسين الأداء
الفصل 2المراحل الخمس لأنبوب العرضإتقان التدفق الأساسي لعرض المتصفح
الفصل 3بناء شجرة DOM وشجرة CSSOMفهم كيفية تحليل HTML وCSS
الفصل 4بناء شجرة العرضمعرفة أي العناصر سيتم عرضها
الفصل 5التخطيط وإعادة التدفقتجنب إثارة حسابات التخطيط المكلفة
الفصل 6الرسم وإعادة الرسمتقليل عمليات الرسم غير الضرورية
الفصل 7التركيب وتسريع GPUالاستفادة من GPU لتحسين أداء الرسوم المتحركة
الفصل 8حلقة الأحداثفهم آلية تنفيذ JavaScript
الفصل 9تحسين الأداء العمليإتقان تقنيات تحسين الأداء الشائعة

يبدأ كل فصل من "فهم المبدأ"، ولا تحتاج إلى كتابة أكواد تحسين يدويًا. عندما تواجه مشكلة في الأداء، يمكنك العودة للمراجعة في أي وقت.


1. لماذا نفهم "أنبوب العرض"؟

1.1 من "يعمل" إلى "يعمل بسرعة": طريق التقدم في تطوير الواجهة الأمامية

عندما تبدأ تعلم الواجهة الأمامية، نهتم فقط بما إذا كان الكود "يعمل" — تعرض الصفحة، ويمكن النقر على الأزرار، وهذا يعتبر نجاحًا. لكن مع كبر المشاريع وزيادة المستخدمين، ستكتشف سريعًا حقيقة قاسية: نفس الوظيفة، شخص ما يكتب صفحة سلسة كالحرير، وآخر يكتب صفحة متقطعة تجعل المستخدم يريد تحطيم الفأرة.

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

🐢 عقلية المبتدئ (يهتم بالوظيفة فقط)

  • المهم أن الصفحة تظهر فقط
  • التقطع مشكلة المتصفح
  • تحسين الأداء شيء يؤجل للنهاية

🚀 العقلية المتقدمة (تهتم بالتجربة)

  • السلاسة هي جوهر تجربة المستخدم
  • فهم آلية عمل المتصفح
  • التفكير في الأداء أثناء كتابة الكود

فهم أنبوب العرض هو الخطوة الأساسية للانتقال من "يعمل" إلى "يعمل بسرعة".

1.2 قصة واقعية عن فخ أداء: لماذا أصبح "التحسين" أبطأ؟

قصة شياو تشانغ مع فخ الأداء

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

فكر شياو تشانغ: "الصفحة بطيئة لأن عناصر DOM كثيرة جدًا، سأخفيها أولاً بـ display:none، ثم أعرضها بعد التعديل، هكذا لن يكرر المتصفح العرض صحيح؟"

فكتب هذا الكود:

javascript
// "التحسين" الذي تظنه
const container = document.getElementById('list')
container.style.display = 'none'  // أخفِ أولاً، لن يثير العرض صحيح؟

for (let i = 0; i < 1000; i++) {
  const item = document.createElement('div')
  item.style.width = Math.random() * 100 + 'px'  // عرض عشوائي
  container.appendChild(item)
}

container.style.display = 'block'  // أظهر في النهاية، عرض مرة واحدة

بعد الاختبار اكتشف أن الصفحة أصبحت أبطأ! احتار شياو تشانغ: من الواضح أنه "حسّن"، لماذا أصبح أبطأ؟

لاحقًا، نظر مدير الواجهة الأمامية في الكود، وأشار إلى المشكلة: رغم أن العناصر مخفية، إلا أن كل تعديل لـ style.width لا يزال يثير حساب الأنماط وعلامات التخطيط في المتصفح، مما يجعل المتصفح يقوم بعمل غير مجدٍ في الخلفية.

الطريقة الصحيحة هي استخدام DocumentFragment للعمل الدفعي في الذاكرة، ثم إدخاله في DOM دفعة واحدة في النهاية، مما يثير العرض مرة واحدة فقط.

💡 الدرس الأساسي

بدون فهم آلية عمل المتصفح، قد "تكون ذكيًا بشكل خاطئ" وتكتب كومة من "أكواد التحسين"، لتجعل الأداء أسوأ في النهاية. فهم أنبوب العرض يجعلك تعرف أي العمليات مكلفة وأيها رخيصة، وبالتالي تتجنب بذل الجهد في المكان الخطأ.


2. المفهوم الأساسي: ما هو "أنبوب العرض"؟

🤔 ما هو "العرض"؟

العرض (Rendering)، ببساطة هو عملية "رسم" المتصفح للكود ليصبح صفحة الويب التي تراها.

يمكنك تخيله كـ مطبعة كتب:

  • HTML = محتوى المخطوطة (نصوص، صور، فصول)
  • CSS = متطلبات التنسيق (حجم الخط، اللون، المسافات)
  • JavaScript = التعديل الديناميكي (تعديلات الكاتب المؤقتة، تعديل التنسيق)

بعد أن يحصل المتصفح على هذه "المواد"، يمر بعدة "مراحل" قبل أن "يطبع" صفحة الويب التي تراها. هذه السلسلة من المراحل هي أنبوب العرض (Rendering Pipeline).

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

2.1 فهم أنبوب العرض من خلال تشبيه المخبز

تخيل أنك تدير مخبزًا، وعليك كل يوم صنع أنواع مختلفة من الخبز للعملاء. المراحل المتضمنة في هذه العملية تشبه بشكل مذهل عملية عرض المتصفح:

المرحلة🥖 تشبيه المخبزالعمل الفعلي للمتصفحمثال ملموس
1. تحضير المكوناتترتيب قائمة المكونات (دقيق، بيض، كريمة...)بناء شجرة DOM: تحليل HTML إلى هيكل شجريتكتب <div><p>Hello</p></div>، يحللها المتصفح إلى شجرة div→p→"Hello"
2. تحضير الوصفةترتيب بطاقات الوصفات (نسب مكونات كل نوع خبز)بناء شجرة CSSOM: تحليل CSS إلى شجرة قواعدتكتب .title { color: red }، يسجل المتصفح "نص .title باللون الأحمر"
3. وضع الخطةحسب المكونات والوصفات، تحديد ما سيتم خبزه اليومبناء شجرة العرض: دمج DOM وCSSOM، والاحتفاظ فقط بالعناصر المرئيةوسم <script> لا يُعرض، لذا ليس في شجرة العرض
4. وضع الخبزوضع الخبز في واجهة العرض، وتحديد مكان كل رغيفالتخطيط (Layout): حساب أبعاد وموقع كل عنصرحساب "هذا div عرضه 200px وارتفاعه 100px، في الموقع (50, 50) من الشاشة"
5. التزييندهن الخبز بالبيض، رش السمسم، وضع الكريمةالرسم (Paint): "رسم" اللون والحدود والظلال لكل عنصررسم "النص الأحمر" فعليًا على الشاشة
6. التجميع النهائيترتيب كل أنواع الخبز معًا بشكل جميلالتركيب (Composite): دمج الطبقات المتعددة في الصورة النهائيةGPU يدمج طبقة الخلفية والنصوص والصور في صورة كاملة واحدة

📊 ماذا يمكنك أن ترى من الجدول؟

دعنا نفسر هذا الجدول سطرًا بسطر، لفهم كل مرحلة من أنبوب العرض:

المرحلة 1-2 (مرحلة التحضير): المتصفح "يفهم" كودك أولاً. HTML وCSS يتم تحليلهما بشكل منفصل، لأن مسؤولياتهما مختلفة — HTML يحدد "ما هو المحتوى"، وCSS يحدد "كيف يبدو".

المرحلة 3 (مرحلة الدمج): لماذا "الدمج"؟ لأنه ليس كل عناصر HTML ستُعرض (مثل <head>، <script>)، يحتاج المتصفح لدمج "العناصر المرئية" مع "أنماطها" لتشكيل "مخطط البناء".

المرحلة 4-5 (مرحلة الرسم): التخطيط هو "حساب الموقع"، والرسم هو "وضع اللون". تغيير التخطيط (مثل تغيير العرض) يثير الرسم، لكن تغيير الرسم (مثل تغيير اللون) لا يثير التخطيط.

المرحلة 6 (مرحلة التركيب): "سحر" المتصفحات الحديثة. الطريقة التقليدية هي "الرسم دفعة واحدة" (CPU بطيء)، والطريقة الحديثة هي "الرسم الطبقي + تركيب GPU" (سريع)، وهذا هو سبب أن حركة transform أكثر سلاسة من حركة width.

2.2 المراحل الخمس لأنبوب العرض

🏭渲染管线从代码到像素的五步旅程
想象你在印刷厂工作:稿件要排版、印刷、装订,最后才能变成书本。浏览器渲染网页也一样,HTML 和 CSS 要经过一道道"工序",才能变成屏幕上的画面。
🌲
构建DOM/CSSOM
解析代码
🎨
构建渲染树
合并筛选
📐
布局
计算位置
✏️
绘制
填充颜色
🔮
合成
合并图层
👆 点击上方任意阶段,查看详细解释
💡核心思想:每个阶段各司其职,前面的阶段为后面阶段准备数据。理解这个流程,你就能知道什么时候用什么方式修改页面,才能避免性能问题。

3. المرحلة الأولى: بناء شجرة DOM وشجرة CSSOM

3.1 لماذا "الشجرة"؟

🤔 ما هو DOM؟

DOM (Document Object Model، نموذج كائن المستند)، هو هيكل شجري يحول المتصفح مستند HTML إليه، لتسهيل معالجة JavaScript لعناصر الصفحة.

يمكنك تخيله كـ شجرة العائلة:

  • في الأعلى "الجد" (<html>)
  • تحته "الأبناء" (<body>، <head>)
  • وتحته "الأحفاد" (<div>، <p>، <span>)

لماذا التحويل إلى شجرة؟ لأن الهيكل الشجري مناسب جدًا "للبحث" و"التعديل". مثلاً، إذا أردت العثور على "كل العناصر التي صنفها title"، يمكن للمتصفح البحث بسرعة في الشجرة، بدلاً من البحث في نصوص غير مرتبة ببطء.

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

الخطوة الأولى: التحليل المعجمي — تفكيك الكود إلى "كلمات"

html
<div class="container">
  <p>Hello World</p>
</div>

عندما يرى المتصفح هذا الكود، "يفككه" أولاً:

  • <div> → "وسم بداية div"
  • class="container" → "خاصية class، قيمتها container"
  • <p> → "وسم بداية p"
  • Hello World → "محتوى نصي"
  • </p> → "وسم نهاية p"
  • </div> → "وسم نهاية div"

الخطوة الثانية: التحليل النحوي — تجميع "الكلمات" إلى "عقد"

حسب قواعد HTML، يجمع المتصفح هذه "الكلمات" إلى "عقد":

  • عقدة عنصر: <div>، <p>
  • عقدة خاصية: class="container"
  • عقدة نصية: "Hello World"

الخطوة الثالثة: بناء الشجرة — إنشاء "علاقات الأب والابن"

أخيرًا، حسب علاقات التداخل بين الوسوم، يبني المتصفح الهيكل الشجري:

Document (عقدة جذر المستند)
└── html
    └── body
        └── div.class = "container"
            └── p
                └── "Hello World"

3.2 شجرة CSSOM: "دليل القواعد" للأنماط

🤔 ما هو CSSOM؟

CSSOM (CSS Object Model، نموذج كائن CSS)، هو هيكل شجري يحول المتصفح قواعد CSS إليه، لاستخدامه في حساب النمط النهائي لكل عنصر.

يمكنك تخيله كـ دليل تنسيق الملابس:

  • القواعد العلوية (مثل خط body) تؤثر على السفلية (كل العناصر الابنة)
  • إذا كان هناك تعارض (مثلاً عدة قواعد تحدد ألوانًا مختلفة لنفس العنصر)، يتم التحديد حسب "الأولوية"
  • في النهاية يحسب ماذا "يرتدي" كل عنصر

عملية بناء CSSOM تشبه DOM، لكن بفارق أساسي: CSS "يُورث" و"يتتالي".

عرض عملية بناء CSSOM

CSS الأصلي:

css
body {
  font-size: 16px;
  color: #333;
}

.container {
  width: 100%;
  color: red;  /* سيغطي color الخاص بـ body */
}

.container p {
  font-weight: bold;
}

شجرة CSSOM بعد البناء:

StyleSheet
├── body
│   ├── font-size: 16px
│   └── color: #333
└── .container
    ├── width: 100%
    ├── color: red  (أولوية أعلى، تغطي color الخاص بـ body)
    └── p
        └── font-weight: bold

3.3 سجل الفخاخ: لماذا CSS الخاص بي "لا يعمل"؟

الفخ الأول: تعارض أوزان محددات CSS

عرض الأخطاء الشائعة
css
/* كود CSS الذي كتبته */
#header { color: red; }      /* محدد id، وزنه 100 */
.title { color: blue; }     /* محدد class، وزنه 10 */

/* HTML */
<div id="header" class="title">ما لون هذا النص؟</div>

تظن أنه أزرق، لكن النتيجة أحمر. لأن وزن محدد id (100) أعلى من محدد class (10).

الفخ الثاني: وسوم HTML غير مغلقة، المتصفح "يصلحها تلقائيًا"

عرض كيف يصلح المتصفح HTML الخاطئ
html
<!-- HTML الذي كتبته -->
<div>
  <p>هذا نص
</div>

<!-- بعد إصلاح المتصفح -->
<div>
  <p>هذا نص</p>  <!-- المتصفح يغلق الوسم تلقائيًا نيابة عنك -->
</div>

المتصفح "متسامح" جدًا، يصلح أخطائك تلقائيًا. لكن هذا التسامح له ثمن — يحتاج المتصفح لحسابات إضافية لتخمين نيتك، مما يؤثر على الأداء.

🌲DOM到渲染树浏览器如何构建渲染树
浏览器需要把 HTML 和 CSS 合并成一棵"渲染树"。想象你在组装家具:图纸是 DOM,说明书是 CSSOM,只有结合两者,才能知道每个零件长什么样、放在哪里。
DOM树
<html>
<head>
<style>
<body>
<div>
<span>
<script>
+
CSSOM树
body
div
color: red
span
display: block
script
display: none
=
渲染树
div
span
可见节点
不可见节点(不包含在渲染树中)
💡核心要点:渲染树只包含可见的节点(display: none 的元素会被忽略)。每个渲染树节点都包含对应的 DOM 节点和计算出的样式信息。渲染树构建完成后,浏览器才能进入布局阶段。

4. المرحلة الثانية: بناء شجرة العرض

4.1 لماذا نحتاج "شجرة العرض"؟

قد تسأل: "لدينا شجرة DOM وشجرة CSSOM، لماذا نبني شجرة عرض أخرى؟ ألا يمكن استخدام DOM مباشرة؟"

الجواب: شجرة DOM تحتوي على الكثير من المعلومات "غير المفيدة".

مثلاً هذا الكود:

html
<html>
<head>
  <title>عنوان الصفحة</title>
  <style>/* كود CSS */</style>
  <script>/* كود JavaScript */</script>
</head>
<body>
  <div class="container">
    <p>محتوى مرئي</p>
  </div>
  <div style="display: none">
    <p>محتوى مخفي (display:none)</p>
  </div>
</body>
</html>

شجرة DOM تحتوي كل العناصر:

  • <head>، <title>، <style>، <script> (هذه لا تُعرض)
  • div بـ display: none (لا يُعرض أيضًا)

لكن شجرة العرض تحتوي فقط العناصر "التي سترسم على الشاشة":

  • إزالة <head> وعناصره الفرعية
  • إزالة div بـ display: none

4.2 قواعد بناء شجرة العرض

عند بناء شجرة العرض، يتبع المتصفح مجموعة من القواعد:

السيناريوطريقة المعالجةمثالالتأثير على الأداء
display: noneاستبعاد كامل من شجرة العرضالعنصر وعناصره الفرعية كلها غير مرئية✅ يقلل عبء العرض
visibility: hiddenموجود في شجرة العرض، لكن لا يُرسميشغل مساحة، لكنه شفاف تمامًا⚠️ لا يزال يحتاج حساب التخطيط
opacity: 0موجود في شجرة العرض، لكنه شفافيمكن التفاعل معه (قابل للنقر)، لكنه غير مرئي⚠️ لا يزال يحتاج حساب التخطيط
خارج نطاق الرؤيةموجود في شجرة العرض، لا يُرسم مؤقتًايُرسم عند التمرير إلى نطاق الرؤية⚠️ لكنه لا يزال في شجرة العرض

📊 ماذا يمكنك أن ترى من الجدول؟

الاكتشاف الأساسي: display: none هو الطريقة الوحيدة "الموفرة للأداء حقًا" للإخفاء، لأن العنصر خارج شجرة العرض تمامًا، ولا يقوم المتصفح بأي تخطيط أو رسم له.

أما visibility: hidden وopacity: 0 فرغم أنهما "غير مرئيين"، إلا أنهما لا يزالان في شجرة العرض، ويحتاج المتصفح لحساب تخطيطهما (يشغلان مساحة). إذا كنت بحاجة إلى "إخفاء دون التأثير على التخطيط" (مثل حركة التلاشي)، استخدم opacity؛ وإذا كنت بحاجة إلى "إخفاء كامل دون شغل مساحة"، استخدم display: none.

4.3 سجل الفخاخ: لماذا تظل الصفحة بطيئة رغم ضبط display:none؟

❌ سوء فهم شائع: الاعتقاد أن عناصر display:none "غير موجودة"

يعتقد الكثيرون أنه بعد ضبط display: none، "يختفي" العنصر، ولا تؤثر أي عملية عليه على الأداء. هذا خاطئ!

رغم أن عناصر display: none ليست في شجرة العرض، إلا أنه عند تعديل خصائصها عبر JavaScript، لا يزال المتصفح بحاجة إلى:

  1. إعادة حساب الأنماط (مطابقة قواعد CSS)
  2. تتبع التغييرات (استعدادًا للعرض المستقبلي)

انظر إلى مثال "التحسين" التالي:

عرض كود "التحسين غير الفعال"
javascript
// ❌ "التحسين" الذي تظنه: أخفِ أولاً، عدل ثم أظهر
const container = document.getElementById('list')
container.style.display = 'none'

// عمليات DOM مجنونة
for (let i = 0; i < 1000; i++) {
  const item = document.createElement('div')
  item.style.width = Math.random() * 100 + 'px'  // تغيير العرض!
  item.textContent = `Item ${i}`
  container.appendChild(item)
}

container.style.display = 'block'

// المشكلة: كل تعديل لـ style.width يجبر المتصفح على إعادة حساب الأنماط،
// حتى لو كان العنصر display:none!

✅ الطريقة الصحيحة للتحسين:

javascript
// استخدام DocumentFragment للعمل الدفعي
const container = document.getElementById('list')
const fragment = document.createDocumentFragment()  // حاوية افتراضية

// كل العمليات تتم على fragment في الذاكرة
for (let i = 0; i < 1000; i++) {
  const item = document.createElement('div')
  item.style.width = Math.random() * 100 + 'px'
  item.textContent = `Item ${i}`
  fragment.appendChild(item)  // لا يؤثر على DOM الحقيقي
}

// إدخال دفعة واحدة في DOM الحقيقي، يثير العرض مرة واحدة فقط
container.appendChild(fragment)

5. المرحلة الثالثة: التخطيط وإعادة التدفق

5.1 ما هو "التخطيط"؟

🤔 ما هو التخطيط (Layout)؟

التخطيط، ويسمى أيضًا إعادة التدفق (Reflow)، هو عملية حساب المتصفح "أين يقع كل عنصر وكم مساحة يشغلها" في شجرة العرض.

يمكنك تخيله كـ مصمم ديكور يقيس الغرفة:

  • أولاً يقيس طول وعرض كل غرفة
  • يقرر أين يوضع الأثاث
  • يحسب إحداثيات كل قطعة أثاث

لماذا التخطيط "مكلف"؟ لأن تغيير عنصر واحد قد يؤثر على عناصر أخرى. مثلاً، إذا جعلت div أوسع، قد يندفع div المجاور للأسفل، مما يؤدي إلى إعادة حساب الصفحة بأكملها.

5.2 "حقول الألغام" التي تثير إعادة التدفق

فيما يلي العمليات الشائعة التي تثير إعادة التدفق، يُنصح بحفظها:

الفئةالخاصية/العمليةالتأثير على الأداءالبديل
الأبعادwidth, height, min/max-width/height💀💀💀استخدم transform: scale() بدلاً منها
الموقعtop, right, bottom, left💀💀💀استخدم transform: translate() بدلاً منها
الهوامشmargin, padding💀💀استخدم transform أو gap بدلاً منها
الحدودborder-width💀💀تجنب التعديل المتكرر قدر الإمكان
المحتوىتغيير محتوى النص، تحميل الصور💀💀احجز مساحة مسبقًا لتجنب اهتزاز التخطيط
الخطfont-size, line-height💀💀💀تجنب التعديل المتكرر قدر الإمكان
العرضتغيير قيمة display💀💀💀استخدم visibility أو opacity بدلاً منها (إذا لم تكن بحاجة للإخفاء الكامل)
الاستعلامoffsetWidth, offsetHeight وغيرها💀💀💀💀💀القراءة الدفعية، تجنب اهتزاز التخطيط

📊 ماذا يمكنك أن ترى من الجدول؟

الاكتشافات الأساسية:

  1. خصائص الهندسة (العرض، الارتفاع، الموقع) هي الأكثر تكلفة: تثير حساب التخطيط الكامل
  2. خصائص الاستعلام أخطر من التعديل: قراءة offsetWidth تجبر التخطيط المتزامن (انظر القسم 5.4)
  3. transform وopacity هما الأفضل أداءً: لا يثيران إعادة التدفق، بل يثيران التركيب فقط

5.3 سجل الفخاخ: لماذا رسومي المتحركة متقطعة كعرض PowerPoint؟

الفخ: استخدام width للرسوم المتحركة

عرض كود الرسوم المتحركة سيئة الأداء
css
/* ❌ رسوم متحركة سيئة: تثير إعادة التدفق */
.box {
  width: 100px;
  transition: width 0.3s;
}

.box:hover {
  width: 200px;  /* تغيير العرض يثير إعادة التدفق! */
}

كل إطار من الرسوم المتحركة يثير إعادة التدفق، ويحتاج المتصفح إلى:

  1. إعادة حساب العرض
  2. إعادة حساب الموقع (قد يؤثر على عناصر أخرى)
  3. إعادة الرسم

✅ رسوم متحركة جيدة: استخدام transform

css
/* ✅ رسوم متحركة جيدة: تثير التركيب فقط */
.box {
  width: 100px;
  transform: scaleX(1);
  transition: transform 0.3s;
}

.box:hover {
  transform: scaleX(2);  /* التدوير لا يثير إعادة التدفق! */
}

transform يعالج مباشرة بواسطة GPU، لا يثير إعادة التدفق ولا إعادة الرسم، الرسوم المتحركة سلسة كالحرير.

5.4 قاتل الأداء: التخطيط المتزامن القسري

💀 أخطر مشكلة أداء: اهتزاز التخطيط

التخطيط المتزامن القسري (Forced Synchronous Layout)، ويسمى أيضًا اهتزاز التخطيط (Layout Thrashing)، هو أكثر مشاكل الأداء شيوعًا وخطورة.

سببه: عندما تقرأ JavaScript خصائص التخطيط (مثل offsetWidth)، يجب على المتصفح تنفيذ حساب التخطيط فورًا ليرجع قيمة دقيقة.

إذا تبادلت "القراءة والكتابة"، فسيؤدي ذلك إلى تكرار المتصفح "تخطيط → قراءة → تخطيط → قراءة"، مما يشكل حلقة مفرغة.

عرض كود اهتزاز التخطيط
javascript
// ❌ سيء جدًا: تبادل القراءة والكتابة، يؤدي إلى اهتزاز التخطيط
const elements = document.querySelectorAll('.item')

for (let i = 0; i < elements.length; i++) {
  const height = elements[i].offsetHeight  // قراءة → تخطيط قسري
  elements[i].style.width = (height * 2) + 'px'  // كتابة → تعليم الحاجة لإعادة التدفق
  // قراءة الدورة التالية ستجبر التخطيط مرة أخرى... حلقة مفرغة!
}

// إذا كان هناك 100 عنصر، سيتم إثارة 100 عملية حساب تخطيط!

✅ الطريقة الصحيحة للتحسين: فصل القراءة عن الكتابة

javascript
const elements = document.querySelectorAll('.item')

// الخطوة الأولى: القراءة الدفعية (اقرأ الكل أولاً)
const heights = []
for (let i = 0; i < elements.length; i++) {
  heights.push(elements[i].offsetHeight)  // يثير التخطيط مرة واحدة فقط
}

// الخطوة الثانية: الكتابة الدفعية (ثم اكتب الكل)
requestAnimationFrame(() => {
  for (let i = 0; i < elements.length; i++) {
    elements[i].style.width = (heights[i] * 2) + 'px'  // يثير إعادة التدفق مرة واحدة فقط
  }
})
📐布局与重排看看布局计算如何影响页面
盒子
邻居元素
触发阶段:
性能影响:-
是否影响其他元素:-
💡核心要点:布局属性(如 width、margin)会触发重排,影响周围元素的位置。而 transform 只触发合成,在 GPU 上处理,不影响其他元素,性能更好。

6. المرحلة الرابعة: الرسم وإعادة الرسم

6.1 ما هو "الرسم"؟

🤔 ما هو الرسم (Paint)؟

الرسم، هو عملية "رسم" المتصفح للعناصر التي تم "حساب تخطيطها" فعليًا على الشاشة.

يمكنك تخيله كـ دهان الغرفة:

  • مرحلة التخطيط = قياس الأبعاد، رسم الخطوط
  • مرحلة الرسم = الدهان الفعلي، لصق ورق الجدران

الرسم ليس مكلفًا كالتخطيط، لكنه ليس رخيصًا أيضًا. الرسم المتكرر لا يزال يؤثر على الأداء، خاصة للعناصر المعقدة (الظلال، التدرجات، إلخ).

6.2 إشارات إثارة إعادة الرسم

على عكس إعادة التدفق، إعادة الرسم تتعلق فقط بتغيير "المظهر"، وليس "الهندسة":

الفئةالخاصيةالتأثير على الأداءملاحظات
اللونcolor, background-color💀أكثر مثيرات إعادة الرسم شيوعًا
الخلفيةbackground-image, background-position💀💀الصور أبطأ من الألوان الصلبة
الحدودborder-color, border-style💀تغيير لون/نمط الحدود
النصtext-decoration, text-shadow💀💀الظلال أبطأ من النص العادي
ظل الصندوقbox-shadow💀💀💀الظلال المعقدة بطيئة جدًا
تدوير الزواياborder-radius💀تغيير حجم التدوير
الشفافيةopacityخاص: لا يثير إعادة الرسم، يثير التركيب فقط

📊 ماذا يمكنك أن ترى من الجدول؟

الاكتشاف الأساسي: opacity خاص! إنه مثل transform، لا يثير إعادة الرسم، بل يثير مرحلة التركيب مباشرة. هذا هو سبب أن حركة التلاشي بـ opacity هي الأفضل أداءً.

بالإضافة إلى ذلك، الظلال والتدرجات أغلى من إعادة الرسم، لأنها تحتاج حسابات بكسل معقدة. إذا كانت صفحتك تحتوي على الكثير من box-shadow، فكر في استخدام العناصر الوهمية أو الصور بدلاً منها.

6.3 سجل الفخاخ: لماذا تأثير hover الخاص بي بطيء؟

الفخ: استخدام box-shadow لحركة hover

عرض تأثير hover سيئ الأداء
css
/* ❌ تأثير hover سيئ: حركة box-shadow بطيئة جدًا */
.card {
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  transition: box-shadow 0.3s;
}

.card:hover {
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);  /* الظل بطيء جدًا! */
}

box-shadow يحتاج حساب كل بكسل، مما يسبب تقطعًا أثناء الحركة.

✅ الطريقة الجيدة: استخدام transform أو العناصر الوهمية

css
/* ✅ تأثير hover جيد: استخدام transform */
.card {
  transform: translateY(0);
  transition: transform 0.3s, box-shadow 0.3s;
}

.card:hover {
  transform: translateY(-4px);  /* غير الظل فقط عند hover، لا تحركه */
  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
}
🎨绘制层优化浏览器如何通过分层提升性能
🖼️背景层
📄内容层
卡片
触发新层的 CSS 属性:
transform: translate3d(0,0,0)任何3D变换都会创建新层
opacity配合transition使用时
position: fixed固定定位元素需要独立层
will-change: transform显式提示浏览器创建层
💡核心要点:浏览器把需要动画的元素提升到独立的 GPU 层,这样动画时只需要调整位置和透明度,不需要重绘。但不要滥用,每个层都会占用 GPU 内存。

7. المرحلة الخامسة: التركيب وتسريع GPU

7.1 ما هو "التركيب"؟

🤔 ما هو التركيب (Composite)؟

التركيب، هو "سحر" المتصفحات الحديثة، حيث يقسم أجزاء الصفحة المختلفة إلى طبقات (Layer) متعددة، ثم يستخدم GPU (معالج الرسوميات) لتركيب الصورة النهائية بشكل متوازٍ.

يمكنك تخيله كـ طبقات Photoshop:

  • الطريقة التقليدية = كل شيء مرسوم على طبقة واحدة (CPU تسلسلي، بطيء)
  • طريقة التركيب = الرسم الطبقي، ثم الدمج النهائي (GPU متوازٍ، سريع)

لماذا التركيب سريع؟ لأن GPU ماهر في معالجة مهام "تركيب الصور" المتوازية، أسرع بعشرات المرات من CPU.

7.2 أي العناصر تُرفع إلى "طبقة التركيب"؟

يرفع المتصفح تلقائيًا بعض العناصر إلى طبقات تركيب مستقلة. فيما يلي شروط الإثارة الشائعة:

شرط الإثارةخاصية/قيمة CSSالتأثير على الأداءملاحظات
تحويل ثلاثي الأبعادtransform: translate3d(), rotate3d()✅✅✅أفضل أداء للرسوم المتحركة
خدعة تسريع العتادtransform: translateZ(0)✅✅تعرف شعبيًا بـ "تسريع GPU القسري"
حركة الشفافيةتغيير opacity (مع الحركة)✅✅✅لا يثير إعادة الرسم
تحديد المواقع الثابتposition: fixedيتجنب إعادة التخطيط عند التمرير
Will-Changewill-change: transform, opacity✅✅ينشئ الطبقة مسبقًا، انتبه للذاكرة
Canvas/WebGL<canvas>, محتوى WebGL✅✅في طبقة مستقلة طبيعيًا
Video<video>✅✅طبقة مستقلة، تمنع التأثير المتبادل

📊 ماذا يمكنك أن ترى من الجدول؟

الاكتشاف الأساسي: transform وopacity هما أفضل خصائص الرسوم المتحركة أداءً، لأنهما لا يثيران إعادة التدفق وإعادة الرسم، بل يثيران التركيب مباشرة. هذا هو سبب أن أدلة تحسين الأداء تقول دائمًا "استخدم transform وopacity للرسوم المتحركة".

لكن انتبه: كل طبقة تركيب تستهلك ذاكرة GPU، وإساءة استخدام translateZ(0) ستؤدي إلى انفجار الذاكرة (انظر القسم 7.4).

7.3 سجل الفخاخ: طبقات التركيب الكثيرة تجعلها أبطأ؟

💀 فخ التحسين المفرط

سمع البعض أن "تسريع GPU سريع"، فأضافوا transform: translateZ(0) لكل العناصر، لتصبح الصفحة أبطأ في النهاية.

سبب المشكلة: كل طبقة تركيب تحتاج تخزين "نسيج" (صورة نقطية) في GPU، مما يستهلك ذاكرة. إذا كانت الصفحة تحتوي 100 طبقة تركيب، قد تنفجر ذاكرة GPU، مما يؤدي إلى تعطل الأجهزة الضعيفة أو التراجع إلى عرض CPU.

عرض كود "التحسين المفرط"
css
/* ❌ ممارسة خاطئة: تفعيل تسريع GPU لكل العناصر */
.card { transform: translateZ(0); }
.button { transform: translateZ(0); }
.icon { transform: translateZ(0); }
/* ... 100 عنصر كلها مضاف لها ... */

/* النتيجة: انفجار ذاكرة GPU، تجمد الصفحة */

✅ الممارسة الصحيحة: الاستخدام حسب الحاجة

css
/* الاستراتيجية 1: تفعيل فقط للعناصر التي تحتاج حركة فعلاً */
.card {
  transition: transform 0.3s ease;
}

.card:hover {
  transform: translateY(-5px);  /* إنشاء طبقة تركيب تلقائيًا */
}

/* الاستراتيجية 2: استخدام will-change لإشعار المتصفح */
.card {
  will-change: transform;  /* إنشاء الطبقة مسبقًا */
}

/* الاستراتيجية 3: إزالة بعد انتهاء الحركة */
.card:not(:hover) {
  will-change: auto;  /* تحرير ذاكرة GPU */
}
🎬合成层演示浏览器渲染的最后阶段 - 图层合成
合成是浏览器渲染的最后一步。想象你在制作PPT动画:你已经准备好了所有图层,现在只需要调整它们的位置、透明度,然后把它们叠在一起显示出来。这就是合成要做的事情。
🖼️背景层
📄内容层
浮层
合成结果
🖼️
📄
💡核心要点:合成阶段在 GPU 上执行,只调整位置、透明度等,不重新绘制像素。因此 transform 和 opacity 动画性能最好,不会触发重排和重绘。

8. حلقة الأحداث: "تقنية التعدد" في JavaScript

🤔 ما هي حلقة الأحداث؟

حلقة الأحداث (Event Loop)، هي آلية JavaScript لتحقيق "اللا تزامن". لأن JavaScript أحادي الخيط (يستطيع فعل شيء واحد فقط في كل مرة)، لكنه يحتاج لمعالجة مهام متعددة مثل نقرات المستخدم وطلبات الشبكة والمؤقتات، لذا يحتاج "نظام جدولة" لإدارة هذه المهام.

يمكنك تخيلها كـ مركز فرز الطرود:

  • Call Stack (مكدس الاستدعاء) = الطرد الذي تتم معالجته حاليًا
  • Web APIs = مستودعات التعاون الخارجية (المؤقتات، طلبات الشبكة، إلخ)
  • Callback Queue (طابور الردود) = رف الطرود المنتظرة
  • Event Loop (حلقة الأحداث) = روبوت الفرز (يتحقق باستمرار "هل يمكن معالجة المهمة التالية")

8.1 المهام الكبرى والمهام الصغرى

في البداية، كان لدى JavaScript طابور مهام واحد فقط. لكن مع تعقيد البرمجة اللا تزامنية، قدمت المتصفحات نوعين من المهام:

النوعالمصادر الشائعةالأولويةتوقيت التنفيذ
المهمة الكبرىsetTimeout/setInterval، عمليات I/O، عرض UIمنخفضةتنفيذ واحدة في كل دورة من حلقة الأحداث
المهمة الصغرىPromise.then، MutationObserverعاليةبعد انتهاء المهمة الكبرى الحالية، تفرغ فورًا كل المهام الصغرى

"قاعدة" ترتيب التنفيذ:

1. تنفيذ المهمة الكبرى الحالية (مثل <script> بالكامل)
2. تنفيذ كل المهام الصغرى الناتجة أثناء التنفيذ (Promise.then وغيرها)
   ↳ المهام الصغرى يمكن أن تنتج مهامًا صغرى جديدة، تفرغ كلها قبل المتابعة
3. إذا لزم الأمر، عرض UI (إعادة تدفق/إعادة رسم)
4. بدء دورة حلقة الأحداث التالية، تنفيذ المهمة الكبرى التالية

8.2 سجل الفخاخ: Promise أسرع من setTimeout؟

❌ سوء فهم شائع: setTimeout(fn, 0) سينفذ "فورًا"

يعتقد الكثيرون أن setTimeout(fn, 0) تعني "التنفيذ فورًا بعد 0 ميلي ثانية"، وهذا فهم خاطئ.

في الواقع، معنى setTimeout(fn, 0) هو: "انتظر 0 ميلي ثانية على الأقل، ثم أضف الرد إلى طابور المهام الكبرى". لكنه يحتاج انتظار تفريغ مكدس الاستدعاء الحالي، وتفريغ طابور المهام الصغرى، وربما عرض UI، قبل أن يتمكن من التنفيذ.

عرض ترتيب التنفيذ
javascript
console.log('1. Start')

setTimeout(() => {
  console.log('2. setTimeout callback')
}, 0)

Promise.resolve().then(() => {
  console.log('3. Promise.then')
})

console.log('4. End')

// ترتيب الإخراج الذي تظنه:
// 1. Start
// 4. End
// 2. setTimeout callback  ← setTimeout(0) أليس فوريًا؟
// 3. Promise.then

// ترتيب الإخراج الفعلي:
// 1. Start
// 4. End
// 3. Promise.then         ← Promise.then ينفذ قبل setTimeout!
// 2. setTimeout callback

رسم توضيحي لتدفق التنفيذ:

مكدس الاستدعاء (Call Stack)      طابور المهام الكبرى         طابور المهام الصغرى
                                 [setTimeout callback]       [Promise.then callback]

1. console.log('1. Start')
   → إخراج: 1. Start

2. setTimeout(fn, 0)
   → إضافة الرد إلى طابور المهام الكبرى  ← [setTimeout callback]

3. Promise.resolve().then()
   → إضافة الرد إلى طابور المهام الصغرى                        ← [Promise.then callback]

4. console.log('4. End')
   → إخراج: 4. End

5. تفريغ مكدس الاستدعاء، فحص طابور المهام الصغرى
   → العثور على رد Promise.then
   → تنفيذ: console.log('3. Promise.then')
   → إخراج: 3. Promise.then

6. تفريغ طابور المهام الصغرى
   → قد يحتاج عرض UI (إذا كان هناك تغيير)

7. فحص طابور المهام الكبرى
   → العثور على رد setTimeout
   → تنفيذ: console.log('2. setTimeout callback')
   → إخراج: 2. setTimeout callback

💡 الدرس الأساسي

المهام الصغرى "أكثر استعجالاً" من المهام الكبرى. إذا كنت تريد أن تنفذ عملية ما "بعد انتهاء كتلة الكود الحالية، لكن قبل تحديث UI"، استخدم Promise.then أو queueMicrotask.

setTimeout(0) لا يضمن التنفيذ الفوري، سيتم تأخيره على الأقل حتى تفريغ مكدس الاستدعاء الحالي وتفريغ طابور المهام الصغرى.

Event Loop: How JavaScript Executes Code

Code queue

1
console.log("1")
Executing
2
setTimeout(() => console.log("2"), 0)
3
console.log("3")
4
fetch("/api").then(() => console.log("4"))
5
console.log("5")

Worker (single thread)

👨‍💻
Running
Run console.log("1")

Task queue

No pending tasks

Output log

Waiting for output...

Execution order: not started

Written order: 1, 2, 3, 4, 5

Code is written top to bottom, but it does not always run top to bottom because async work is delayed until the current code finishes.

🔄宏任务与微任务事件循环中的任务优先级
JavaScript 是单线程的,但可以通过任务队列实现异步。就像餐厅只有一个厨师,但他可以同时处理多个订单:先做VIP订单(微任务),再做普通订单(宏任务)。
主线程(执行栈)
同步代码
任务队列
微任务队列(优先级高)
Promise.then()
queueMicrotask()
宏任务队列(优先级低)
setTimeout()
setInterval()
I/O 操作
代码示例
console.log('1')

setTimeout(() => console.log('2'), 0)  // 宏任务

Promise.resolve().then(() => console.log('3'))  // 微任务

console.log('4')

// 输出顺序:1 → 4 → 3 → 2
💡核心要点:每次宏任务执行完后,会清空所有微任务,然后再执行下一个宏任务。这就是为什么 Promise.then() 比 setTimeout() 先执行。

9. تحسين الأداء العملي: اجعل صفحتك "تطير"

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

9.1 القاعدة الذهبية: تجنب التخطيط المتزامن القسري

المشكلة: تبادل قراءة وكتابة خصائص التخطيط، مما يؤدي إلى اهتزاز التخطيط.

عرض المقارنة قبل وبعد التحسين
javascript
// ❌ سيء جدًا: تبادل القراءة والكتابة، يؤدي إلى اهتزاز التخطيط
for (let i = 0; i < elements.length; i++) {
  const height = elements[i].offsetHeight  // قراءة → تخطيط قسري
  elements[i].style.height = (height * 2) + 'px'  // كتابة → تعليم الحاجة لإعادة التدفق
  // قراءة الدورة التالية ستجبر التخطيط مرة أخرى... حلقة مفرغة!
}

// ✅ ممتاز: اقرأ الكل أولاً، ثم اكتب الكل
// الخطوة الأولى: القراءة الدفعية
const heights = []
for (let i = 0; i < elements.length; i++) {
  heights.push(elements[i].offsetHeight)
}

// الخطوة الثانية: الكتابة الدفعية
requestAnimationFrame(() => {
  for (let i = 0; i < elements.length; i++) {
    elements[i].style.height = (heights[i] * 2) + 'px'
  }
})

9.2 استخدام transform وopacity للرسوم المتحركة

المشكلة: استخدام width وheight وleft وtop للرسوم المتحركة يثير إعادة التدفق.

عرض المقارنة قبل وبعد التحسين
css
/* ❌ رسوم متحركة سيئة: تثير إعادة التدفق */
.box {
  transition: width 0.3s, left 0.3s;
}
.box.moving {
  width: 200px;
  left: 100px;
}

/* ✅ رسوم متحركة جيدة: تثير التركيب فقط */
.box {
  transition: transform 0.3s;
}
.box.moving {
  transform: translateX(100px) scaleX(2);
}

9.3 التمرير الافتراضي: حل مشكلة القوائم الكبيرة

المشكلة: عندما يصل عدد عناصر القائمة إلى الآلاف، يؤدي كثرة عقد DOM إلى مشاكل في الأداء.

الفكرة الأساسية: عرض فقط عناصر القائمة المرئية داخل نطاق الرؤية (مع إضافة قليل من التخزين المؤقت)، مما يجعل عدد عقد DOM ثابتًا بغض النظر عن إجمالي البيانات.

渲染性能优化让页面丝滑流畅的秘诀
渲染性能优化的目标是每秒60帧(16.67ms/帧)。就像拍电影,每秒帧数越多,画面越流畅。超过这个时间,用户就会感觉卡顿。
❌ 不好的做法
// 触发重排和重绘
function animate() {
element.style.width = '100px'
element.style.height = '100px'
requestAnimationFrame(animate)
}
性能开销
VS
✅ 优化做法
/* 只触发合成 */
function animate() {
element.style.transform = 'translate3d(0,0,0)'
requestAnimationFrame(animate)
}
性能开销
黄金法则:
1️⃣优先使用 transformopacity 做动画
2️⃣避免频繁读取布局属性(如 offsetWidth)
3️⃣使用 will-change 提前告知浏览器
💡核心要点:渲染路径越长,性能越差。最佳路径是:合成(Composite)> 重绘(Paint)> 布局(Layout)> 样式计算(Style)。尽量让动画停留在"合成"阶段,在 GPU 上完成。
عرض تنفيذ التمرير الافتراضي
vue
<template>
  <div class="virtual-list" @scroll="handleScroll">
    <!-- عنصر وهمي لتمديد شريط التمرير -->
    <div class="phantom" :style="{ height: totalHeight + 'px' }"></div>

    <!-- عناصر القائمة المعروضة فعليًا -->
    <div class="content" :style="{ transform: `translateY(${offsetY}px)` }">
      <div
        v-for="item in visibleItems"
        :key="item.id"
        class="item"
        :style="{ height: itemHeight + 'px' }"
      >
        {{ item.name }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const props = defineProps({
  items: Array,
  itemHeight: { type: Number, default: 50 }
})

const scrollTop = ref(0)
const buffer = 5  // عدد العناصر المؤقتة

// كم عنصرًا يمكن عرضه في المنطقة المرئية
const visibleCount = computed(() => 10)

// الفهرس الابتدائي
const startIndex = computed(() =>
  Math.max(0, Math.floor(scrollTop.value / props.itemHeight) - buffer)
)

// الفهرس النهائي
const endIndex = computed(() =>
  Math.min(props.items.length, startIndex.value + visibleCount.value + buffer * 2)
)

// البيانات المرئية الحالية
const visibleItems = computed(() =>
  props.items.slice(startIndex.value, endIndex.value)
)

// الارتفاع الإجمالي
const totalHeight = computed(() => props.items.length * props.itemHeight)

// الإزاحة
const offsetY = computed(() => startIndex.value * props.itemHeight)

const handleScroll = (e) => {
  scrollTop.value = e.target.scrollTop
}
</script>

9.4 منع الاهتزاز والتقييد: تقليل تكرار إثارة الأحداث

المشكلة: الأحداث المتكررة (مثل scroll وresize) تؤدي إلى مشاكل في الأداء.

عرض تنفيذ منع الاهتزاز والتقييد
javascript
// منع الاهتزاز (Debounce): تأخير التنفيذ، إذا تم الإثارة مرة أخرى خلال وقت التأخير، يعاد الضبط
function debounce(fn, delay) {
  let timer = null
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

// التقييد (Throttle): تنفيذ بفاصل زمني ثابت
function throttle(fn, interval) {
  let lastTime = 0
  return function (...args) {
    const now = Date.now()
    if (now - lastTime >= interval) {
      lastTime = now
      fn.apply(this, args)
    }
  }
}

// مثال على الاستخدام
window.addEventListener('scroll', debounce(handleScroll, 200))
window.addEventListener('resize', throttle(handleResize, 100))

9.5 التحميل الكسول: تأخير تحميل الموارد غير الحرجة

المشكلة: تحميل الكثير من الموارد في الشاشة الأولى يؤدي إلى بطء فتح الصفحة.

عرض تنفيذ التحميل الكسول
javascript
// تحميل كسول للصور
const lazyImages = document.querySelectorAll('img[data-src]')

const imageObserver = new IntersectionObserver((entries, observer) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src  // تحميل الصورة الحقيقية
      img.removeAttribute('data-src')
      observer.unobserve(img)  // توقف عن المراقبة
    }
  })
})

lazyImages.forEach(img => imageObserver.observe(img))

10. مشاكل الأداء التي يجب أن تتعرف عليها الآن

بعد فهم أنبوب عرض المتصفح، يجب أن تكون قادرًا على التعرف على مشاكل الأداء الشائعة التالية:

الكود المشكلموضع المشكلةكيف تصفها للذكاء الاصطناعي
element.style.width = ...تعديل العرض بشكل متكرر في حلقة"هذا سيثير إعادة تدفق متعددة، يُرجى استخدام transform أو المعالجة الدفعية"
height = element.offsetHeightقراءة خصائص التخطيط مباشرة بعد الكتابة"هذا تخطيط متزامن قسري، يُرجى فصل عمليات القراءة والكتابة"
element.className = ...تعديل متكرر لـ class يثير إعادة حساب الأنماط"استخدم classList.add/remove بدلاً منه، لتقليل حساب الأنماط"
حركة بـ width/leftيثير إعادة التدفق وإعادة الرسم، أداء سيئ"استخدم transform وopacity للرسوم المتحركة"
إضافة translateZ(0) لكل العناصرإساءة استخدام تسريع GPU تؤدي إلى انفجار الذاكرة"فعل تسريع GPU فقط للعناصر التي تحتاج حركة"
عرض 10000 عنصر قائمة كلهاكثرة عقد DOM تؤدي إلى تقطع"نفذ التمرير الافتراضي، اعرض فقط المنطقة المرئية"
التلاعب بـ DOM مباشرة في حدث scrollتكرار الإثارة العالي يؤدي إلى تقطع"استخدم requestAnimationFrame أو التقييد للتحسين"
box-shadow كحركة hoverحساب الظل المعقد بطيء جدًا"استخدم transform أو العناصر الوهمية، تجنب تحريك الظل"

إذا قرأت "سجل الفخاخ" في كل فصل بجدية، فأنت الآن تتقن هذه المفاهيم الأساسية:

  • مراحل أنبوب العرض الخمس: DOM/CSSOM → شجرة العرض → التخطيط → الرسم → التركيب
  • إعادة التدفق مقابل إعادة الرسم: إعادة التدفق هي الأكثر تكلفة (تغيير هندسي)، إعادة الرسم أقل (تغيير المظهر)
  • التخطيط المتزامن القسري: تبادل القراءة والكتابة يؤدي إلى اهتزاز التخطيط، يجب الفصل بينهما
  • تسريع GPU: transform وopacity يعالجان بواسطة GPU، الأفضل أداءً
  • حلقة الأحداث: JavaScript أحادي الخيط، يحقق اللا تزامن من خلال طوابير المهام

هذه المفاهيم ستساعدك في تحديد اختناقات الأداء بسرعة.

💡 عند مواجهة مشاكل الأداء، قل للذكاء الاصطناعي هكذا

  • "الرسوم المتحركة متقطعة، تحقق مما إذا كانت تثير إعادة تدفق أو إعادة رسم"
  • "أداء التمرير سيئ، قد تحتاج تقييدًا أو requestAnimationFrame"
  • "البيانات كبيرة الحجم تسبب تقطعًا في القائمة، تحتاج تمريرًا افتراضيًا"
  • "تعديل الأنماط المتكرر يسبب مشكلة في الأداء، يُرجى التحسين باستخدام transform"

11. الخلاصة: جوهر تحسين أنبوب العرض

من خلال دراسة هذه المقالة، يمكننا استخلاص الاستنتاجات الأساسية التالية:

من الناحية العملية: ليس المهم أن يكون التحسين أكثر، بل أن يكون التحسين "في المكان الصحيح". فهم أنبوب عرض المتصفح يجعلك تعرف أين تبذل الجهد وأين تتركه.

من منظور التكلفة:

  • معظم هدر الأداء يأتي من التبادل المتكرر لقراءة وكتابة خصائص التخطيط، ويحتاج إلى الفصل بين القراءة والكتابة والمعالجة الدفعية
  • تأثيرات الرسوم المتحركة المعقدة إذا أثارت إعادة التدفق وإعادة الرسم، فغالبًا ما يكون السبب استخدام "خصائص خاطئة"، وتحتاج إلى transform وopacity لحلها
  • عند مواجهة عرض قوائم بكميات كبيرة من البيانات، الاعتماد على Virtual DOM وحده لم يعد كافيًا، ويجب دمجه مع تقنيات مثل التمرير الافتراضي

الهدف هو: في ظل ظروف المتصفح والعتاد المحددة، جعل كل خطوة من خطوات العرض تحقق مكسبًا واضحًا في الأداء.


12. جدول المصطلحات

المصطلح الإنجليزيالمقابل العربيالشرح
DOMنموذج كائن المستندهيكل شجري يشكله المتصفح بعد تحليل مستند HTML، يمكن لـ JavaScript التلاعب بعناصر الصفحة عبر DOM API
CSSOMنموذج كائن CSSهيكل شجري يشكله المتصفح بعد تحليل CSS، يدمج مع DOM لحساب الأنماط النهائية
Render Treeشجرة العرضتدمج من شجرة DOM وشجرة CSSOM، تحتوي فقط العقد المرئية، تستخدم لحساب التخطيط والرسم اللاحقين
Layoutالتخطيطعملية حساب المعلومات الهندسية (الموقع، الحجم) لكل عقدة في شجرة العرض، تسمى أيضًا Reflow
Reflowإعادة التدفق/الإعادةعندما تتغير الخصائص الهندسية لعنصر (الأبعاد، الموقع)، يحتاج المتصفح لإعادة حساب التخطيط
Paintالرسم/إعادة الرسمعملية رسم أنماط العناصر (اللون، الخلفية، الحدود، إلخ) بعد حساب التخطيط على الشاشة
Repaintإعادة الرسمعندما تتغير خصائص المظهر لعنصر (كاللون، الخلفية) دون التأثير على الخصائص الهندسية، يثار تحديث الرسم
Compositeالتركيبعملية دمج طبقات الرسم المتعددة (Layer) في الصورة النهائية على الشاشة، تنفذ عادة على GPU
Layerطبقة/طبقة تركيبسطح رسم مستقل ينشئه المتصفح لتحسين العرض، يمكن تحويله وتركيبه بشكل منفصل
Event Loopحلقة الأحداثآلية تنفيذ JavaScript اللا تزامنية، مسؤولة عن جدولة تنفيذ المهام الكبرى والمهام الصغرى
Call Stackمكدس الاستدعاءهيكل بيانات يسجل دوال JavaScript التي يتم تنفيذها حاليًا
Macro Taskمهمة كبرىنوع مهام بأولوية أقل في حلقة الأحداث، مثل setTimeout وsetInterval وعمليات I/O
Micro Taskمهمة صغرىنوع مهام بأولوية أعلى في حلقة الأحداث، مثل Promise.then وMutationObserver
Forced Synchronous Layoutتخطيط متزامن قسريتبادل قراءة وكتابة خصائص التخطيط في JavaScript، مما يجبر المتصفح على تنفيذ حساب التخطيط فورًا، مشكلة أداء
Layout Thrashingاهتزاز التخطيطظاهرة انخفاض حاد في الأداء بسبب التخطيط المتزامن القسري المتكرر
Virtual Scrollingتمرير افتراضيتقنية لعرض فقط عناصر القائمة المرئية داخل نطاق الرؤية، لتحسين أداء القوائم كبيرة البيانات
RAFطلب إطار الحركةAPI يوفره المتصفح لتنفيذ كود JavaScript المتعلق بالرسوم المتحركة قبل إعادة الرسم التالية