الأمان والأداء
تحسين أداء CSS
تحسين أداء CSS
يؤثر CSS على كل من وقت التحميل والأداء أثناء التشغيل. يمكن أن يتسبب CSS غير المُحسّن في عرض بطيء للصفحة ورسوم متحركة متقطعة وإعادة رسم غير ضرورية. يغطي هذا الدرس تقنيات تحسين تسليم CSS وتقليل تكاليف العرض وتحسين الأداء المرئي من خلال ميزات CSS الحديثة وأفضل الممارسات.
CSS الحرج
أدرج CSS الحرج للمحتوى أعلى الصفحة مباشرة للقضاء على حظر العرض:
<!-- CSS حرج مدمج في <head> -->
<style>
/* فقط الأنماط للمحتوى أعلى الصفحة */
.header { background: #333; padding: 1rem; }
.hero { min-height: 100vh; display: flex; }
.hero h1 { font-size: 3rem; color: #fff; }
</style>
<!-- تحميل CSS المتبقي بشكل غير متزامن -->
<link rel="preload" href="/css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
<style>
/* فقط الأنماط للمحتوى أعلى الصفحة */
.header { background: #333; padding: 1rem; }
.hero { min-height: 100vh; display: flex; }
.hero h1 { font-size: 3rem; color: #fff; }
</style>
<!-- تحميل CSS المتبقي بشكل غير متزامن -->
<link rel="preload" href="/css/main.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="/css/main.css"></noscript>
ملاحظة: يجب أن يكون CSS الحرج أقل من 14 كيلوبايت (حجم أول حزمة TCP) لتحقيق أقصى أداء للرسم الأول. يمكن لأدوات مثل Critical أو PurgeCSS أتمتة الاستخراج.
احتواء CSS
استخدم احتواء CSS للحد من نطاق التخطيط والرسم:
/* عزل عرض المكون */
.card {
contain: layout style paint;
/* المتصفح يعلم أن التغييرات بالداخل لن تؤثر على الخارج */
}
/* احتواء صارم للأدوات المعزولة تمامًا */
.widget {
contain: strict;
/* يعادل: layout style paint size */
}
/* احتواء المحتوى للمحتوى الديناميكي */
.content-area {
contain: content;
/* يعادل: layout style paint */
}
/* مثال: تحسين قائمة */
.list-item {
contain: layout style;
/* التغييرات على عنصر واحد لا تؤدي إلى إعادة تدفق الآخرين */
}
.card {
contain: layout style paint;
/* المتصفح يعلم أن التغييرات بالداخل لن تؤثر على الخارج */
}
/* احتواء صارم للأدوات المعزولة تمامًا */
.widget {
contain: strict;
/* يعادل: layout style paint size */
}
/* احتواء المحتوى للمحتوى الديناميكي */
.content-area {
contain: content;
/* يعادل: layout style paint */
}
/* مثال: تحسين قائمة */
.list-item {
contain: layout style;
/* التغييرات على عنصر واحد لا تؤدي إلى إعادة تدفق الآخرين */
}
نصيحة: استخدم `content-visibility: auto` للمحتوى خارج الشاشة لتخطي العرض تمامًا. يمكن أن يؤدي هذا إلى تحسين وقت العرض الأولي بشكل كبير للصفحات الطويلة.
خاصية content-visibility
/* تخطي عرض المحتوى خارج الشاشة */
.section {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* احتجاز المساحة */
}
/* مثال: مقال طويل مع أقسام */
<style>
.article-section {
content-visibility: auto;
contain-intrinsic-size: 0 400px;
}
</style>
<article>
<section class="article-section">...</section>
<section class="article-section">...</section>
<section class="article-section">...</section>
</article>
/* النتيجة: يتم عرض الأقسام المرئية فقط */
/* يمكن تقليل وقت العرض بنسبة 50-70٪ في الصفحات الطويلة */
.section {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* احتجاز المساحة */
}
/* مثال: مقال طويل مع أقسام */
<style>
.article-section {
content-visibility: auto;
contain-intrinsic-size: 0 400px;
}
</style>
<article>
<section class="article-section">...</section>
<section class="article-section">...</section>
<section class="article-section">...</section>
</article>
/* النتيجة: يتم عرض الأقسام المرئية فقط */
/* يمكن تقليل وقت العرض بنسبة 50-70٪ في الصفحات الطويلة */
خاصية will-change
ألمح للمتصفح بالتغييرات القادمة للتحسين:
/* تحسين الرسوم المتحركة القادمة */
.modal {
will-change: transform, opacity;
}
/* طبّق قبل التغيير، أزل بعده */
.element:hover {
will-change: transform;
}
.element:active {
transform: scale(1.1);
will-change: auto; /* إعادة التعيين بعد الرسوم المتحركة */
}
/* مثال JavaScript */
const element = document.querySelector('.animate');
element.addEventListener('mouseenter', () => {
element.style.willChange = 'transform';
});
element.addEventListener('animationend', () => {
element.style.willChange = 'auto';
});
.modal {
will-change: transform, opacity;
}
/* طبّق قبل التغيير، أزل بعده */
.element:hover {
will-change: transform;
}
.element:active {
transform: scale(1.1);
will-change: auto; /* إعادة التعيين بعد الرسوم المتحركة */
}
/* مثال JavaScript */
const element = document.querySelector('.animate');
element.addEventListener('mouseenter', () => {
element.style.willChange = 'transform';
});
element.addEventListener('animationend', () => {
element.style.willChange = 'auto';
});
تحذير: لا تفرط في استخدام `will-change`. يستهلك موارد الذاكرة والـ GPU. استخدمه فقط للعناصر التي ستتغير فعلاً، وأزله عند الانتهاء.
تسريع GPU
استخدم transform و opacity للرسوم المتحركة السلسة:
/* سيء: يؤدي إلى إعادة حساب التخطيط */
.box {
transition: width 0.3s, height 0.3s, top 0.3s, left 0.3s;
}
.box:hover {
width: 200px;
height: 200px;
top: 50px;
left: 50px;
}
/* جيد: يستخدم تركيب GPU */
.box {
transition: transform 0.3s, opacity 0.3s;
}
.box:hover {
transform: scale(1.5) translate(25px, 25px);
opacity: 0.9;
}
/* فرض تسريع GPU مع translateZ */
.animated {
transform: translateZ(0); /* ينشئ طبقة تركيب جديدة */
backface-visibility: hidden; /* يمنع الوميض */
}
.box {
transition: width 0.3s, height 0.3s, top 0.3s, left 0.3s;
}
.box:hover {
width: 200px;
height: 200px;
top: 50px;
left: 50px;
}
/* جيد: يستخدم تركيب GPU */
.box {
transition: transform 0.3s, opacity 0.3s;
}
.box:hover {
transform: scale(1.5) translate(25px, 25px);
opacity: 0.9;
}
/* فرض تسريع GPU مع translateZ */
.animated {
transform: translateZ(0); /* ينشئ طبقة تركيب جديدة */
backface-visibility: hidden; /* يمنع الوميض */
}
تقليل إعادة الرسم وإعادة التدفق
قلل من تذبذب التخطيط وإعادة الحسابات غير الضرورية:
// سيء: إعادة تدفق متعددة
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
const width = box.offsetWidth; // قراءة (تؤدي إلى إعادة تدفق)
box.style.width = width + 10 + 'px'; // كتابة (تؤدي إلى إعادة تدفق)
});
// جيد: تجميع القراءات والكتابات
const boxes = document.querySelectorAll('.box');
const widths = Array.from(boxes).map(box => box.offsetWidth);
boxes.forEach((box, i) => {
box.style.width = widths[i] + 10 + 'px';
});
// استخدم فئات CSS بدلاً من الأنماط المضمنة
// سيء: يفرض إعادة حساب النمط
element.style.width = '100px';
element.style.height = '100px';
element.style.background = 'red';
// جيد: تغيير فئة واحدة
element.classList.add('large-red-box');
// استخدم DocumentFragment لإدراجات DOM متعددة
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
container.appendChild(fragment); // إعادة تدفق واحدة
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
const width = box.offsetWidth; // قراءة (تؤدي إلى إعادة تدفق)
box.style.width = width + 10 + 'px'; // كتابة (تؤدي إلى إعادة تدفق)
});
// جيد: تجميع القراءات والكتابات
const boxes = document.querySelectorAll('.box');
const widths = Array.from(boxes).map(box => box.offsetWidth);
boxes.forEach((box, i) => {
box.style.width = widths[i] + 10 + 'px';
});
// استخدم فئات CSS بدلاً من الأنماط المضمنة
// سيء: يفرض إعادة حساب النمط
element.style.width = '100px';
element.style.height = '100px';
element.style.background = 'red';
// جيد: تغيير فئة واحدة
element.classList.add('large-red-box');
// استخدم DocumentFragment لإدراجات DOM متعددة
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const div = document.createElement('div');
fragment.appendChild(div);
}
container.appendChild(fragment); // إعادة تدفق واحدة
ملاحظة: قراءة خصائص التخطيط (offsetWidth، offsetHeight، getComputedStyle) تفرض تخطيطًا متزامنًا. جمّع كل القراءات قبل الكتابات لتجنب تذبذب التخطيط.
أداء CSS-in-JS
حسّن توليد CSS في وقت التشغيل:
// سيء: ينشئ كائن نمط جديد في كل عرض
function Component({ color }) {
return (
<div style={{ backgroundColor: color, padding: '20px' }}>
Content
</div>
);
}
// جيد: استخراج الأنماط الثابتة
const staticStyles = { padding: '20px' };
function Component({ color }) {
return (
<div style={{ ...staticStyles, backgroundColor: color }}>
Content
</div>
);
}
// أفضل: استخدام فئات CSS مع متغيرات ديناميكية
const styles = `
.component {
padding: 20px;
background-color: var(--bg-color);
}
`;
function Component({ color }) {
return (
<div className="component" style={{ '--bg-color': color }}>
Content
</div>
);
}
function Component({ color }) {
return (
<div style={{ backgroundColor: color, padding: '20px' }}>
Content
</div>
);
}
// جيد: استخراج الأنماط الثابتة
const staticStyles = { padding: '20px' };
function Component({ color }) {
return (
<div style={{ ...staticStyles, backgroundColor: color }}>
Content
</div>
);
}
// أفضل: استخدام فئات CSS مع متغيرات ديناميكية
const styles = `
.component {
padding: 20px;
background-color: var(--bg-color);
}
`;
function Component({ color }) {
return (
<div className="component" style={{ '--bg-color': color }}>
Content
</div>
);
}
إزالة CSS غير المستخدم
أزل الأنماط غير المستخدمة لتقليل حجم الحزمة:
// إعداد PurgeCSS (purge.config.js)
module.exports = {
content: [
'./src/**/*.html',
'./src/**/*.js',
'./src/**/*.jsx',
],
css: ['./src/styles/**/*.css'],
output: './dist/styles/',
safelist: [
'active', 'open', 'visible', // فئات ديناميكية
/^modal-/, // أنماط regex
]
};
// إعداد Tailwind CSS purge
module.exports = {
purge: {
enabled: true,
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
options: {
safelist: ['bg-red-500', 'text-center'],
}
},
};
// النتيجة: يمكن تقليل CSS من 500 كيلوبايت إلى 10-20 كيلوبايت
module.exports = {
content: [
'./src/**/*.html',
'./src/**/*.js',
'./src/**/*.jsx',
],
css: ['./src/styles/**/*.css'],
output: './dist/styles/',
safelist: [
'active', 'open', 'visible', // فئات ديناميكية
/^modal-/, // أنماط regex
]
};
// إعداد Tailwind CSS purge
module.exports = {
purge: {
enabled: true,
content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
options: {
safelist: ['bg-red-500', 'text-center'],
}
},
};
// النتيجة: يمكن تقليل CSS من 500 كيلوبايت إلى 10-20 كيلوبايت
نصيحة: استخدم PostCSS مع cssnano لتصغير وتحسين CSS. فعّل ميزات مثل دمج القواعد وإزالة التكرارات وتحسين تعبيرات calc().
تحسين المحددات
/* سيء: محددات معقدة */
body div.container ul li a.link { color: blue; }
/* المتصفح يقرأ من اليمين إلى اليسار، يفحص كل رابط */
/* جيد: محددات بسيطة ومحددة */
.nav-link { color: blue; }
/* سيء: محدد عالمي */
* { box-sizing: border-box; }
/* جيد: استهداف عناصر محددة */
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
/* استخدم BEM للخصوصية المسطحة */
.card { }
.card__header { }
.card__title { }
.card--featured { }
/* تجنب المحددات الزائفة المكلفة */
/* سيء */
:nth-child(n+1):nth-child(-n+10) { }
/* جيد */
.item:nth-child(-n+10) { }
body div.container ul li a.link { color: blue; }
/* المتصفح يقرأ من اليمين إلى اليسار، يفحص كل رابط */
/* جيد: محددات بسيطة ومحددة */
.nav-link { color: blue; }
/* سيء: محدد عالمي */
* { box-sizing: border-box; }
/* جيد: استهداف عناصر محددة */
html { box-sizing: border-box; }
*, *::before, *::after { box-sizing: inherit; }
/* استخدم BEM للخصوصية المسطحة */
.card { }
.card__header { }
.card__title { }
.card--featured { }
/* تجنب المحددات الزائفة المكلفة */
/* سيء */
:nth-child(n+1):nth-child(-n+10) { }
/* جيد */
.item:nth-child(-n+10) { }
استراتيجيات تحميل CSS
<!-- الاستراتيجية 1: حرج + غير متزامن -->
<style>/* CSS حرج */</style>
<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">
<!-- الاستراتيجية 2: استعلامات الوسائط للتحميل الشرطي -->
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)">
<!-- الاستراتيجية 3: التحميل المسبق للموارد عالية الأولوية -->
<link rel="preload" href="fonts/main.woff2" as="font" crossorigin>
<link rel="preload" href="critical.css" as="style">
<!-- الاستراتيجية 4: DNS prefetch لـ CSS الخارجي -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter">
<style>/* CSS حرج */</style>
<link rel="preload" href="main.css" as="style" onload="this.rel='stylesheet'">
<!-- الاستراتيجية 2: استعلامات الوسائط للتحميل الشرطي -->
<link rel="stylesheet" href="print.css" media="print">
<link rel="stylesheet" href="mobile.css" media="(max-width: 768px)">
<!-- الاستراتيجية 3: التحميل المسبق للموارد عالية الأولوية -->
<link rel="preload" href="fonts/main.woff2" as="font" crossorigin>
<link rel="preload" href="critical.css" as="style">
<!-- الاستراتيجية 4: DNS prefetch لـ CSS الخارجي -->
<link rel="dns-prefetch" href="//fonts.googleapis.com">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter">
تمرين: راجع CSS لموقعك باستخدام علامة التبويب Coverage في Chrome DevTools. حدد CSS غير المستخدم (عادةً 60-80٪ في الأطر). اضبط PurgeCSS أو أداة مماثلة لإزالة الأنماط غير المستخدمة. استخرج CSS الحرج للمحتوى أعلى الصفحة ونفّذ التحميل غير المتزامن. قِس التأثير على First Contentful Paint (FCP) باستخدام Lighthouse قبل وبعد التحسين.