Important: this documentation is not applicable to your currently selected format stories!
amp-animation
Description
Defines and displays an animation.
Required Scripts
<script async custom-element="amp-animation" src="https://cdn.ampproject.org/v0/amp-animation-0.1.js"></script>
Supported Layouts
يحدد المكّوِن الحركات ويشغّلها.
النص البرمجي المطلوب | <script async custom-element="amp-animation" src="https://cdn.ampproject.org/v0/amp-animation-0.1.js"></script> |
التنسيقات المعتمدة | nodisplay |
أمثلة | animations.amp.html |
[جدول المحتويات]
نظرة عامة
تعتمد "حركات AMP" على واجهة برمجة تطبيقات Web Animations لتحديد الحركات وتشغيلها في مستندات AMP.
التنسيق
يحدد عنصر amp-animation
الحركة كبنية JSON.
مواصفات حركات المستوى الأعلى
يحدد كائن المستوى الأعلى عملية الحركة الكلية التي تتكون من عدد عشوائي من مكونات الحركة التي يتم تحديدها كمصفوفة animations
:
<amp-animation layout="nodisplay">
<script type="application/json">
{
// Timing properties
...
"animations": [
{
// Animation 1
},
...
{
// Animation N
}
]
}
</script>
الوضع في DOM
يُسمح بوضع المكّوِن <amp-animation>
فقط كعنصر ثانوي مباشر للعنصر <body>
إذا كانت السمة trigger="visibility"
. إذا لم يتم تحديد trigger
وتم التحكم في تشغيل الحركة برمجيًا من خلال إجراءاتها، يمكن عندها وضع المكوِّن في أي مكان في DOM.
مكوِّن الحركة
كل مكوِّن من مكونات الحركة عبارة عن تأثير إطارات رئيسية يتألف من:
- العناصر الهدف المُشَار إليها بواسطة محدد
- الشروط: الاستعلام عن الوسائط والشرط supports
- خصائص التوقيت
- الإطارات الرئيسية
{
"selector": "#target-id",
// Conditions
// Variables
// Timing properties
// Subtargets
...
"keyframes": []
}
الشروط
يمكن أن تحدد الشروط ما إذا سيتم تضمين مكون الحركة في الحركة النهائية أو لا.
الاستعلام عن الوسائط
يمكن تحديد الاستعلام عن الوسائط باستخدام الخاصية media
. يمكن أن تحتوي هذه الخاصية على أي تعبير مسموح به لواجهة برمجة تطبيقات
Window.matchMedia ويتوافق مع قاعدة @media
في CSS.
إذا تم تحديد قيمة لأحد مكونات الحركة، لن يتم تضمين المكوِّن إلا إذا تطابق الاستعلام عن الوسائط مع البيئة الحالية.
الشرط Supports
يمكن تحديد الشرط Supports باستخدام الخاصية supports
. يمكن أن تحتوي هذه الخاصية على أي تعبير مسموح به لواجهة برمجة تطبيقات
CSS.supports ويتوافق مع قاعدة @supports
في CSS.
إذا تم تحديد قيمة لأحد مكونات الحركة، لن يتم تضمين المكوِّن إلا إذا تطابق الشرط supports مع البيئة الحالية.
عبارة الحركة switch
في بعض الحالات، يكون من المريح الجمع بين عدة حركات شرطية في حركة واحدة، مع تعيين إحداها لتكون الحركة التلقائية الاختيارية. يمكن تحقيق ذلك باستخدام عبارة الحركة switch
بالتنسيق التالي:
{
{
// Optional selector, vars, timing
...
"switch": [
{
"media": "(min-width: 320px)",
"keyframes": {...},
},
{
"supports": "offset-distance: 0",
"keyframes": {...},
},
{
// Optional default: no conditionals
}
]
}
في الحركة switch
، يتم تقييم الحركات المرشحة بالترتيب المحدد، ويتم تنفيذ أول حركة تتطابق مع العبارات الشرطية وتجاهل البقية.
على سبيل المثال، تشغِّل هذه الحركة حركة motion-path إذا كانت متاحة، وترجع إلى التحويل:
{
"selector": "#target1",
"duration": "1s",
"switch": [
{
"supports": "offset-distance: 0",
"keyframes": {
"offsetDistance": [0, '300px']
}
},
{
"keyframes": {
"transform": [0, '300px']
}
}
]
}
المتغيرات
يمكن لمكوِّن الحركة أن يعلن عن متغيرات CSS التي سيتم استخدامها لقيم التوقيت والإطارات الرئيسية من خلال التعبيرات var()
. ويتم تقييم التعبيرات var()
باستخدام السياق الهدف الحالي. يتم نشر متغيرات CSS المحددة في مكونات الحركة إلى حركات مدمجة، ويتم تطبيقها على أهداف الحركة وبالتالي تُلغي CSS المستخدَمة في الحركات النهائية.
على سبيل المثال:
<amp-animation layout="nodisplay">
<script type="application/json">
{
"--delay": "0.5s",
"--x": "100px",
"animations": [
{
"selector": "#target1",
"delay": "var(--delay)",
"--x": "150px",
"keyframes": {"transform": "translate(var(--x), var(--y, 0px)"}
},
...
]
}
</script>
</amp-animation>
في هذا المثال:
- يتم نشر
--delay
في الحركات المدمجة وتستخدَم كوقت فاصل للحركة#target1
. - يتم نشر
--x
في الحركات المدمجة ولكن ألغتها الحركة#target1
وتم استخدامها لاحقًا للخاصيةtransform
. - لم يتم تحديد
--y
في أي مكان في<amp-animation>
وبالتالي سيتم الاستعلام عنه في العنصر#target1
. ويتم تلقائيًا تعيين القيمة0px
في حال عدم تحديده في CSS أيضًا.
راجع قسم var()
وcalc()
للحصول على المزيد من المعلومات عن var()
.
خصائص التوقيت
قد تحتوي مكونات حركة المستوى الأعلى ومكونات الحركة على خصائص التوقيت. يتم تعريف هذه الخصائص بالتفصيل في AnimationEffectTimingProperties من مواصفات Web Animation. وتتضمن مجموعة الخصائص المسموح بها هنا:
الخاصية | النوع | القيمة التلقائية | الوصف |
---|---|---|---|
duration | وقت | 0 | تمثل الخاصية مدة الحركة. وإما أن تكون قيمة رقمية بالملي ثانية أو قيمة وقت CSS، مثل `2s`. |
delay | وقت | 0 | تمثل الوقت الفاصل قبل بدء تنفيذ الحركة. وإما أن تكون قيمة رقمية بالملي ثانية أو قيمة وقت CSS، مثل `2s`. |
endDelay | وقت | 0 | تمثل الوقت الفاصل بعد اكتمال الحركة وقبل اعتبارها مكتملة فعليًا. وإما أن تكون قيمة رقمية بالملي ثانية أو قيمة وقت CSS، مثل `2s`. |
iterations | رقم أو "Infinity" أو "infinite" | 1 | عدد مرات تكرار تأثير الحركة. |
iterationStart | رقم/CSS | 0 | معادلة الوقت التي يبدأ فيها تأثير الحركة. |
easing | سلسلة | "linear" | دالة التوقيت التي تستخدَم لقياس الوقت لتغيير سرعة التأثيرات. |
direction | سلسلة | "normal" | إحدى القيم "normal" أو "reverse" أو "alternate" أو "alternate-reverse". |
fill | سلسلة | "none" | إحدى القيم "none" أو "forwards" أو "backwards" أو "both" أو "auto". |
تسمح كل خصائص التوقيت إما بقيم رقمية/سلسلة مباشرة أو قيم CSS. يمكن مثلاً تحديد "duration" بالقيمة 1000
أو 1s
أو 1000ms
. بالإضافة إلى ذلك، يُسمح أيضًا باستخدام calc()
وvar()
وتعبيرات CSS الأخرى.
مثال لخصائص التوقيت بالترميز JSON:
{
...
"duration": "1s",
"delay": 100,
"endDelay": "var(--end-delay, 10ms)",
"easing": "ease-in",
"fill": "both"
...
}
تكتسب مكونات الحركة خصائص التوقيت المحددة لحركة المستوى الأعلى.
الأهداف الفرعية
متى توفرت إمكانية تحديد selector
، فمن الممكن أيضًا تحديد subtargets: []
. يمكن أن تلغي الأهداف الفرعية خصائص التوقيت أو المتغيرات المحددة في الحركة لأهداف فرعية معينة وموضّحة إما من خلال فهرس أو محدد CSS.
على سبيل المثال:
{
"selector": ".target",
"delay": 100,
"--y": "100px",
"subtargets": [
{
"index": 0,
"delay": 200,
},
{
"selector": ":nth-child(2n+1)",
"--y": "200px"
}
]
}
في هذا المثال، تم تلقائيًا تعيين الوقت الفاصل لجميع الأهداف المطابقة لـ ".target" بقيمة 100ms ولـ "- y" بقيمة 100px. ومع ذلك، يتم إلغاء الهدف الأول (index: 0
) ليكون الوقت الفاصل 200ms بينما تم إلغاء الأهداف الفردية لتكون "--y" بقيمة 200px.
لاحظ إمكانية تطابق عدة أهداف فرعية مع عنصر هدف واحد.
الإطارات الرئيسية
يمكن تحديد الإطارات الرئيسية بعدة طرق موضحة في قسم الإطارات الرئيسية من مواصفات Web Animations أو كسلسلة تشير إلى اسم @keyframes
في CSS.
في ما يلي بعض الأمثلة المعتادة لتعريفات الإطارات الرئيسية.
يحدد تنسيق "to" لشكل الكائن المختزَل الحالة النهائية على 100%:
{
"keyframes": {"opacity": 0, "transform": "scale(2)"}
}
يحدد التنسيق "from-to" لشكل الكائن المختزَل حالتي البداية والنهاية على 0 و100%:
{
"keyframes": {
"opacity": [1, 0],
"transform": ["scale(1)", "scale(2)"]
}
}
يحدد التنسيق "value-array" لشكل الكائن المختزَل عدة قيم لحالتي البداية والنهاية وعدة معادلات (متساوية التباعد):
{
"keyframes": {
"opacity": [1, 0.1, 0],
"transform": ["scale(1)", "scale(1.1)", "scale(2)"]
}
}
يحدد شكل المصفوفة الإطارات الرئيسية. ويتم تعيين المعادلات تلقائيًا على 0 و100% مع التوزيع بين القيمتين بتباعد متساوٍ:
{
"keyframes": [
{"opacity": 1, "transform": "scale(1)"},
{"opacity": 0, "transform": "scale(2)"}
]
}
يمكن أن يتضمن شكل المصفوفة أيضًا "المعادلة" بشكل صريح:
{
"keyframes": [
{"opacity": 1, "transform": "scale(1)"},
{"offset": 0.1, "opacity": 0.1, "transform": "scale(2)"},
{"opacity": 0, "transform": "scale(3)"}
]
}
يمكن أيضًا أن يتضمن شكل المصفوفة "تغيير السرعة":
{
"keyframes": [
{"easing": "ease-out", "opacity": 1, "transform": "scale(1)"},
{"opacity": 0, "transform": "scale(2)"}
]
}
للحصول على مزيد من التنسيقات الإضافية للإطارات الرئيسية، راجع مواصفات Web Animations.
تسمح قيم الخاصية بأي قيم CSS صالحة، بما في ذلك calc()
وvar()
وغيرها من تعبيرات CSS.
الإطارات الرئيسية من CSS
توجد طريقة أخرى لتحديد الإطارات الرئيسية كقاعدة في ورقة أنماط المستند (العلامة <style>
) كقاعدة @keyframes
في CSS. على سبيل المثال:
<style amp-custom>
@keyframes keyframes1 {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
</style>
<amp-animation layout="nodisplay">
<script type="application/json">
{
"duration": "1s",
"keyframes": "keyframes1"
}
</script>
</amp-animation>
إطارات CSS @keyframes
مساوية تقريبًا لتضمين تعريف الإطارات الرئيسية في JSON وفقًا لمواصفات Web Animations. ومع ذلك، هناك بعض الفروق
- للحصول على الدعم على مستوى المنصة، قد يلزم توفير بادئات الموردين، مثل
@-ms-keyframes {}
، أو قد يلزم توفير-moz-transform
. لا حاجة إلى بادئات الموردين ولا يُسمح بها في التنسيق JSON، ولكنها قد تكون ضرورية في CSS. - لن تتمكن المنصات التي لا تتيح
calc()
وvar()
من الاستفادة من تعويضاتamp-animation
عند تحديد الإطارات الرئيسية في CSS. ننصح دائمًا بتضمين قيم احتياطية في CSS. - يتعذر استخدام إضافات CSS، مثل
width()
وheight()
وnum()
وrand()
وindex()
وlength()
في CSS.
الخصائص المدرجة في القائمة البيضاء للإطارات الرئيسية
لا يمكن استخدام جميع خصائص CSS في الإطارات الرئيسية. وحدها خصائص CSS التي يمكن للمتصفحات الحديثة تحسينها وتحريكها بسرعة مدرَجة في القائمة البيضاء. وستزيد هذه القائمة كلما أكدت خصائص إضافية أنها توفر أداء جيدًا. تحتوي القائمة الحالية على:
- opacity
- transform
- visibility
- offset-distance
لاحظ أن استخدام خصائص CSS المبدوءة بالموردين ليست ضرورية أو مسموحًا بها.
الأشكال المختصرة لتهيئة الحركات
إذا تضمنت الحركة عنصرًا واحدًا وكان تأثير واحد للإطارات الرئيسية كافيًا، يمكن اختصار التهيئة على مكّوِن الحركة هذا فقط. على سبيل المثال:
<amp-animation layout="nodisplay">
<script type="application/json">
{
"selector": "#target-id",
"duration": "1s",
"keyframes": {"opacity": 1}
}
</script>
</amp-animation>
إذا كانت الحركة تتألف من قائمة مكونات، ولكن لا تحتوي على حركة المستوى الأعلى، يمكن اختصار التهيئة إلى مصفوفة من المكونات. على سبيل المثال:
<amp-animation layout="nodisplay">
<script type="application/json">
[
{
"selector": ".target-class",
"duration": 1000,
"keyframes": {"opacity": 1}
},
{
"selector": ".target-class",
"duration": 600,
"delay": 400,
"keyframes": {"transform": "scale(2)"}
}
]
</script>
</amp-animation>
إنشاء الحركة
يمكن أن تشير الحركات إلى حركات أخرى، وبالتالي فهي تجمع بين عدة إعلانات للمكّوِن amp-animation
في حركة نهائية واحدة. إن الإشارة إلى حركة من حركة أخرى تساوي تقريبًا عملية الدمج. يكمن سبب تقسيم الحركة إلى عناصر مختلفة في إعادة استخدام الحركة نفسها من عدة أماكن أو تصغير إعلان كل حركة وزيادة إمكانية إدارتها.
على سبيل المثال:
<amp-animation id="anim1" layout="nodisplay">
<script type="application/json">
{
"animation": "anim2",
"duration": 1000,
"--scale": 2
}
</script>
</amp-animation>
<amp-animation id="anim2" layout="nodisplay">
<script type="application/json">
{
"selector": ".target-class",
"keyframes": {"transform": "scale(var(--scale))"}
}
</script>
</amp-animation>
تضم الحركة التي في المثال الحركة "anim2" كجزء من "anim1". ويتم تضمين "anim2" بدون هدف (selector
). في هذه الحالة، من المتوقع أن تشير الحركة المضمنَّة إلى هدفها الخاص.
يتيح نموذج آخر تضمين الحركة لتوفير الهدف أو أهداف متعددة. في هذه الحالة، يتم تنفيذ الحركة المضمنَّة لكل هدف مطابق. على سبيل المثال:
<amp-animation id="anim1" layout="nodisplay">
<script type="application/json">
{
"selector": ".target-class",
"animation": "anim2",
"duration": 1000,
"--scale": 2
}
</script>
</amp-animation>
<amp-animation id="anim2" layout="nodisplay">
<script type="application/json">
{
"keyframes": {"transform": "scale(var(--scale))"}
}
</script>
</amp-animation>
هنا، عندما يتطابق ".target-class" مع عنصر واحد أو عدة عناصر أو لا يحدث تطابق، يتم تنفيذ "anim2" لكل هدف مطابق.
يتم تمرير المتغيرات وخصائص التوقيت المحددة في حركة الطلب إلى الحركة المضمنَّة أيضًا.
تعبيرات var()
وcalc()
يتيح amp-animation
استخدام تعبيرات var()
وcalc()
لقيم التوقيت والإطارات الرئيسية.
على سبيل المثال:
<amp-animation layout="nodisplay">
<script type="application/json">
[
{
"selector": ".target-class",
"duration": "4s",
"delay": "var(--delay)",
"--y": "var(--other-y, 100px)",
"keyframes": {"transform": "translate(calc(100vh + 20px), var(--y))"}
}
]
</script>
</amp-animation>
يتم تعويض كل من var()
وcalc()
في المنصات التي لا تتيحهما بشكل مباشر. يتم استخراج الخصائص var()
من العناصر الهدف المقابلة. ومع ذلك، يتعذر للأسف تعويض var()
بشكل كامل. وبالتالي، عندما يكون التوافق مهمًا، ننصح بشدة بتضمين القيم التلقائية في تعبيرات var()
. على سبيل المثال:
<amp-animation layout="nodisplay">
<script type="application/json">
[
{
"selector": ".target-class",
"duration": "4s",
"delay": "var(--delay, 100ms)",
}
]
</script>
</amp-animation>
يمكن لمكونات الحركة تحديد متغيراتها الخاصة، مثل حقول --var-name
. يتم نشر هذه المتغيرات في الحركات المدمجة وتلغي متغيرات العناصر الهدف المحددة من خلال ورقة الأنماط (العلامة <style>
). تحاول تعبيرات var()
أولاً حل قيم المتغيرات المحددة في الحركات ثم تستعلم عن أنماط الهدف.
إضافات CSS
يوفر amp-animation
إضافات CSS متعددة لتلبية احتياجات الحركات المعتادة: rand()
وnum()
وwidth()
وheight()
. يمكن استخدام هذه الدوال متى أمكن استخدام قيم CSS ضمن amp-animation
، بما في ذلك قيم التوقيت والإطارات الرئيسية.
الإضافة index()
من CSS
تعرض الدالة index()
فهرس العنصر الهدف الحالي في تأثير الحركة. تكون الإضافة أكثر ملاءمة عندما يتم تحريك أهداف متعددة بنفس التأثير باستخدام الخاصية selector
. سيكون لأول هدف يتطابق مع المحدد الفهرس 0
، وسيكون فهرس الثاني 1
وهكذا.
بالإضافة إلى الميزات الأخرى، يمكن ضم هذه الخاصية مع تعبيرات calc()
واستخدامها لإنشاء تأثير مرحلي. على سبيل المثال:
{
"selector": ".class-x",
"delay": "calc(200ms * index())"
}
الإضافة length()
من CSS
تعرض الدالة length()
عدد العناصر الهدف في تأثير الحركة. وتكون هذه الإضافة أكثر صلة عند اقترانها بالإضافة index()
:
{
"selector": ".class-x",
"delay": "calc(200ms * (length() - index()))"
}
الإضافة rand()
من CSS
تعرض الدالة rand()
قيمة CSS عشوائية ولها شكلان.
شكل بدون وسيطات يعرض الرقم العشوائي من 0 و1.
{
delay: "calc(10s * rand())"
}
الشكل الثاني يحتوي على وسيطتين ويعرض القيمة العشوائية بين هاتين الوسيطتين.
{
"delay": "rand(5s, 10s)"
}
الإضافات width()
وheight()
من CSS
تعرض إضافتَا width()
وheight()
عرض/ارتفاع العنصر المتحرك أو العنصر المعيَّن بالمحدد. تكون القيمة المعروضة بالبكسل، مثل 100px
.
الأشكال التالية متاحة:
- width()
وheight()
- عرض/ارتفاع العنصر المتحرك.
- width('.selector')
وheight('.selector')
- عرض/ارتفاع العنصر المعيَّن بالمحدد. يمكن استخدام أي محدد CSS مثل width('#container > li')
.
- width(closest('.selector'))
وheight(closest('.selector'))
- عرض/ارتفاع العنصر المعيَّن بأقرب محدد.
الإضافتان width()
وheight()
مفيدتان بشكل خاص للتحويلات. خصائص left
وtop
وخصائص CSS المشابهة يمكنها استخدام قيم %
للتعبير عن الحركات نسبة حجم الحاوية. ومع ذلك، تفهم الخاصية transform
قيم %
بشكل مختلف، فهي تفهمها كنسبة مئوية من العنصر المحدد. وبالتالي، يمكن استخدام width()
وheight()
للتعبير عن حركات التحويل من حيث عناصر الحاوية وما شابهها.
يمكن دمج هذه الدالات مع تعبيرات calc()
وvar()
وغيرها من تعبيرات CSS. على سبيل المثال:
{
"transform": "translateX(calc(width('#container') + 10px))"
}
الإضافة num()
من CSS
تعرض الدالة num()
تمثيلاً رقميًا لقيمة CSS. على سبيل المثال:
- num(11px)
yields 11
;
- num(110ms)
yields 110
;
- etc.
على سبيل المثال، يحسب التعبير التالي الوقت الفاصل بالثواني نسبة إلى عرض العنصر:
{
"delay": "calc(1s * num(width()) / 100)"
}
صور SVG المتحركة
صور SVG المتحركة رائعة ونحن بالتأكيد ننصح باستخدامها في الحركات.
تتم إتاحة صور SVG المتحركة عبر نفس خصائص CSS الموضحة في الخصائص المدرجة في القائمة البيضاء للإطارات الرئيسية مع بعض الفروق الدقيقة:
- عناصر IE/Edge SVG لا تتيح خصائص
transform
من CSS. يتم تعويض الحركةtransform
نفسها. ومع ذلك، لا يتم تطبيق الحالة الأولية المحددة في ورقة الأنماط. إذا كانت الحالة الأولية المحوّلة مهمة على IE/Edge، يُنصح بتكرارها عبر السمة SVGtransform
. - في حين يتم تعويض CSS
transform
لـ IE/Edge، فمن المستحيل لسوء الحظ تعويض الخاصيةtransform-origin
. وبالتالي، عندما يكون التوافق مع IE/Edge مطلوبًا، يُنصح باستخدامtransform-origin
التلقائية فقط. - تواجه معظم المتصفحات حاليًا مشاكل في فهم CSS
transform-origin
بشكل صحيح. اطّلِع على مشاكل Chrome وSafari وFirefox. ويُفترَض حل غالبية هذا الالتباس بمجرد تنفيذ CSStransform-box
. عندما تكونtransform-origin
مهمة، يُنصَح أيضًا بتضمينtransform-box
CSS المطلوبة حفاظًا على التوافق في المستقبل.
تشغيل الحركة
يمكن تشغيل الحركة عبر السمة trigger
أو الإجراء on
.
السمة trigger
القيمة visibility
هي القيمة الوحيدة المتاحة حاليًا للسمة trigger
. يتم تشغيل visibility
عندما يكون المستند أو التضمين الأساسي مرئيًا (في إطار العرض).
على سبيل المثال:
<amp-animation id="anim1" layout="nodisplay"
trigger="visibility">
...
</amp-animation>
التشغيل عبر الإجراء on
على سبيل المثال:
<amp-animation id="anim1" layout="nodisplay">
...
</amp-animation>
<button on="tap:anim1.start">Animate</button>
الإجراء on
يصدِّر العنصر amp-animation
الإجراءات التالية:
start
- لبدء الحركة التي لم تبدأ ويمكن تحديد خصائص التوقيت والمتغيرات كوسيطات للإجراء. مثال:anim1.start(delay=-100, --scale=2)
restart
- لبدء الحركة أو إعادة تشغيل حركة العاملة حاليًا ويمكن تحديد خصائص التوقيت والمتغيرات كوسيطات للإجراء. مثال:anim1.start(delay=-100, --scale=2)
pause
- لإيقاف الحركة العاملة حاليًاresume
- لاستئناف الحركة العاملة حاليًاtogglePause
- للتبديل بين إجراء الإيقاف المؤقت وإجراء الاستئنافseekTo
- لإيقاف الحركة مؤقتًا والانتقال إلى النقطة الزمنية المحددة في الوسيطةtime
بالملي ثانية أو الوسيطةpercent
كنقطة مئوية في المخطط الزمنيreverse
- لعكس الحركةfinish
- لإنهاء الحركةcancel
- لإلغاء الحركة
لقد قرأت هذا المستند عشرات المرات ولكن ألم يُجِب عن جميع أسئلتك؟ ربما حصل ذات الأمر مع أشخاص آخرين: تواصل معهم على Stack Overflow.
الذهاب إلى Stack Overflow هل وجدت خطأ أو تفتقد لميزة؟يشجع مشروع AMP مشاركتك ومساهمتك بشدة! ونأمل أن تكون مشاركًا دائمًا في مجتمعنا مفتوح المصدر، ولكننا نشجع أيضًا المساهمات التي تحدث لمرة واحدة في الأمور التي تتحمس لها.
الانتقال إلى GitHub