معالج SASS/SCSS

الدوال المخصصة مع @function

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

إنشاء دوال مخصصة في SASS

بينما توفر SASS مكتبة غنية من الدوال المدمجة، تأتي القوة الحقيقية من القدرة على إنشاء دوالك المخصصة باستخدام توجيه @function. تتيح لك الدوال المخصصة تغليف المنطق المعقد وإجراء الحسابات وإرجاع قيم قابلة لإعادة الاستخدام يمكن استخدامها في جميع أنحاء أوراق الأنماط الخاصة بك.

صيغة @function و @return

يتم تعريف دالة SASS باستخدام توجيه @function، متبوعاً باسم ومعاملات بين قوسين وجسم يحتوي على المنطق. يجب أن تستخدم كل دالة توجيه @return لإرجاع قيمة.

الصيغة الأساسية للدالة

// دالة بسيطة تضاعف رقماً
@function double($number) {
  @return $number * 2;
}

// الاستخدام
.element {
  width: double(50px);
  // النتيجة: width: 100px;
}

.another {
  font-size: double(8px);
  // النتيجة: font-size: 16px;
}

// دالة مع معاملات متعددة
@function calculate-percentage($value, $total) {
  @return ($value / $total) * 100%;
}

.progress-bar {
  width: calculate-percentage(75, 100);
  // النتيجة: width: 75%;
}

.completion {
  width: calculate-percentage(3, 5);
  // النتيجة: width: 60%;
}
ملاحظة: يجب أن تكون أسماء الدوال وصفية وتستخدم kebab-case (أحرف صغيرة مع واصلات) حسب الاتفاقية. على عكس الـ mixins، لا تخرج الدوال CSS مباشرة - إنها تعيد فقط قيماً يمكنك استخدامها في خصائص CSS الخاصة بك.

الفرق بين الدوال والـ Mixins

فهم متى تستخدم الدوال مقابل الـ mixins أمر حاسم لكتابة كود SASS نظيف. الفرق الرئيسي هو أن الدوال تعيد قيماً بينما الـ mixins تخرج كتل CSS.

الدوال تعيد قيماً

// الدالة تعيد قيمة محسوبة
@function calculate-rem($px-value) {
  @return ($px-value / 16px) * 1rem;
}

// استخدم القيمة المُرجعة في الخصائص
.heading {
  font-size: calculate-rem(24px);
  // النتيجة: font-size: 1.5rem;

  margin-bottom: calculate-rem(16px);
  // النتيجة: margin-bottom: 1rem;
}

// يمكنك استخدام نتائج الدالة في الحسابات
.container {
  padding: calculate-rem(20px) + 0.5rem;
  // النتيجة: padding: 1.75rem;
}

الـ Mixins تخرج CSS

// الـ mixin يخرج قواعد CSS
@mixin heading-style {
  font-size: 24px;
  font-weight: 700;
  margin-bottom: 16px;
  color: #333;
}

// تضمين mixin يخرج كل CSS الخاص به
.heading {
  @include heading-style;
  // النتيجة:
  // .heading {
  //   font-size: 24px;
  //   font-weight: 700;
  //   margin-bottom: 16px;
  //   color: #333;
  // }
}

// لا يمكنك استخدام mixin كقيمة
.bad-example {
  font-size: @include heading-style; // خطأ!
}

متى تستخدم كل منهما

  • استخدم الدوال عندما:
    • تحتاج إلى حساب وإرجاع قيمة واحدة
    • تقوم بإجراء تحويلات الوحدات (px إلى rem، deg إلى rad)
    • تحسب الألوان أو الأحجام أو قيم CSS أخرى
    • تحتاج إلى استخدام النتيجة في خصائص أو حسابات متعددة
  • استخدم الـ Mixins عندما:
    • تحتاج إلى إخراج خصائص CSS متعددة
    • تقوم بإنشاء أنماط قابلة لإعادة الاستخدام
    • تحتاج إلى إنشاء كتل CSS كاملة مع محددات متداخلة
    • تعمل مع استعلامات الوسائط أو keyframes
نصيحة: قاعدة جيدة: إذا وجدت نفسك تريد استخدام @return في mixin، فمن المحتمل أنك تحتاج إلى دالة بدلاً من ذلك. إذا كنت تريد إخراج قواعد CSS، استخدم mixin.

الدوال مع الوسائط والقيم الافتراضية

يمكن أن تقبل الدوال وسائط متعددة وتوفر قيماً افتراضية للمعاملات الاختيارية، مما يجعلها مرنة وقابلة لإعادة الاستخدام.

قيم المعاملات الافتراضية

// دالة مع قيم افتراضية
@function create-spacing($multiplier: 1, $base: 8px) {
  @return $multiplier * $base;
}

// استخدام القيم الافتراضية
.compact {
  margin: create-spacing();
  // النتيجة: margin: 8px; (1 * 8px)
}

// تجاوز المعامل الأول
.spacious {
  margin: create-spacing(3);
  // النتيجة: margin: 24px; (3 * 8px)
}

// تجاوز كلا المعاملين
.custom {
  margin: create-spacing(2, 10px);
  // النتيجة: margin: 20px; (2 * 10px)
}

// معاملات مسماة للوضوح
.explicit {
  margin: create-spacing($multiplier: 4, $base: 5px);
  // النتيجة: margin: 20px;
}

// تخطي المعامل الأول، توفير الثاني
.different-base {
  margin: create-spacing($base: 12px);
  // النتيجة: margin: 12px; (1 * 12px - يستخدم المضاعف الافتراضي)
}

معاملات متعددة مع القيم الافتراضية

// دالة تباعد شاملة
@function spacing($size: md, $direction: all) {
  $sizes: (
    xs: 4px,
    sm: 8px,
    md: 16px,
    lg: 24px,
    xl: 32px
  );

  $base-value: map-get($sizes, $size);

  @if $direction == all {
    @return $base-value;
  } @else if $direction == vertical {
    @return $base-value 0;
  } @else if $direction == horizontal {
    @return 0 $base-value;
  } @else {
    @return $base-value;
  }
}

// استخدامات متنوعة
.box-1 {
  padding: spacing();
  // النتيجة: padding: 16px;
}

.box-2 {
  padding: spacing(lg);
  // النتيجة: padding: 24px;
}

.box-3 {
  padding: spacing(md, vertical);
  // النتيجة: padding: 16px 0;
}

.box-4 {
  padding: spacing($size: xl, $direction: horizontal);
  // النتيجة: padding: 0 32px;
}

بناء دوال خدمية: تحويلات الوحدات

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

دالة px-to-rem()

محول بكسل إلى REM

// حجم الخط الأساسي العام
$base-font-size: 16px;

@function px-to-rem($px-value) {
  // إزالة الوحدة إذا كانت موجودة
  @if unitless($px-value) {
    $px-value: $px-value * 1px;
  }

  // حساب قيمة rem
  @return ($px-value / $base-font-size) * 1rem;
}

// اسم مستعار مختصر
@function rem($px-value) {
  @return px-to-rem($px-value);
}

// الاستخدام في جميع أنحاء ورقة الأنماط
body {
  font-size: rem(16px);
  // النتيجة: font-size: 1rem;
}

h1 {
  font-size: rem(32);
  // النتيجة: font-size: 2rem;
  margin-bottom: rem(24);
  // النتيجة: margin-bottom: 1.5rem;
}

.card {
  padding: rem(20) rem(30);
  // النتيجة: padding: 1.25rem 1.875rem;
  border-radius: rem(8);
  // النتيجة: border-radius: 0.5rem;
}

// متقدم: التعامل مع قيم متعددة
@function rem-multiple($values...) {
  $result: ();

  @each $value in $values {
    $result: append($result, rem($value));
  }

  @return $result;
}

.complex {
  padding: rem-multiple(10px, 20px, 15px, 20px);
  // النتيجة: padding: 0.625rem 1.25rem 0.9375rem 1.25rem;
}

دالة em()

محول بكسل إلى EM (حساس للسياق)

// تحويل px إلى em بناءً على سياق الأب
@function em($px-value, $context: 16px) {
  @if unitless($px-value) {
    $px-value: $px-value * 1px;
  }

  @if unitless($context) {
    $context: $context * 1px;
  }

  @return ($px-value / $context) * 1em;
}

// الاستخدام مع سياقات مختلفة
.parent {
  font-size: 20px;

  .child {
    font-size: em(16px, 20px);
    // النتيجة: font-size: 0.8em; (نسبة إلى الأب 20px)
    padding: em(10px, 20px);
    // النتيجة: padding: 0.5em;
  }
}

.standard {
  font-size: em(18px);
  // النتيجة: font-size: 1.125em; (نسبة إلى 16px الافتراضي)
}

دالة المقياس النمطي

إنشاء مقياس الطباعة

// مقياس نمطي للطباعة
$base-size: 16px;
$ratio: 1.25; // مقياس الثلث الرئيسي

@function modular-scale($step) {
  @return $base-size * pow($ratio, $step);
}

// دالة pow مخصصة لإصدارات SASS الأقدم
@function pow($base, $exponent) {
  $result: 1;

  @if $exponent > 0 {
    @for $i from 1 through $exponent {
      $result: $result * $base;
    }
  } @else if $exponent < 0 {
    @for $i from $exponent through -1 {
      $result: $result / $base;
    }
  }

  @return $result;
}

// توليد مقياس الطباعة
h1 {
  font-size: modular-scale(4);
  // النتيجة: 39.0625px (16 * 1.25^4)
}

h2 {
  font-size: modular-scale(3);
  // النتيجة: 31.25px (16 * 1.25^3)
}

h3 {
  font-size: modular-scale(2);
  // النتيجة: 25px (16 * 1.25^2)
}

h4 {
  font-size: modular-scale(1);
  // النتيجة: 20px (16 * 1.25^1)
}

body {
  font-size: modular-scale(0);
  // النتيجة: 16px (16 * 1.25^0)
}

small {
  font-size: modular-scale(-1);
  // النتيجة: 12.8px (16 * 1.25^-1)
}

بناء دوال مساعدة للألوان

الدوال المخصصة ممتازة لإنشاء أنظمة ألوان متسقة وخدمات الموضوعات.

دالة تباين الألوان

// اختيار لون نص متباين بناءً على الخلفية
@function contrasting-color($background-color, $light: #fff, $dark: #000) {
  // حساب الإضاءة النسبية
  @if lightness($background-color) > 50% {
    @return $dark;
  } @else {
    @return $light;
  }
}

// الاستخدام
.button-primary {
  $bg: #007bff;
  background-color: $bg;
  color: contrasting-color($bg);
  // النتيجة: color: #fff; (الخلفية الداكنة تحتاج نص فاتح)
}

.button-warning {
  $bg: #ffc107;
  background-color: $bg;
  color: contrasting-color($bg);
  // النتيجة: color: #000; (الخلفية الفاتحة تحتاج نص داكن)
}

// إصدار متقدم مع عتبة مخصصة
@function smart-contrast($bg, $threshold: 60%, $light: #fff, $dark: #000) {
  @if lightness($bg) > $threshold {
    @return $dark;
  } @else {
    @return $light;
  }
}

.subtle-button {
  $bg: #e0e0e0;
  background-color: $bg;
  color: smart-contrast($bg, $threshold: 70%);
}

دوال درجة اللون والظل

// إنشاء درجة (مزج مع الأبيض)
@function tint($color, $percentage) {
  @return mix(white, $color, $percentage);
}

// إنشاء ظل (مزج مع الأسود)
@function shade($color, $percentage) {
  @return mix(black, $color, $percentage);
}

// إنشاء نغمة (مزج مع الرمادي)
@function tone($color, $percentage) {
  @return mix(gray, $color, $percentage);
}

$brand-blue: #007bff;

.light-variant {
  background: tint($brand-blue, 20%);
  // 20% أبيض ممزوج مع الأزرق
}

.lighter-variant {
  background: tint($brand-blue, 40%);
  // 40% أبيض ممزوج مع الأزرق
}

.dark-variant {
  background: shade($brand-blue, 20%);
  // 20% أسود ممزوج مع الأزرق
}

.muted-variant {
  background: tone($brand-blue, 30%);
  // 30% رمادي ممزوج مع الأزرق
}

// توليد لوحة ألوان
$primary: #3498db;

.palette {
  &-tint-1 { background: tint($primary, 10%); }
  &-tint-2 { background: tint($primary, 30%); }
  &-tint-3 { background: tint($primary, 50%); }
  &-tint-4 { background: tint($primary, 70%); }
  &-tint-5 { background: tint($primary, 90%); }

  &-base { background: $primary; }

  &-shade-1 { background: shade($primary, 10%); }
  &-shade-2 { background: shade($primary, 30%); }
  &-shade-3 { background: shade($primary, 50%); }
  &-shade-4 { background: shade($primary, 70%); }
  &-shade-5 { background: shade($primary, 90%); }
}

بناء دوال حسابية استجابية

دالة الطباعة السائلة

// حساب حجم الخط السائل باستخدام clamp
@function fluid-size($min-size, $max-size, $min-vw: 320px, $max-vw: 1200px) {
  $slope: ($max-size - $min-size) / ($max-vw - $min-vw);
  $y-intercept: $min-size - ($slope * $min-vw);

  @return clamp(
    $min-size,
    #{$y-intercept} + #{$slope * 100}vw,
    $max-size
  );
}

// الاستخدام
h1 {
  font-size: fluid-size(24px, 48px);
  // يتدرج من 24px إلى 48px بين 320px و 1200px viewport
}

p {
  font-size: fluid-size(14px, 18px);
  // يتدرج من 14px إلى 18px
}

// حجم سائل مبسط يعتمد على clamp
@function fluid($min, $preferred, $max) {
  @return clamp($min, $preferred, $max);
}

.responsive-text {
  font-size: fluid(16px, 4vw, 32px);
}

دالة حشو نسبة العرض إلى الارتفاع

// حساب الحشو لصناديق نسبة العرض إلى الارتفاع
@function aspect-ratio-padding($width, $height) {
  @return ($height / $width) * 100%;
}

// نسب عرض إلى ارتفاع شائعة
@function ar-16-9() {
  @return aspect-ratio-padding(16, 9);
}

@function ar-4-3() {
  @return aspect-ratio-padding(4, 3);
}

@function ar-square() {
  @return aspect-ratio-padding(1, 1);
}

// الاستخدام
.video-wrapper {
  position: relative;
  padding-bottom: ar-16-9();
  // النتيجة: padding-bottom: 56.25%;

  iframe {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
  }
}

.thumbnail {
  position: relative;
  padding-bottom: ar-4-3();
  // النتيجة: padding-bottom: 75%;
}

.avatar {
  position: relative;
  padding-bottom: ar-square();
  // النتيجة: padding-bottom: 100%;
}

الدوال النقية والتأثيرات الجانبية

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

جيد: دالة نقية

// ✅ جيد: دالة نقية
@function double($value) {
  @return $value * 2;
}

// تعيد دائماً نفس المخرجات لنفس المدخلات
.element {
  width: double(50px);
  // دائماً: 100px
}

سيء: دالة مع تأثيرات جانبية

// ❌ سيء: دالة تعدل متغير عام
$global-counter: 0;

@function increment-and-return($value) {
  $global-counter: $global-counter + 1; // تأثير جانبي!
  @return $value + $global-counter;
}

// هذا لا يمكن التنبؤ به - النتيجة تعتمد على عدد مرات الاستدعاء
.element-1 {
  width: increment-and-return(10px);
  // النتيجة: 11px (العداد الآن 1)
}

.element-2 {
  width: increment-and-return(10px);
  // النتيجة: 12px (العداد الآن 2) - غير متوقع!
}

// ✅ أفضل: تمرير جميع القيم المطلوبة كمعاملات
@function calculate-with-offset($value, $offset) {
  @return $value + $offset;
}

.element-1 {
  width: calculate-with-offset(10px, 1px);
  // النتيجة: 11px - متوقع
}

.element-2 {
  width: calculate-with-offset(10px, 2px);
  // النتيجة: 12px - صريح وواضح
}
تحذير: تجنب إنشاء دوال تعدل المتغيرات العامة أو لها تأثيرات جانبية. يجب أن تكون الدوال حتمية - مع نفس المدخلات، يجب أن تعيد دائماً نفس المخرجات. هذا يجعل كودك قابلاً للتنبؤ وأسهل في التصحيح.

معالجة الأخطاء مع @error و @warn

توفر SASS توجيهات @error و @warn للمساعدة في اكتشاف الأخطاء وتوفير ملاحظات مفيدة أثناء التجميع.

استخدام @error للمشاكل الحرجة

// دالة مع فحص الأخطاء
@function safe-divide($dividend, $divisor) {
  @if $divisor == 0 {
    @error "لا يمكن القسمة على صفر! المقسوم: #{$dividend}";
  }

  @return $dividend / $divisor;
}

// هذا سيُجمّع
.valid {
  width: safe-divide(100px, 2);
  // النتيجة: width: 50px;
}

// هذا سيوقف التجميع مع رسالة خطأ
.invalid {
  width: safe-divide(100px, 0);
  // خطأ: لا يمكن القسمة على صفر! المقسوم: 100px
}

// التحقق من معاملات الدالة
@function get-color($name) {
  $colors: (
    primary: #007bff,
    secondary: #6c757d,
    success: #28a745
  );

  @if not map-has-key($colors, $name) {
    @error "اللون '#{$name}' غير موجود. الألوان المتاحة: #{map-keys($colors)}";
  }

  @return map-get($colors, $name);
}

.button {
  background: get-color(primary);
  // يعمل بشكل جيد
}

.bad-button {
  background: get-color(danger);
  // خطأ: اللون 'danger' غير موجود. الألوان المتاحة: primary, secondary, success
}

استخدام @warn للمشاكل غير الحرجة

// دالة مع تحذيرات للاستخدام المهمل
@function calculate-spacing($multiplier) {
  @if $multiplier < 0 {
    @warn "مضاعف تباعد سالب (#{$multiplier}) قد يسبب مشاكل في التخطيط.";
  }

  @if $multiplier > 10 {
    @warn "تم اكتشاف مضاعف تباعد كبير (#{$multiplier}). ضع في اعتبارك استخدام قيمة أصغر.";
  }

  @return $multiplier * 8px;
}

// هذه تُجمّع لكن تظهر تحذيرات
.element {
  margin: calculate-spacing(-1);
  // تحذير: مضاعف تباعد سالب (-1) قد يسبب مشاكل في التخطيط.
  // النتيجة: margin: -8px;
}

.spacious {
  margin: calculate-spacing(15);
  // تحذير: تم اكتشاف مضاعف تباعد كبير (15). ضع في اعتبارك استخدام قيمة أصغر.
  // النتيجة: margin: 120px;
}

// تحذيرات الإهمال
@function old-name($value) {
  @warn "old-name() مهمل. استخدم new-name() بدلاً من ذلك.";
  @return new-name($value);
}

@function new-name($value) {
  @return $value * 2;
}
نصيحة: استخدم @error للحالات التي ستسبب بالتأكيد مشاكل (مدخلات غير صالحة، بيانات مطلوبة مفقودة). استخدم @warn للحالات التي قد تسبب مشاكل أو لإشعارات الإهمال. هذا يساعد في اكتشاف الأخطاء أثناء التطوير دون أن تكون صارماً للغاية.

تمرين 1: مكتبة تحويل الوحدات

أنشئ مكتبة شاملة لتحويل الوحدات:

  1. اكتب دالة px-to-rem() مع حجم خط أساسي قابل للتكوين
  2. اكتب دالة rem-to-px() التي تحول مرة أخرى إلى بكسلات
  3. اكتب دالة px-to-em() التي تقبل معامل سياق
  4. اكتب دالة strip-unit() التي تزيل الوحدة من القيمة
  5. أضف معالجة @error للمدخلات غير الصالحة (قيم سالبة، أرقام بدون وحدات حيث تُتوقع الوحدات)
  6. أنشئ حالات اختبار باستخدام فئات CSS فعلية للتحقق من أن جميع الدوال تعمل بشكل صحيح

تمرين 2: بناء نظام الألوان

قم ببناء نظام ألوان كامل باستخدام دوال مخصصة:

  1. أنشئ دالة tint() التي تفتح لوناً عن طريق المزج مع الأبيض
  2. أنشئ دالة shade() التي تغمق لوناً عن طريق المزج مع الأسود
  3. أنشئ دالة accessible-color() التي تعيد لون نص متوافق مع WCAG لأي خلفية
  4. أنشئ دالة color-palette() التي تولد خريطة من 9 تنويعات ألوان (5 درجات، لون أساسي، 4 ظلال)
  5. استخدم هذه الدوال لإنشاء مجموعة كاملة من خدمات الألوان (.bg-primary-1 حتى .bg-primary-9)

تمرين 3: نظام حجم استجابي

أنشئ نظام حجم استجابي بدوال مخصصة:

  1. اكتب دالة fluid-size() التي تنشئ طباعة سائلة باستخدام clamp()
  2. اكتب دالة responsive-spacing() التي تحسب التباعد بناءً على عرض المنفذ
  3. اكتب دالة breakpoint-value() التي تعيد قيماً مختلفة بناءً على اسم نقطة التوقف
  4. أنشئ دالة scale() التي تولد أحجاماً بناءً على نسبة مقياس نمطي
  5. استخدم هذه الدوال لإنشاء نظام طباعة (h1-h6) وخدمات تباعد (.m-1 حتى .m-5) استجابية بالكامل
ملاحظة: الدوال المخصصة هي واحدة من أقوى الميزات في SASS. تسمح لك بإنشاء أنظمة أنماط قابلة لإعادة الاستخدام وقابلة للاختبار وقابلة للصيانة. اسعَ دائماً لجعل دوالك نقية وموثقة جيداً وتتضمن معالجة أخطاء مناسبة.