معالج SASS/SCSS

توجيهات التحكم: الحلقات (@for و @each و @while)

20 دقيقة الدرس 15 من 30

الحلقات في SASS

الحلقات هي واحدة من أقوى الميزات في SASS، مما يمكنك من إنشاء أنماط CSS متكررة بكفاءة. توفر SASS ثلاثة أنواع من الحلقات: @for للتكرار الرقمي، و @each للتكرار عبر القوائم والخرائط، و @while للتكرار الشرطي. تسمح لك توجيهات التحكم هذه بكتابة كود DRY (لا تكرر نفسك) وإنشاء أنظمة فئات خدمية كاملة بعدة أسطر فقط من SASS.

حلقة @for: from...through مقابل from...to

تتكرر حلقة @for عبر نطاق من الأرقام. توفر SASS نوعين: from...through (شاملة القيمة النهائية) و from...to (باستثناء القيمة النهائية).

@for...through (شاملة)

// through تتضمن القيمة النهائية
@for $i from 1 through 5 {
  .item-#{$i} {
    width: 20px * $i;
  }
}

// CSS المجمع:
// .item-1 { width: 20px; }
// .item-2 { width: 40px; }
// .item-3 { width: 60px; }
// .item-4 { width: 80px; }
// .item-5 { width: 100px; }

// مثال عملي: طبقات z-index
@for $layer from 1 through 10 {
  .layer-#{$layer} {
    z-index: $layer * 10;
  }
}

// النتيجة:
// .layer-1 { z-index: 10; }
// .layer-2 { z-index: 20; }
// ...
// .layer-10 { z-index: 100; }

@for...to (باستثناء)

// to تستثني القيمة النهائية
@for $i from 1 to 5 {
  .col-#{$i} {
    flex: $i;
  }
}

// CSS المجمع (لاحظ: يصل فقط إلى 4، وليس 5):
// .col-1 { flex: 1; }
// .col-2 { flex: 2; }
// .col-3 { flex: 3; }
// .col-4 { flex: 4; }

// العد العكسي
@for $i from 5 through 1 {
  .priority-#{$i} {
    order: $i;
  }
}

// النتيجة:
// .priority-5 { order: 5; }
// .priority-4 { order: 4; }
// .priority-3 { order: 3; }
// .priority-2 { order: 2; }
// .priority-1 { order: 1; }
نصيحة: استخدم through عندما تريد تضمين الرقم النهائي (الأكثر شيوعاً)، و to عندما تريد التوقف قبله. فكر في through كـ <= و to كـ < في عوامل المقارنة.

إنشاء فئات خدمية باستخدام @for

واحد من أكثر الاستخدامات العملية لحلقات @for هو إنشاء أنظمة فئات خدمية، مشابهة لتلك الموجودة في أطر العمل مثل Tailwind CSS أو Bootstrap.

خدمات الهامش والحشو

// إنشاء خدمات الهامش
@for $i from 0 through 10 {
  .m-#{$i} {
    margin: #{$i * 4}px;
  }

  .mt-#{$i} {
    margin-top: #{$i * 4}px;
  }

  .mr-#{$i} {
    margin-right: #{$i * 4}px;
  }

  .mb-#{$i} {
    margin-bottom: #{$i * 4}px;
  }

  .ml-#{$i} {
    margin-left: #{$i * 4}px;
  }

  .mx-#{$i} {
    margin-left: #{$i * 4}px;
    margin-right: #{$i * 4}px;
  }

  .my-#{$i} {
    margin-top: #{$i * 4}px;
    margin-bottom: #{$i * 4}px;
  }
}

// النتيجة: .m-0 إلى .m-10، .mt-0 إلى .mt-10، إلخ.
// الاستخدام: <div class="m-4 mt-8">...</div>

// إنشاء خدمات الحشو
@for $i from 0 through 10 {
  .p-#{$i} {
    padding: #{$i * 4}px;
  }

  .pt-#{$i} {
    padding-top: #{$i * 4}px;
  }

  .pr-#{$i} {
    padding-right: #{$i * 4}px;
  }

  .pb-#{$i} {
    padding-bottom: #{$i * 4}px;
  }

  .pl-#{$i} {
    padding-left: #{$i * 4}px;
  }

  .px-#{$i} {
    padding-left: #{$i * 4}px;
    padding-right: #{$i * 4}px;
  }

  .py-#{$i} {
    padding-top: #{$i * 4}px;
    padding-bottom: #{$i * 4}px;
  }
}

// متقدم: تباعد استجابي
$breakpoints: (
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px
);

@each $breakpoint-name, $breakpoint-value in $breakpoints {
  @media (min-width: $breakpoint-value) {
    @for $i from 0 through 10 {
      .m-#{$breakpoint-name}-#{$i} {
        margin: #{$i * 4}px;
      }
    }
  }
}

// النتيجة: .m-sm-4، .m-md-4، .m-lg-4، إلخ.

خدمات العرض والارتفاع

// إنشاء فئات عرض بالنسبة المئوية
@for $i from 1 through 12 {
  .w-#{$i} {
    width: percentage($i / 12);
  }

  .col-#{$i} {
    flex: 0 0 percentage($i / 12);
    max-width: percentage($i / 12);
  }
}

// النتيجة:
// .w-1 { width: 8.33333%; }
// .w-2 { width: 16.66667%; }
// ...
// .w-12 { width: 100%; }

// خدمات العرض الثابت
@for $i from 1 through 20 {
  .w-#{$i * 20} {
    width: #{$i * 20}px;
  }

  .h-#{$i * 20} {
    height: #{$i * 20}px;
  }
}

// النتيجة: .w-20، .w-40، .w-60، ...، .w-400
// النتيجة: .h-20، .h-40، .h-60، ...، .h-400

// خدمات viewport الكاملة
@for $i from 1 through 10 {
  .vw-#{$i * 10} {
    width: #{$i * 10}vw;
  }

  .vh-#{$i * 10} {
    height: #{$i * 10}vh;
  }
}

// النتيجة: .vw-10 إلى .vw-100، .vh-10 إلى .vh-100

حلقة @each: التكرار عبر القوائم

حلقة @each مثالية للتكرار عبر قوائم القيم، مما يجعلها مثالية لإنشاء تنويعات المكونات بناءً على مجموعات محددة مسبقاً من القيم.

@each الأساسي مع القوائم

// تكرار قائمة بسيط
$sizes: small, medium, large, x-large;

@each $size in $sizes {
  .button-#{$size} {
    @if $size == small {
      padding: 8px 16px;
      font-size: 14px;
    } @else if $size == medium {
      padding: 12px 24px;
      font-size: 16px;
    } @else if $size == large {
      padding: 16px 32px;
      font-size: 18px;
    } @else if $size == x-large {
      padding: 20px 40px;
      font-size: 20px;
    }
  }
}

// خدمات المحاذاة
$alignments: left, center, right, justify;

@each $align in $alignments {
  .text-#{$align} {
    text-align: $align;
  }
}

// النتيجة:
// .text-left { text-align: left; }
// .text-center { text-align: center; }
// .text-right { text-align: right; }
// .text-justify { text-align: justify; }

// خدمات العرض
$displays: block, inline, inline-block, flex, grid, none;

@each $display in $displays {
  .d-#{$display} {
    display: $display;
  }
}

// خدمات الموضع
$positions: static, relative, absolute, fixed, sticky;

@each $position in $positions {
  .position-#{$position} {
    position: $position;
  }
}

نظام الألوان مع @each

// تعريف لوحة الألوان
$colors: (
  primary: #007bff,
  secondary: #6c757d,
  success: #28a745,
  danger: #dc3545,
  warning: #ffc107,
  info: #17a2b8,
  light: #f8f9fa,
  dark: #343a40
);

// إنشاء خدمات لون النص
@each $name, $color in $colors {
  .text-#{$name} {
    color: $color;
  }

  .bg-#{$name} {
    background-color: $color;
  }

  .border-#{$name} {
    border-color: $color;
  }

  .btn-#{$name} {
    background-color: $color;
    border-color: $color;
    color: if(lightness($color) > 50%, #000, #fff);

    &:hover {
      background-color: darken($color, 7.5%);
      border-color: darken($color, 10%);
    }

    &:active {
      background-color: darken($color, 10%);
      border-color: darken($color, 12.5%);
    }
  }

  .alert-#{$name} {
    background-color: mix(white, $color, 85%);
    border: 1px solid mix(white, $color, 70%);
    color: darken($color, 10%);
  }
}

// النتيجة: نظام ألوان كامل مع فئات .text-* و .bg-* و .border-* و .btn-* و .alert-*

@each مع الخرائط (تفكيك مفتاح-قيمة)

عند استخدام @each مع الخرائط، يمكنك تفكيك كل من المفتاح والقيمة، مما يجعلها مثالية لإنشاء أنماط بناءً على كائنات التكوين.

نظام نقاط التوقف

// خريطة نقاط التوقف الاستجابية
$breakpoints: (
  xs: 0,
  sm: 576px,
  md: 768px,
  lg: 992px,
  xl: 1200px,
  xxl: 1400px
);

// إنشاء عروض الحاوية
@each $name, $width in $breakpoints {
  @if $width > 0 {
    @media (min-width: $width) {
      .container {
        max-width: $width - 20px;
      }

      .container-#{$name} {
        max-width: $width - 20px;
      }
    }
  } @else {
    .container-#{$name} {
      width: 100%;
    }
  }
}

// خدمات إخفاء/إظهار لكل نقطة توقف
@each $name, $width in $breakpoints {
  @if $width > 0 {
    @media (min-width: $width) {
      .d-#{$name}-none { display: none; }
      .d-#{$name}-block { display: block; }
      .d-#{$name}-flex { display: flex; }
      .d-#{$name}-grid { display: grid; }
    }
  }
}

// النتيجة: .d-sm-none، .d-md-flex، .d-lg-grid، إلخ.

مقياس الطباعة

// مقياس حجم الخط
$font-sizes: (
  xs: 12px,
  sm: 14px,
  base: 16px,
  lg: 18px,
  xl: 20px,
  2xl: 24px,
  3xl: 30px,
  4xl: 36px,
  5xl: 48px,
  6xl: 60px
);

@each $name, $size in $font-sizes {
  .text-#{$name} {
    font-size: $size;
  }
}

// مقياس وزن الخط
$font-weights: (
  thin: 100,
  light: 300,
  normal: 400,
  medium: 500,
  semibold: 600,
  bold: 700,
  black: 900
);

@each $name, $weight in $font-weights {
  .font-#{$name} {
    font-weight: $weight;
  }
}

// مقياس ارتفاع السطر
$line-heights: (
  none: 1,
  tight: 1.25,
  snug: 1.375,
  normal: 1.5,
  relaxed: 1.625,
  loose: 2
);

@each $name, $height in $line-heights {
  .leading-#{$name} {
    line-height: $height;
  }
}

@each لإنشاء فئات الأيقونات

خدمات الأيقونات والصور

// أيقونات وسائل التواصل الاجتماعي
$social-icons: (
  facebook: #1877f2,
  twitter: #1da1f2,
  instagram: #e4405f,
  linkedin: #0a66c2,
  youtube: #ff0000,
  github: #333,
  discord: #5865f2,
  whatsapp: #25d366
);

@each $name, $color in $social-icons {
  .icon-#{$name} {
    color: $color;

    &:hover {
      color: darken($color, 10%);
    }
  }

  .btn-#{$name} {
    background-color: $color;
    color: white;
    border: none;

    &:hover {
      background-color: darken($color, 10%);
    }
  }

  .badge-#{$name} {
    background-color: mix(white, $color, 85%);
    color: $color;
    border: 1px solid mix(white, $color, 70%);
  }
}

// خدمات حجم الصورة
$image-sizes: (
  xs: 24px,
  sm: 32px,
  md: 48px,
  lg: 64px,
  xl: 96px,
  2xl: 128px
);

@each $name, $size in $image-sizes {
  .avatar-#{$name} {
    width: $size;
    height: $size;
    border-radius: 50%;
    object-fit: cover;
  }

  .icon-#{$name} {
    width: $size;
    height: $size;
  }
}

حلقة @while: التكرار الشرطي

تستمر حلقة @while في التكرار طالما ظل الشرط صحيحاً. إنها أقل استخداماً من @for أو @each، لكنها مفيدة للسيناريوهات الأكثر تعقيداً حيث عدد التكرارات غير معروف مسبقاً.

صيغة @while وأمثلة

// حلقة @while أساسية
$i: 1;

@while $i <= 5 {
  .item-#{$i} {
    width: 50px * $i;
  }

  $i: $i + 1; // لا تنسَ الزيادة!
}

// النتيجة:
// .item-1 { width: 50px; }
// .item-2 { width: 100px; }
// .item-3 { width: 150px; }
// .item-4 { width: 200px; }
// .item-5 { width: 250px; }

// قوى العدد 2 (مفيد لمقاييس z-index)
$power: 1;

@while $power <= 1024 {
  .z-#{$power} {
    z-index: $power;
  }

  $power: $power * 2;
}

// النتيجة: .z-1، .z-2، .z-4، .z-8، .z-16، .z-32، .z-64، .z-128، .z-256، .z-512، .z-1024

// متتالية فيبوناتشي (للتباعد)
$fib-a: 0;
$fib-b: 1;
$count: 0;

@while $count < 10 {
  .fib-space-#{$count} {
    margin: #{$fib-b}px;
  }

  $temp: $fib-a + $fib-b;
  $fib-a: $fib-b;
  $fib-b: $temp;
  $count: $count + 1;
}

// النتيجة: .fib-space-0 (1px)، .fib-space-1 (1px)، .fib-space-2 (2px)،
// .fib-space-3 (3px)، .fib-space-4 (5px)، .fib-space-5 (8px)، إلخ.
تحذير: كن حذراً جداً مع حلقات @while! إذا لم يصبح شرطك خاطئاً أبداً، ستعمل الحلقة إلى الأبد وتتسبب في تجميد أو تعطل مترجم SASS. تأكد دائماً من أن حلقتك لديها طريقة للإنهاء، وتحقق مرة أخرى من منطق الزيادة الخاص بك.

دمج الحلقات مع الاستيفاء #{$var}

الاستيفاء ضروري عند استخدام الحلقات لإنشاء أسماء فئات وقيم خصائص ديناميكية. تقوم صيغة #{$variable} بإدراج قيم المتغيرات في المحددات وأسماء الخصائص.

تقنيات استيفاء متقدمة

// الاستيفاء في المحددات
$directions: top, right, bottom, left;

@each $direction in $directions {
  .border-#{$direction} {
    border-#{$direction}: 1px solid #ccc;
  }

  .rounded-#{$direction} {
    border-#{$direction}-left-radius: 8px;
    border-#{$direction}-right-radius: 8px;
  }
}

// الاستيفاء مع الحسابات
$steps: 5;

@for $i from 1 through $steps {
  $opacity: $i / $steps;

  .opacity-#{$i * 20} {
    opacity: $opacity;
  }
}

// النتيجة: .opacity-20 (0.2)، .opacity-40 (0.4)، ...، .opacity-100 (1)

// استيفاء معقد
$properties: (
  m: margin,
  p: padding
);

$directions: (
  t: top,
  r: right,
  b: bottom,
  l: left,
  x: (left, right),
  y: (top, bottom)
);

@each $prop-key, $prop-value in $properties {
  @each $dir-key, $dir-value in $directions {
    @for $i from 0 through 10 {
      .#{$prop-key}#{$dir-key}-#{$i} {
        @if type-of($dir-value) == list {
          @each $side in $dir-value {
            #{$prop-value}-#{$side}: #{$i * 4}px;
          }
        } @else {
          #{$prop-value}-#{$dir-value}: #{$i * 4}px;
        }
      }
    }
  }
}

// النتيجة: .mt-0، .mt-4، .mt-8، .px-0، .px-4، .my-8، إلخ.

مثال من العالم الواقعي: نظام فئات خدمية كامل

بناء نظام خدمي مشابه لـ Tailwind

// التكوين
$base-spacing: 4px;
$spacing-scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 64;

$colors: (
  slate: #64748b,
  gray: #6b7280,
  red: #ef4444,
  orange: #f97316,
  yellow: #eab308,
  green: #22c55e,
  blue: #3b82f6,
  indigo: #6366f1,
  purple: #a855f7,
  pink: #ec4899
);

$breakpoints: (
  sm: 640px,
  md: 768px,
  lg: 1024px,
  xl: 1280px,
  2xl: 1536px
);

// إنشاء خدمات التباعد
@each $space in $spacing-scale {
  $value: $space * $base-spacing;

  // الهامش
  .m-#{$space} { margin: $value; }
  .mx-#{$space} { margin-left: $value; margin-right: $value; }
  .my-#{$space} { margin-top: $value; margin-bottom: $value; }
  .mt-#{$space} { margin-top: $value; }
  .mr-#{$space} { margin-right: $value; }
  .mb-#{$space} { margin-bottom: $value; }
  .ml-#{$space} { margin-left: $value; }

  // الحشو
  .p-#{$space} { padding: $value; }
  .px-#{$space} { padding-left: $value; padding-right: $value; }
  .py-#{$space} { padding-top: $value; padding-bottom: $value; }
  .pt-#{$space} { padding-top: $value; }
  .pr-#{$space} { padding-right: $value; }
  .pb-#{$space} { padding-bottom: $value; }
  .pl-#{$space} { padding-left: $value; }

  // الفجوة
  .gap-#{$space} { gap: $value; }
  .gap-x-#{$space} { column-gap: $value; }
  .gap-y-#{$space} { row-gap: $value; }
}

// إنشاء خدمات الألوان
@each $name, $color in $colors {
  // ألوان النص
  .text-#{$name} {
    color: $color;
  }

  // ألوان الخلفية
  .bg-#{$name} {
    background-color: $color;
  }

  // ألوان الحدود
  .border-#{$name} {
    border-color: $color;
  }

  // إنشاء الظلال (100-900)
  @for $shade from 1 through 9 {
    $lightness: 95 - ($shade * 10);

    .text-#{$name}-#{$shade}00 {
      color: scale-color($color, $lightness: $lightness);
    }

    .bg-#{$name}-#{$shade}00 {
      background-color: scale-color($color, $lightness: $lightness);
    }
  }
}

// خدمات استجابية
@each $breakpoint-name, $breakpoint-value in $breakpoints {
  @media (min-width: $breakpoint-value) {
    // تباعد استجابي
    @each $space in $spacing-scale {
      $value: $space * $base-spacing;

      .#{$breakpoint-name}\:m-#{$space} {
        margin: $value;
      }

      .#{$breakpoint-name}\:p-#{$space} {
        padding: $value;
      }
    }

    // عرض استجابي
    .#{$breakpoint-name}\:block { display: block; }
    .#{$breakpoint-name}\:flex { display: flex; }
    .#{$breakpoint-name}\:grid { display: grid; }
    .#{$breakpoint-name}\:hidden { display: none; }
  }
}

// النتيجة: مئات من فئات الخدمة مثل:
// .m-4، .px-8، .text-blue، .bg-red-500، .sm:flex، .lg:p-12، إلخ.

تمرين 1: بناء نظام شبكة كامل

أنشئ نظام شبكة من 12 عموداً باستخدام الحلقات:

  1. استخدم @for لإنشاء فئات .col-1 حتى .col-12 مع عروض مناسبة
  2. أنشئ فئات الإزاحة .offset-1 حتى .offset-11 باستخدام margin-left
  3. أنشئ فئات push و pull (.push-1، .pull-1) باستخدام الموضع النسبي
  4. أضف متغيرات استجابية لنقاط التوقف sm و md و lg و xl (.col-md-6، .col-lg-4، إلخ.)
  5. قم بتضمين خدمات الفجوة (.gap-1 حتى .gap-8) لتخطيطات grid/flexbox

تمرين 2: نظام الأيقونات والشارات

قم ببناء نظام أيقونات وشارات باستخدام الحلقات:

  1. أنشئ خريطة لـ 8 منصات وسائط اجتماعية على الأقل مع ألوان علامتها التجارية
  2. استخدم @each لإنشاء فئات .icon-[name] مع ألوان مناسبة
  3. أنشئ فئات أزرار .btn-[name] مع ألوان العلامة التجارية
  4. أنشئ فئات .badge-[name] مع إصدارات خلفية فاتحة
  5. أضف متغيرات الحجم (sm، md، lg) لكل أيقونة باستخدام حلقات متداخلة
  6. أنشئ حالات التمرير التي تغمق الألوان بنسبة 10%

تمرين 3: مولد خدمات متقدم

أنشئ مولد فئات خدمية شامل:

  1. قم ببناء خريطة تكوين مع مقياس التباعد والألوان ونقاط التوقف وأحجام الخطوط
  2. استخدم حلقات @each متداخلة لإنشاء خدمات margin/padding لجميع الاتجاهات
  3. أنشئ خدمات لون النص ولون الخلفية ولون الحدود
  4. أنشئ متغيرات استجابية لجميع الخدمات باستخدام طبقة حلقة أخرى
  5. أضف متغيرات فئة زائفة (:hover، :focus) لخدمات الألوان
  6. احسب وسجل العدد الإجمالي للفئات المُنشأة
  7. إضافي: أضف حلقة @while لإنشاء مقياس تباعد أسي (4px، 8px، 16px، 32px، إلخ.)
ملاحظة: الحلقات قوية للغاية لكن يمكنها بسهولة إنشاء آلاف من فئات CSS. في الإنتاج، ضع في اعتبارك استخدام أداة تنقية CSS (مثل PurgeCSS) لإزالة الفئات غير المستخدمة، أو إنشاء الخدمات التي تحتاجها فعلاً فقط. الهدف هو كتابة كود أقل مع الحفاظ على المرونة، وليس تضخيم حجم ملف CSS الخاص بك.