-
المساهمات
164 -
تاريخ الانضمام
-
تاريخ آخر زيارة
نوع المحتوى
ريادة الأعمال
البرمجة
التصميم
DevOps
التسويق والمبيعات
العمل الحر
البرامج والتطبيقات
آخر التحديثات
قصص نجاح
أسئلة وأجوبة
كتب
دورات
كل منشورات العضو ابراهيم الخضور
-
قد تبدو بعض التعابير النمطية regular expressions بسيطةً لكن قد يستغرق تنفيذها وقتًا طويلًا، وقد يسبب توقف محرك JavaScript عن الاستجابة، وسيواجه المطورون عاجلًا أم آجلًا هذا السلوك، ومن أعراضه توقف استجابة محرك تعبير نمطي يعمل جيدًا في بعض الأحيان، عندما يبحث ضمن نص معين مستهلكًا موارد المعالج 100%، حيث سيقترح المتصفح في حالة مثل هذه إيقاف تنفيذ السكربت، وإعادة تحميل الصفحة، وليس جيدًا بالطبع أن يُوقف سكربت JavaScript يعمل في الواجهة الخلفية استجابة عملية من عمليات الخادم، فلا بد إذًا من إلقاء نظرة على ذلك. مشكلة انتظار انتهاء التعبير النمطي لنفترض وجود نص نريد أن نتحقق من كونه يتألف من كلمات +w\ يفصل بينها فراغات اختيارية ?s\، حيث ستكون إحدى الطرق الواضحة إنشاء تعبير نمطي يبحث عن كلمة يليها فراغ اختياري ?w+\s\، وأخيرًا نضيف المحدد الكمي * لتكرار العملية، ويقود هذا التعبير إلى استخدام التعبير $*(?w+\s\)^ الذي يبحث عن كلمة على الأقل بالمواصفات السابقة، بحيث يبدأ البحث من بداية النص ^ وينتهي بنهايته $. let regexp = /^(\w+\s?)*$/; alert( regexp.test("A good string") ); // true ناجح alert( regexp.test("Bad characters: $@#") ); // false فاشل يبدو أنّ التعبير سيعمل والنتيجة صحيحة، لكنه في نصوص معينة سيستغرق وقتًا طويلًا حتى تتوقف استجابة محرك JavaScript، وتُستهلك موارد المعالج 100%. قد لا تلاحظ شيئًا إن نفّذت المثال التالي، لأن محرك JavaScript سيتوقف عن الاستجابة، وسيتوقف المتصفح عن التجاوب مع الأحداث، وستتوقف واجهة المستخدم عن العمل (تتيح معظم المتصفحات ميزة التمرير فقط)، وسيقترح المتصفح بعد فترة إعادة تحميل الصفحة، فكن على حذر. let regexp = /^(\w+\s?)*$/; let str = "An input string that takes a long time or even makes this regexp hang!"; // سيأخذ بعض الوقت alert( regexp.test(str) ); وعلينا القول -حتى نكون منصفين- بأن بعض محركات التعابير النمطية تتعامل مع هذا النوع من البحث بفعالية، فالمحرك "V8" وابتداءً من النسخة 8.8 قادر على ذلك، فلن تتوقف استجابة المتصفح 88 Chrome في حالات مثل هذه، بينما ستتوقف استجابة متصفح Firefox. السؤال الذي طرح نفسه، ما المشكلة؟ لماذا تتوقف استجابة التعبير النمطي؟ لتوضيح ذلك دعونا نبسّط المثال السابق بإزالة الفراغات ?S\، وبالتالي سيصبح التعبير النمطي على الشكل $*(?w+\s\)^، ولتوضيح الأمر أكثر دعونا نستبدل الصنف d\ بالصنف w\، وستتوقف مع ذلك استجابة التعبير الجديد أيضًا، فمثلًا: let regexp = /^(\d+)*$/; let str = "012345678901234567890123456789z"; // انتبه، سيأخذ بعض الوقت alert( regexp.test(str) ); ما المشكلة في هذا التعبير النمطي؟ قد يلاحظ القارئ أنّ التعبير *(+d\) غريب بعض الشيء، فوجود المحدد الكمي * يبدو مبالغًا فيه، فإن أردنا عددًا يمكن استخدام d\، ومع ذلك يبدو التعبير الجديد المبسط عمليًا أكثر، لكن سبب بطئه أيضًا لم يتغير، لهذا علينا دراسته بالتفصيل للوقوف على المشكلة، فما الذي يحدث أثناء البحث عن النمط $*(+d\)^ ضمن النص 123456789z، واختُصر قليلًا للوضوح، ولماذا يستغرق الأمر وقتًا؟ إليك ما يفعله المحرك: أولًا، يحاول المحرك بدايةً البحث عن محتوى الأقواس، وهي الأعداد +d\، وطالما أنّ + محدد كمي جشع greedy افتراضيًا فسيضم كل الأرقام في النص. \d+....... (123456789)z عند ضم الأرقام جميعها يعدُّ المحرك أن البحث عن +d\ قد أنجز، وأن النتيجة هي 123456789، ثم ينتقل بعد ذلك إلى تطبيق المحدد الكمي *، لكن الأرقام في النص قد استهلكت جميعها، فلن يقدم مرتكز البداية ^ أي شيء، ثم يبحث المحرك عن آخر محارف النمط $، ولن يجده لأنّ المحرف الباقي من النص هو z: X \d+........$ (123456789)z ثانيًا، وطالما أنّ التطابق غير موجود فسينقص المُكمِّم + عدد المحارف واحدًا ويعيد البحث، لذلك ستكون نتيجة +d\ كل الأرقام عدا الأخير 12345678: \d+....... (12345678)9z ثالثًا، يحاول المحرك الآن البحث في الموقع التالي بعد 12345678، وعندها يمكن تطبيق المكمِّم *، وسيعطي النمط *(+d\) تطابقًا جديدًا وهو 9. \d+.......\d+ (12345678)(9)z ثم يحاول المحرك من جديد إيجاد آخر محرف من النمط $ فلن يجده، بل سيجد المحرف الباقي من النص، وهو z: X \d+.......\d+ (12345678)(9)z رابعًا، لن يحصل المحرك على التطابق المطلوب؛ وسيستمر في العودة والتعقب مخفضًا عدد التكرارات وهذا ما يسمى بعملية التعقب التراجعي Backtracking أو التراجع والمطابقة ببساطة، وتجري عملية التعقب التراجعي عادةً بالشكل التالي: يقلل آخر محدد كمي جشع عدد التكرارات حتى يصل إلى الحد الأدنى، ثم يأتي دور المحدد الكمي الجشع الذي يسبقه في إنقاص عدد التكرارات وهكذا، إلى أن يتقصى المحرك كل الحالات الممكنة، وإليك بعض الأمثلة عن هذه الحالات: العدد الأول مؤلف من 7 أرقام، ثم عدد برقمين: X \d+......\d+ (1234567)(89)z العدد الأول من 7 أرقام، ثم عددين كل منهما مكون من رقم واحد: X \d+......\d+\d+ (1234567)(8)(9)z العدد الأول من 6 أرقام، والثاني من ثلاثة: X \d+.......\d+ (123456)(789)z العدد الأول من 6 أرقام، يليه عددان آخران: X \d+.....\d+ \d+ (123456)(78)(9)z ويوجد عدد كبير من الاحتمالات التي نفصل فيها سلسلةً من الأرقام 123456789 إلى أعداد، ولنكون أكثر دقة توجد 2<sup>n</sup>-1 طريقة، حيث n هو طول سلسلة الأرقام، ففي حالة 9 أرقام -كما في حالتنا- لدينا 511 احتمال، أما في حالة 20 رقمًا فلدينا 1048575 احتمال، وبالتالي سيسبب مرور المحرك بهذه الحالات التأخير. العودة إلى الكلمات والنصوص يحدث الأمر ذاته كما في مثالنا الأول، عندما بحثنا عن كلمات باستخدام النمط $*(?w+\s\)^ ضمن النص التالي: An input that hangs! والسبب طبعًا أن الكلمة +w\ قد تُمثَّل بعدد كبير من الحالات: (input) (inpu)(t) (inp)(u)(t) (in)(p)(ut) ... قد يكون عدم وجود التطابق واضحًا، لأن النص ينتهي بإشارة تعجب، لكن ما يتوقعه التعبير النمطي هو محرف كلمة w\ أو فراغ s\ في النهاية، وهذا ما لا يعرفه المحرك، إذ سيبحث عن كل الحالات التي يحتمل أن تطابق فيها النمط *(?w+\s\) كل محارف النص، بما في ذلك الحالات التي تضم الفراغ *(w+\s\) أو التي لا تضمها *(+w\)، لأن النمط ?s\ اختياري، وسيستغرق وقتًا طويلًا نظرًا لوجود عدد كبير من الحالات التي سيستكشفها المحرك، فما العمل؟ هل علينا تفعيل البحث الكسول lazy mode؟ لن يساعدنا ذلك لسوء الحظ، ستتوقف الاستجابة أيضًا إذا استبدلنا النمط ?+w\ بالنمط +w\، وسيتغير ترتيب الحالات التي سيبحث فيها المحرك فقط، وليس عددها. تتجنب بعض محركات التعبير النمطي المرور على كل الحالات من خلال بعض الاختبارات، أو استخدام وسائل أتمتة محدودة، أو قد تجعل العملية أكثر سرعةً، ومع ذلك لا تتبع معظم المتصفحات هذه الأساليب، كما أنها لا تساعد دومًا. ما هو الحل؟ توجد مقاربتان لحل المشكلة، الأولى تخفيض عدد الحالات الممكنة، فمثلًا لنجعل المساحة الفارغة إجباريةً، بجعل النمط بالشكل التالي $*w+\s)*\w\)^، أي سنبحث عن أي عدد من الكلمات التي يفصل بينها فراغ، عدا الكلمة الأخيرة فستكون اختيارية w\*، سينتهي البحث سواء وجدت أم لا، انظر إلى التعبير التالي المكافئ للسابق (يحصل على التطابقات نفسها) ويعمل جيدًا: let regexp = /^(\w+\s)*\w*$/; let str = "An input string that takes a long time or even makes this regex hang!"; alert( regexp.test(str) ); // false لماذا اختفت المشكلة؟ لأن الفراغ بين الكلمات أصبح إجباريًا، فلو حذفنا الفراغ في التعبير السابق فسيقود إلى عدد أكبر من حالات +w\ ضمن الكلمة ذاتها، إذ يمكن الحصول على الكلمة input من تكرارين +w\ بالشكل التالي: \w+ \w+ (inp)(ut) لكن النمط الجديد مختلف، فالكلمة متبوعة بفراغ حتمًا *(w+\s\)، وبالتالي لن نحصل على الكلمة من خلال تكرارين للنمط w+\s\، وبهذا لن يهدر المزيد من الوقت في البحث عن كل الحالات الممكنة للحصول على كلمة. منع التعقب التراجعي في التعابير النمطية لن تساعدنا إعادة كتابة النمط دائمًا، إذ كانت العملية سهلةً وواضحةً في المثال السابق، لكنها عادةً ليست كذلك، كما ستقود إعادة كتابة النمط إلى أنماط أكثر تعقيدًا، وهذا أمر سيء، فالتعابير النمطية معقدة بطبيعتها، لحسن الحظ توجد مقاربة بديلة تقتضي منع التعقب التراجعي backtracking للمحدد الكمي، فأصل المشكلة هو تجربة المحرًك للكثير من الحالات الخاطئة -من وجهة نظرنا طبعًا-، فمن الواضح أنّ تعقب + في النمط $*(+d\) سيسبب مشكلةً، ولن يتغير شيء إن بدّلنا النمط +d+\d\ بالنمط +d\: \d+........ (123456789)! \d+...\d+.... (1234)(56789)! وقد نرغب في مثالنا الأصلي $*(?w+\s\)^ بمنع تعقب +w\، لأنها من المفترض أن تبحث عن كلمة كاملة بأكبر طول ممكن، ولا حاجة لتخفيض عدد التكرارات، أو فصلها إلى كلمتين +w+\w\ وهكذا. تدعم محركات التعابير النمطية الحديثة المحددات الكمية الاستحواذية possessive quantifiers عن طريق إضافة الإشارة + بعد المحدد الكمي، أي نضع ++d\ بدلًا من +d\، وذلك لمنعه من الوقوع في فخ التعقب التراجعي، فالمحددات الكمية الاستحواذية أبسط من النظامية، حيث تطابق ما تستطيع من المحارف دون الوقوع في التعقب التراجعي، وسيكون البحث آنذاك أبسط. كما يوجد ما يُسمى "المجموعات الذرية الملتقطة" atomic capturing groups، وهو وسيلة لمنع التعقب التراجعي ضمن الأقواس، والخبر السيئ هو أنها غير مدعومة في JavaScript، لكن يمكن تقليدها باستخدام شرط التحقق مما يلي المطابقة lookahead transform. البحث عن الخلاص لقد وصلنا إلى موضوع متقدم فعلًا، إذ نريد منع المحددات الكمية -مثل +- من التعقب التراجعي، لأن تعقب بعض الأمور غير منطقي على الإطلاق. إنّ النمط الذي يأخذ أكبر عدد ممكن من تكرارات w\ دون تعقب تراجعي هو 1\((+w\)=?)، وبالطبع يمكن اختيار أي نمط بدل w\، وقد يبدو النمط غريبًا، لكنه في الواقع تحويل بسيط، لنصفه: سيبحث نمط البحث قُدُمًا =? عن أطول كلمة +w\ ابتداءً من الموقع الحالي. لن يتذكر المحرك محتوى ما بين القوسين المسبوق بالمحارف =?، لذلك وضعنا +w\ ضمن أقواس، ثم سيتذكر المحرك محتوى القوسين التاليين. ثم نشير إلى الأقواس الخارجية بالرقم 1. سيتقدم البحث إلى الأمام وعند وجود كلمة +w\ فسيحددها بالرقم 1\، وبكذا سنكون قد صممنا محددًا كميًا استحواذيًا من المحدد الكمي +، حيث يلتقط الكلمة +w\ كاملةً فقط، وليس جزءًا منها، فيمكن مثلًا الحصول على الكلمة Java من الكلمة JavaScript، وترك الكلمة Script لتتطابق مع بقية النمط، وإليك موازنةً بين نمطين: alert( "JavaScript".match(/\w+Script/)); // JavaScript alert( "JavaScript".match(/(?=(\w+))\1Script/)); // null في الحالة الأولى: سنحصل على الكلمة كاملةً، لكن المحدد الكمي سيتعقب بقية النمط متراجعًا محرفًا محرفًا، محاولًا إيجاد بقية النمط، ثم سينجح أخيرًا، عندما يتطابق النمط +w\ الكلمة Java. في الحالة الثانية: سيجري البحث قُدمًا وسيجد الكلمة JavaScript كاملةً، وسيحددها بالرقم 1، وبالتالي لا طريقة بعد ذلك لإيجاد الكلمة Script. يمكن استخدام تعابير نمطية أكثر تعقيدًا من w\ ضمن 1\((+w\)=?) عندما نريد منع التعقب التراجعي للمحدد الكمي +. لنكتب مثالنا الأول باستخدام التحقق مما يلي التطابق لمنع التعقب التراجعي: let regexp = /^((?=(\w+))\2\s?)*$/; alert( regexp.test("A good string") ); // true let str = "An input string that takes a long time or even makes this regex hang!"; alert( regexp.test(str) ); // false, يعمل وبسرعة وضعنا 2\ بدلًا من الرقم 1\ لوجود أقواس خارجية إضافية، كما يمكننا تسمية الأقواس أيضًا (+<word>\w>?). // ?<word>وتُسمى الأقواس كالتالي, \k<word>يشار إلى الأقواس كالتالي let regexp = /^((?=(?<word>\w+))\k<word>\s?)*$/; let str = "An input string that takes a long time or even makes this regex hang!"; alert( regexp.test(str) ); // false alert( regexp.test("A correct string") ); // true خلاصة تُسمى المشكلة التي وصفناها في هذا المقال بالتعقب التراجعي الكارثي، وغطينا طريقتين لحلها: تخفيض عدد الحالات الممكنة التي تتطابق مع نمط إلى الحد الأدنى. منع التعقب التراجعي. ترجمة -وبتصرف- للفصل Catastrophic backtracking من سلسلة The Modern JavaScript Tutorial. اقرأ أيضًا أساسيات البحث باستخدام التعابير النمطية في جافاسكربت المجموعات والمجالات في التعابير النمطية التعابير النمطية (regexp/PCRE) في PHP
-
يمكن أن يشير المصطلح "خادم ويب Web Server" إلى العتاد الصلب أو البرمجيات، أو كلاهما معًا. من ناحية العتاد الصلب: يُعد خادم الويب حاسوبًا يُخزّن البرمجيات اللازمة لإدارة خادم الويب كما يخزّن الملفات التي تتكون منها مواقع الويب (مثل ملفات HTML وCSS وملفات جافاسكربت). يتصل خادم الويب بالإنترنت ويدعم التبادل الفيزيائي للبيانات مع الأجهزة الأخرى المتصلة بالإنترنت. من ناحية البرمجيات: يتألف خادم الويب من عدة أقسام تتحكم بكيفية دخول مستخدمي ويب إلى الملفات التي يستضيفها. يتكون خادم ويب على الأقل من خادم HTTP ويضم برمجيات قادرة على فهم عناوين ويب URL وبرتوكول HTTP (البروتوكول الذي يستخدمه المتصفح لعرض محتوى صفحات الويب). يمكن الولوج إلى خادم HTTP من خلال أسماء النطاقات الخاصة بمواقع الويب التي يُخزّنها ويعيد محتوى هذه المواقع التي يستضيفها إلى جهاز المستخدم النهائي عندما يطلبها. ولو أردنا التكلم ببساطة شديدة نقول: عندما يحتاج المتصفح إلى ملف يستضيفه خادم ويب، يطلب المتصفح هذا الملف عبر بروتوكول HTTP، وعندما يصل الطلب إلى خادم الويب الصحيح (خادم الويب كعتاد صلب)، يقبل خادم HTTP (الناحية البرمجية من خادم ويب) الطلب ويبحث عن الملف المطلوب، ويعيده إلى متصفح الخاص بالمستخدم عبر بروتوكول HTTP أيضًا. إن لم يعثر الخادم على الملف فسيستجيبُ برسالة خطأ ذات الرمز 404. ستحتاج لخادم ويب ساكن Static أو ديناميكي Dynamic لنشر موقع الويب الخاص بك. والفرق بين هذه الخوادم هو: خادم الويب الساكن: هو حاسوب مزوّد بخادم HTTP يعيد الملفات التي يستضيفها كما هي تمامًا إلى المتصفح الذي يطلبها. خادم الويب الديناميكي: يتكون من خادم ويب ساكن بالإضافة إلى برمجيات إضافية تتمثل عادة بخادم تطبيقات وقاعدة بيانات، ويُعد هذا الخادم ديناميكيًا لأن خادم التطبيقات سيُحدّثُ الملفات التي يستضيفها قبل إرسال محتوياتها إلى المتصفح عبر خادم HTTP. لكي ترى صفحة الويب بشكلها النهائي على المتصفح، سيملأ خادم التطبيقات -على سبيل المثال- قالب HTML معين بمحتويات مصدرها قاعدة بيانات ثم يرسلها. تحتوي مواقع الويب الضخمة مثل MDN أو ويكيبيديا مثلًا على آلاف صفحات الويب، وهذه المواقع مكوّنة أساسًا من عدة قوالب HTML جاهزة وقاعدة بيانات ضخمة جدًا بدلًا من آلاف مستندات HTML الجاهزة للتسليم. إذ تتيح هذه التقنية سهولة في الصيانة وتسليم المحتوى. سنتعرف في هذا المقال على ماهية خادم الويب وأنواعه ونكتسب فهمًا عامًا لآلية عمله. ننصحك قبل الشروع في قراءة هذا المقال بالاطلاع على المقال كيف تعمل شبكات الإنترنت؟ وأن تكون على دراية بالفرق بين صفحة الويب وموقع الويب وخادم الويب ومحرك البحث. الملفات المستضافة قبل أن نتعمق أكثر، لنتذكر سريعًا أن المتصفح يُرسل طلبًا إلى خادم ويب عندما يريد إحضار صفحة ويب والذي يبحث بدوره عن الملف المطلوب ضمن مخازنه. عندما يجد الخادم الملف يقرأه أو يعالجه بالطريقة المطلوبة ثم يرسله إلى المتصفح. لننظر إذًا إلى هذه الخطوات بشيء من التفصيل. يجب على خادم الويب في البداية أن يخزّن جميع الملفات المرتبطة بموقع ويب معين وتحديدًا جميع مستندات HTML والملفات المساعدة لها كملفات تنسيق الصفحات CSS، وملفات المعالجة كملفات جافاسكربت، وخطوط الكتابة والفيديوهات وغيرها. بالإمكان استضافة كل أنواع الملفات السابقة تقنيًا على حاسوبك الشخصي، لكن من الأفضل تخزينها على خادم ويب بعيد ومخصص وذلك للأسباب التالية: خوادم الويب المخصصة لاستضافة الملفات متاحة دومًا (أي في حالة عمل مستمر). خوادم الويب متصلة دومًا بالإنترنت باستثناء أوقات الصيانة وعند حدوث مشاكل آنية في منظوماتها. يبقى لخادم الويب عنوان آي بي ثابت دومًا (ما يعرف بعنوان آي بي مخصص Dedicate IP)، إذ لا تؤمن جميع مزودات خدمة الإنترنت ISPs عنوان آي بي ثابت للخطوط المنزلية. تُصان الخوادم المخصصة تقليديًا من قبل جهة مستقلة (طرف ثالث). وبالتالي فإن إيجاد مضيف جيد لملفات وموارد موقعك هي خطوة مفتاحية من خطوات بناء الموقع. تفحص الخدمات المتنوعة التي تقدمها شركات الاستضافة، واختر الشركة التي تقدم الخدمات التي تحتاجها ضمن حدود ميزانيتك فيمكن أن تكون الاستضافة مجانية ويمكن أن تصل إلى آلاف الدولارات شهريًا. عليك في الخطوة التالية أن ترفع ملفاتك إلى الخادم بعد أن تؤمن استضافة موقعك على مزود خدمة. الاتصال عبر بروتوكول HTTP يدعم خادم ويب بروتوكول النقل HTTP ويأتي الاسم كاختصار للعبارة Hypertext Transfer Protocol وهو بروتوكول نقل النصوص التشعبية. يحدد البروتوكول طريقة نقل النصوص التشعبية (مستندات ويب مترابطة) بين حاسوبين. والبروتوكول: هو مجموعة من القواعد التي يجب على حاسوبين اتباعها ليتواصلا بطريقة صحيحة. ويعدّ بروتوكول HTTP بروتوكولًا نصيًا Textual أي لا يتعلق بالحالة Stateless. عمومًا الفرق بين البروتوكول النصي وعديم الحالة هو على الشكل التالي: بروتوكول نصي Textual: جميع الأوامر على شكل نصوص يمكن للبشر قراءتها. بروتوكول عديم الحالة Stateless: لا يمكن للخادم ولا العميل أن يتذكرا أي شيء عن الاتصال السابق بينهما. فمثلًا بالاعتماد على بروتوكول HTTP لا يمكن أن يتذكر الخادم كلمة السر التي أدخلتها أو المرحلة التي وصلت إليها خلال عملية تحويل غير مكتملة، ولإدارة خدمات كهذه ستحتاج إلى خادم تطبيقات. يحدد بروتوكول HTTP قواعد واضحة لآلية التواصل بين الخادم والعميل. عليك في هذه المرحلة أن تنتبه إلى النقاط التالية: عندما يطلب المتصفح ملفًا من خادم ويب عبر بروتوكول HTTP لا بد أن يزود الخادم بعنوان URL للملف. على خادم الويب أن يجيب على كل طلب HTTP يصله برسالة خطأ على الأقل. يُعد خادم HTTP المثبت على خادم الويب مسؤولًا عن معالجة الطلبات القادمة والرد عليها، ومن مسؤولياته نذكر: يتحقق خادم HTTP عند استلام الطلب إن كان هناك تطابق بين عنوان URL وأحد الملفات التي يستضيفها الخادم. يعيد الخادم محتوى الملف المطلوب إن وجد تطابقًا، وإن لم يجد يحضّر خادم التطبيقات ملف الاستجابة اللازم لفشل الطلب. إن لم يتمكن الخادم من تنفيذ أي من الخطوتين السابقتين سيعيد رسالة خطأ إلى المتصفح، وهي عادةً رسالة الخطأ 404 الصفحة غير موجودة. يعدّ هذا الخطأ كثير الحدوث لذلك يحاول مصممو مواقع الويب بذل الجهد في إخراج صفحات الخطأ بطريقة لبقة. المحتوى الثابت والمحتوى المتغير لصفحة ويب يمكن القول أن خادم ويب قادر على التعامل مع المحتويات الثابتة والمتغيرة (الديناميكية). لكن تذكر أن مصطلح "محتوى ساكن (أو ثابت)" يعني "يقدم كما هو". لذلك ستجد أن مواقع الويب الساكنة هي الأسهل إعدادًا ونقترح أن يكون موقعك الأول موقع ويب ساكن. ويعني المصطلح "محتوى متغير" أن الخادم سيعالج هذا المحتوى أو يولّده أثناء المعالجة انطلاقًا من قاعدة بيانات. يتيح لك هذا الخيار مرونة أكثر لكن تبعاته التقنية أعقد، مما يجعل عملية بناء مواقع ديناميكية أكثر صعوبة. هناك العديد من خوادم التطبيقات وكل واحد منها له تقنيات مختلفة لذلك من الصعب أن نقترح عليك خادمًا محددًا. لأن خوادم التطبيقات مثلًا تتعلق بفئة معينة من مواقع الويب مثل المدونات أو مواقع المحتوى القابل للتغيير wikis أو مواقع التجارة الإلكترونية، بينما يكون بعضها الآخر أكثر عموميةً. خذ وقتك في إنتقاء خادم التطبيقات الذي يناسبك عندما تفكر في بناء موقع ديناميكي. إلا إذا رغبت في تعلم برمجة خوادم الويب وبناء خادمك الخاص (وهو أمر مثير بحد ذاته)، وعمومًا لا تُعد اختراع العجلة وانتق خادم تطبيقات جاهز. ترجمة -وبتصرف- للمقال What is a web server اقرأ أيضًا دورة علوم الحاسوب ماهي أسماء النطاقات في شبكة الإنترنت؟ ما هي الأدوات المستخدمة في بناء مواقع ويب؟ ما التكلفة المادية الكاملة لبناء موقع ويب؟ ما هي أدوات مطوري الويب المدمجة في المتصفحات؟
-
يُعد عنوان URL بالإضافة إلى النص التشعبي Hypertext وبروتوكول HTTP أحد المفاهيم المفتاحية للويب فهو الآلية التي تستخدمها المتصفحات browsers للوصول أي مورد موجود في الويب. عنوان URL هو مجرّد عنوان فريد لمورد على الويب، نظريًا يدل كل عنوان URL صحيح على مورد فريد ومحدد كصفحة HTML أو ملف CSS أو صورة أو غيرها من الموارد، ولكن في واقع هناك بعض الاستثناءات، وأكثرها شيوعًا عنوان URL يشير إلى مورد لم يعد موجودًا أو تغيّر موقعه. وطالما أن المورد يُمثّل بعنوان URL والخادم هو من يتعامل مع عنوان URL فلذلك تقع على عاتق مالك الخادم مهمة إدارة الموارد وعناوين URL المرتبطة بها بعناية. سنتعرف في هذا المقال على تفاصيل عنوان URL وكيفية عمل هذه العناوين على الويب. ننصحك قبل الشروع في إكمال قراءة المقال أن تطلع على مقال كيف تعمل شبكة الإنترنت؟ وأن يكون مفهوم الروابط التشعبية واضحًا بالنسبة لك. تشريح عنوان URL إليك بعض الأمثلة عن عناوين URL: https://developer.mozilla.org https://developer.mozilla.org/en-US/docs/Learn/ https://developer.mozilla.org/en-US/search?q=URL يمكن كتابة أيًا من تلك العناوين في شريط العنوان في المتصفح لتحميل الصفحة أو المورد المرتبط به. يتكون عنوان URL من أجزاء مختلفة بعضها إلزامي وبعضها اختياري. لاحظ الأجزاء المظللة في العنوان التالي والتي تُعد الأكثر أهمية في عنوان URL: لتوضيح الفكرة يمكن أن نشبّه عنوان URL بعنوان بريدي نمطي، إذ يمثل المخطط Scheme الخدمة البريدية التي تريد استخدامها، ويمثل اسم النطاق Domain name اسم المدينة أو البلدة، وستعمل المنفذ Port عمل الرمز البريدي، وسيمثل المسار Path البناء الذي ينبغي تسليم البريد إليه. أما المعاملات Parameters فستمثل أي معلومات إضافية كرقم الشقة وستمثل المرساة Anchor مستلم الرسالة الفعلي الذي وجَّهت رسالتك إليه. بروتوكول عنوان URL وهو القسم الأول من URL ويشير إلى البروتوكول الذي ينبغي استخدامه لطلب مورد معين من الخادم (البروتوكول هو طريقة لإعداد آلية لتبادل البيانات ونقلها عبر شبكة من الحواسيب). يُستخدم عادة برتوكولي HTTP و HTTPS (النسخة الآمنة) في مواقع الويب ولا بد من مخاطبة صفحات الويب باستخدام أحدهما. مع ذلك تدرك المتصفحات تمامًا كيف تتعامل مع بروتوكولات Schemes أخرى مثل :mailto (لفتح واجهة بريد إلكتروني) فلا تتفاجأ إن رأيت بروتوكولات أخرى. التصريح Authority يأتي بعد البروتوكول التصريح ويفصل بينهما النمط //:. يتضمن التصريح في حال وجوده اسم النطاق (www.example.com مثلًا) ورقم المنفذ (80 مثلًا) يفصل بينهما الرمز :. يشير النطاق Domain إلى خادم الويب الذي يُرسل إليه الطلب ويكون عادة على شكل اسم، كما يمكن استخدام عنوان آي بي لكن استخدامه نادر وغير ملائم في معظم الأحيان. يشير المنفذ Port إلى البوابة المستخدمة للوصول إلى المورد. يُحذف المنفذ عادة عند استخدام الخادم المنافذ المعيارية للبروتوكول HTTP، إذ يستخدم المنفذ 80 لبروتوكول HTTP والمنفذ 443 لبروتوكول HTTPS عند منح الإذن بالوصول إلى المورد. ما عدا ذلك لا بد من كتابة المنفذ المستخدم. في بعض الحالات لا يستخدم URL قسم التصريح كما هو الحال في واجهات البريد الإلكتروني (mailto:foobar). إذ يحتوي URL مخططًا فقط دون تصريح، لذلك لن تجد النمط //، وإنما ستجد فقط النقطتين المتعامدتين التي تعمل فقط كفاصل بين المخطط وعنوان البريد الإلكتروني. المسار إلى مورد ويمثل الطريق للوصول إلى مورد موجود على خادم ويب (path/to/myfile.html/ مثلًا). مثَّلت هذه المسارات في بدايات الويب الموقع الفيزيائي لملف على خادم، لكنه حاليًا مجرد اختصار يتعامل معه الخادم بطريقة معينة للوصول إلى المورد وغالبًا لا يشير إلى المكان الحقيقي لوجود المورد. المعاملات وهي قائمة من العناصر الثنائية على شكل"مفتاح/قيمة" يفصل بينها الرمز & مثل القائمة key1=value1&key2=value2 كما في الصورة السابقة. يمكن لخادم ويب استخدام هذه المعاملات في تنفيذ عمليات إضافية قبل أن يعيد المورد المطلوب. لكل خادم ويب قواعده الخاصة المتعلقة بمعالجة المعاملات، ومن الأفضل دومًا أن تسأل مالك الخادم عن طريقة تعامل خادمه مع المعاملات. المربط يمثل المربط # (ويشار إليه المرساة أحيانًا) طريقة للاشارة إلى جزء معين من المورد نفسه، وتشبه فكرته فكرة "الاشارة المرجعية" داخل المورد نفسه. فلو كان المورد ملف HTML مثلًا، سينتقل المتصفح إلى النقطة التي يُعرّفها المربط "#"، وفي حال كان المربط يشير إلى نقطة زمنية معينة من ملفات الصوتية أو الفيديو سيحاول المتصفح الانتقال إلى الزمن الذي يحدده المربط. وتجدر الإشارة هنا، إلى أن القسم الذي يقع بعد الرمز "#" (ويُدعى "مُعرِّف القطعة Fragment Identifier") لن يُرسل أبدًا إلى الخادم مع الطلب. كيف تستخدم URL بإمكانك كتابة أي URL ضمن شريط العنوان في المتصفح لتحضر المورد الذي يمثله، لكن هذا الاستخدام ما هو إلا رأس الهرم فقط! تستخدم لغة HTML عناوين URL بكثرة لكي: تنشئ روابطًا مع مستندات أخرى باستخدام العنصر <a>. تربط مستندًا مع موارده عبر عناصر متعددة مثل <link> أو <script>. تعرض الوسائط مثل الصور (باستخدام العنصر <img>) والفيديو (باستخدام العنصر <video>) والملفات الصوتية (باستخدام العنصر<audio>). تعرض ملفات HTML أخرى باستخدام العنصر <iframe>. تستخدم التقنيات الأخرى مثل CSS وJavaScript عناوين URL بكثرة أيضًا، وهذه التقنيات هي ما تشكل الويب في الواقع. عناوين URL المطلقة والنسبية تُعد الأمثلة التي أوردناها سابقًا عن عنوان URL عناوينًا مطلقة Absolute URLs، لكن لا يزال هناك نمط آخر وهي العناوين النسبية Relative URLs ولا بدّ من الإشارة إلى الفوارق بينهما بشيء من التفصيل. تعتمد الأجزاء التي ينبغي استخدامها من عنوان URL والأخرى التي يمكن إهمالها على السياق الذي يستخدم فيه عنوان URL. فمثلًا في شريط عنوان متصفحك لا يوجب أي سياق محدد لعناوين URL لهذا لا بدّ من كتابة URL بشكله الكامل (أو المطلق) كما فعلنا في الأمثلة السابقة. لا حاجة بالطبع في حالة المتصفح أن تذكر البروتوكول لأن المتصفح يستخدم البروتوكول HTTP افتراضيًا، ولا نذكر أيضًا المنفذ الذي نحتاجه إلا في الحالة التي نتصل فيها مع الخادم عبر منفذ غير اعتيادي، ولكن لا بد من كتابة بقية أجزاء عنوان URL. عندما يُستخدم عنوان URL داخل مستند كصفحة HTML ستختلف الأمور قليلًا. إذ يمتلك المتصفح في هذه الحالة عنوان URL الخاص بالمستند ككل، وبالتالي سيتمكن من استخدام معلوماته لإكمال الأجزاء الناقصة لعنوان URL الموجود داخل المستند. يمكن التمييز بين عنوان URL المطلق والنسبي بالنظر فقط إلى جزء المسار، فإذا بدأ عنوان URL بالرمز / فسيحضر المتصفح المورد من أعلى عنوان جذري للخادم دون الحاجة إلى المكان الذي يشير إليه المستند. لنلق نظرة على بعض الأمثلة لتوضيح الأمر. أمثلة عن عناوين URL مطلقة يُعد العنوان الآتي مطلقًا أو كاملًا https://developer.mozailla.org/docs/learn بينما يعد العنوان التالي غير كامل إذ سيستخدم هذا العنوان البروتوكول الضمني للمستند الذي يتضمن عنوان URL. developer.mozilla.org/en-us/docs/Learn// أما العنوان الآتي فهو اسم نطاق ضمني، وهي الحالة الأكثر استخدامًا لعناوين URL المطلقة داخل مستندات HTML. /en-us/docs/learn/ يستخدم المتصفح في هذه الحالة نفس البروتوكول واسم النطاق المستخدمان في تحميل المستند الذي يحتوي على عنوان URL لاحظ أنه لا يمكن حذف اسم النطاق دون حذف البروتوكول أيضًا. أمثلة عن عناوين URL نسبية لتفهم الأمثلة التالية بطريقة صحيحة، سنفترض أن عناوين URL ستُستدعى من داخل المستند الموجود على عنوان URL التالي: https://developer.mozilla.org/en-us/docs/Learn يستخدم العنوان "Skills/Infrastructure/Understanding_URLs" فكرة الموارد الفرعية، فطالما أنّ عنوان URL لم يبدأ بالرمز / سيحاول المتصفح أن يجد المستند في مجلد فرعي ضمن المجلد الذي يحتوي المورد الحالي أي سنرغب في مثالنا في الوصول إلى عنوان URL التالي: https://developer.mozilla.org/en-us/docs/Learn/Skills/Infrastructure/Understanding_URLs. بينما يستخدم العنوان "../CSS/display" فكرة التراجع إلى الخلف في مسار الشجرة، ونستخدم في هذه الحالة النمط /.. الموروث من نظام ملفات يونكس لكي نخبر المتصفح أن ينتقل إلى المجلد الأعلى مباشرة. أي أننا نريد هنا الوصول إلى عنوان URL التالي: https://developer.mozilla.org/en-US/docs/Learn/../CSS/display والذي يمكن اختصاره إلى: https://developer.mozilla.org/en-US/docs/CSS/display عناوين URL الدلالية بالرغم من الصبغة التقنية الواضحة لهذا النوع، إلا أن عناوين URL الدلالية Semantic URLs هي طريقة دخول إلى موقع ويب معين يمكن للبشر فهمها. أي يمكن أن يحفظها المستخدم بسهولة ويدخلها بنفسه في شريط عنوان المتصفح، وطالما أنّ المستخدمين هم مركز اهتمام الويب، فمن أفضل الممارسات التي ينبغي نشرها هي بناء ما يُدعى عناوين URL واضحة ومقروءة Clean URL، وهي عناوين تستخدم كلمات في صلب موضوع محتوى العناوين يمكن لأي شخص فهمها دون أن تكون لديه أي فكرة تقنية عن كيفية إنجاز الأمر. لا علاقة بالطبع للدلالات اللغوية بالحواسيب، فلربما بدت لك عناوين URL كخليط من الرموز العشوائية، لكن هناك إيجابيات عدة لكتابة URL مقروء من قبل المستخدم منها: سهولة تعديلها. تجعل الكثير من النقاط أكثر وضوحًا للمستخدم مثل الموقع الذي يتواجد فيه وما الذي يفعله في هذا الموقع أو ما الذي يقرأه أو يتفاعل معه على الويب. يمكن لعناوين URL المقروءة أن تحسن ترتيب وتصنيف الصفحات في محركات البحث. ترجمة -وبتصرف- للمقال What is a URL. اقرأ أيضًا ما هو خادم الويب؟ ما هي الأدوات المستخدمة في بناء مواقع ويب؟ ما هي أدوات مطوري الويب المدمجة في المتصفحات؟ ما هي أسماء النطاقات في شبكة الإنترنت؟
-
بعد أن تعرفنا على أساسيات التعابير النمطية ثم تعرفنا على المجموعات ثم المحددات الكمية ثم تعلمنا كيفية التقاط عدة مجموعات عبر التعابير النمطية، سنقدم فيما يلي مجموعةً من الأفكار المتقدمة في بناء وتنفيذ تعابير نمطية أكثر كفاءةً في البحث عن التطابقات المطلوبة، مثل المراجع References، والمحرف البديل OR، والبحث قُدُمًا lookahead، والبحث إلى الخلف lookbehind، والبحث في موقع محدد باستخدام الراية y. المراجع إلى المجموعات بالأعداد والأسماء يمكن استخدام محتويات أقواس المجموعات الملتقطة (...) في النتيجة أو في النص البديل، وحتى في النمط نفسه. المراجع باستخدام الأعداد يمكن الإشارة إلى مجموعة ضمن نمط باستخدام N\، حيث N هو رقم المجموعة، ولتوضيح أهمية هذه الإشارة المرجعية لنتأمل المهمة التالية: نريد إيجاد ما داخل إشارتي التنصيص المفردتين '...' أو المزدوجتين "..." في نص ما، فكيف سننجز ذلك؟ يمكن وضع نوعي إشارة التنصيص في أقواس مربعة ['"](?*.)['"]، لكنه في هذه الحالة سيجد محتويات مختلطةً، مثل "...' و'..."، مما سيقودنا إلى تطابقات خاطئة عندما يظهر نوع من الإشارات ضمن آخر، مثل "!She's the one". let str = `He said: "She's the one!".`; let regexp = /['"](.*?)['"]/g; // النتيجة ليست كما نتوقع alert( str.match(regexp) ); // "She' وجد النمط -كما توقعنا- إشارة تنصيص البداية "، ثم استهلك النص بعدها حتى وجد إشارة تنصيص أخرى ' أنهت التطابق، وللتأكد من مطابقة إشارة التنصيص الختامية لإشارة تنصيص البداية، يمكن وضعها ضمن قوسي مجموعة، والإشارة إليها بعدد مرجعي 1\(?*.)(["']) إليك الشيفرة الصحيحة: let str = `He said: "She's the one!".`; let regexp = /(['"])(.*?)\1/g; alert( str.match(regexp) ); // "She's the one!" سيجد محرك التعبير النمطي إشارة تنصيص البداية (["'])، ويتذكر محتواها الذي يمثل المجموعة الملتقطة الأولى، حيث يعني العدد 1\ في النمط إيجاد نفس التطابق الموجود في المجموعة الأولى، وهي في حالتنا إشارة تنصيص تطابق تمامًا إشارة البداية. وسيعني العدد 2\ محتويات المجموعة الثانية، والعدد 3\ محتويات الثالثة، وهكذا. لاحظ، لن نتمكن من الإشارة إلى مجموعة إذا استخدمنا نمط الاستثناء :?، ولن يتذكر المحرك محتوى المجموعات المستثناة (...:?). انتبه، لا تخلط بين الإشارة 1\ في نمط ما، والإشارة 1$ في النص البديل، حيث نستخدم إشارة الدولار 1$ في الإشارة المرجعية ضن النص البديل، والشرطة المعكوسة 1\ ضمن الأنماط. المراجع باستخدام الأسماء إذا احتوى التعبير النمطي على عدة أقواس، فمن الأنسب استخدام الأسماء للدلالة عليها، نستخدم <k<name\ في الدلالة على القوس بالاسم، فإذا سُميت مجموعة ضمن التعبير النمطي بالاسم <quote>?، فسيكون الاسم المرجعي لها هو <k<quote\، وإليك مثالًا: let str = `He said: "She's the one!".`; let regexp = /(?<quote>['"])(.*?)\k<quote>/g; alert( str.match(regexp) ); // "She's the one!" البديل باستخدام OR في التعابير النمطية يعني مصطلح "البديل" Alternation في التعابير النمطية استخدام العملية المنطقية "OR"، ويرمز لها ضمن التعابير النمطية بالخط العمودي |، فلو أردنا مثلًا إيجاد لغات برمجة مثل HTML أو PHP أو Java أو JavaScript، فسيكون التعبير النمطي المناسب هو ?(html|php|java(script، وإليك مثالًا تطبيقيًا: let regexp = /html|php|css|java(script)?/gi; let str = "First HTML appeared, then CSS, then JavaScript"; alert( str.match(regexp) ); // 'HTML', 'CSS', 'JavaScript' لكننا رأينا سابقًا أنّ الأقواس المربعة تنفذ أمرًا مشابهًا، فهي تسمح باختيار أحد المحارف التي توضع ضمنها، فإذا استخدمنا النمط gr[ae]y مثلًا فسنحصل على التطابقين gray أو grey، إذ تسمح الأقواس المربعة باستخدام المحارف أو أصناف المحارف ضمنها، بينما يسمح البديل باستخدام أي عبارات، حيث يعني التعبير A|B|C أيًا من العبارات A أو B أو C، وإليك بعض الأمثلة: يماثل النمط gr(a|e)y النمط gr[ae]y. يعني النمط gra|ey أيًا من gra أو ey. ولتطبيق البديل على جزء محدد من نمط، يمكن وضع هذا الجزء داخل قوسين: يطابق النمط I love HTML|CSS كلًا من I love HTML أو CSS. يطابق النمط (I love (HTML|CSS كلًا من I love HTML أو I love CSS مثال: تعبير نمطي لإيجاد الوقت صادفنا في المقالات السابقة مهمة بناء تعبير نمطي يبحث عن الوقت وفق التنسيق hh:mm، مثل 12:00، لكن التعبير الذي استُخدم d\d:\d\d\ سطحي جدًا، إذ يقبل هذا النمط قيمًا خاطئة للتوقيت مثل 25:99، فكيف سننجز نمطًا أفضل؟ بالنسبة للساعات: إذا كان الرقم الأول 0 أو1 فيمكن أن يكون الثاني أي رقم. إذا كان الرقم الأول 2، فيجب أن يكون الثاني [0-3]. لا يسمح بأي رقم آخر غير ذلك. يمكن كتابة تعبير نمطي يضم الحالتين باستخدام البديل بالشكل التالي: [01]\d|2[0-3] أما بالنسبة للدقائق: ينبغي أن تكون الدقائق بين 00 و59، ويكتب هذا في التعبير النمطي بالشكل [0-5]\d، أي يمكن أن تكون الآحاد أي رقم، والعشرات من 1 إلى 5. وعند ضم الساعات والدقائق معًا سنحصل على التعبير: [01]\d|2[0-3]:[0-5]\d انتهينا تقريبًا لكن مع وجود مشكلة صغيرة، إذ وضعنا محرف البديل | بالشكل السابق فصل التعبير إلى قسمين منفصلين: [01]\d | 2[0-3]:[0-5]\d إليك ما سيحدث، سيبحث النمط عن أحد النمطين، لكن هذا خاطئ، لأنّ البديل سيستخدَم فقط في قسم الساعات من التعبير النمطي، وسنصلح ذلك بوضع قسم الساعات ضمن قوسين ([01]\d|2[0-3]):[0-5]\d إليك الحل النهائي: let regexp = /([01]\d|2[0-3]):[0-5]\d/g; alert("00:00 10:10 23:59 25:99 1:2".match(regexp)); // 00:00,10:10,23:59 التحقق مما يلي أو يسبق التعبير النمطي نحتاج في بعض الأحيان إلى إيجاد تطابقات بشرط أن يأتي بعدها أو قبلها تطابقًا أو نمطًا محددًا دون أن تدخل تلك التطابقات ضمن قيم النتيجة النهائية، ولهذه الغاية سنجد صيغتا تحقق تُدعيان "انظر أمام النمط" lookahead،" وانظر خلف النمط "lookbehind،" وقبل أن نبدأ موضوعنا، سنأخذ مثالًا نحاول فيه إيجاد السعر ضمن النص 1 turkey costs 30€، وهو عدد تتبعه الإشارة €. انظر أمام التعبير النمطي النمط (X(?=Y يعني "ابحث عن X، لكن أعد نتيجة التطابق فقط إذا كان بعدها Y"، ويمكن أن تجد أي نمط مكان X أو Y، فمن أجل عدد صحيح تتبعه الإشارة €، سيكون النمط المناسب هو (€=?)+d\: let str = "1 turkey costs 30€"; alert( str.match(/\d+(?=€)/) ); // 30, € يهمل الرقم 1 لأنه غير متبوع بالإشارة لاحظ أنّ مطابقة ما أمام النمط هو اختبار وعملية تحقيق فقط ببساطة، ولا يشكل محتوى ما بين القوسين جزءًا من النتيجة، فعندما نبحث عن النمط (X(?=Y، فسيجد المحرك X ومن ثم يتحقق من وجود Y بعدها مباشرةً إن وجده أعاد X فقط وإلا لم يعد شيئًا، ثم يتابع البحث. يمكن كتابة اختبارات أكثر تعقيدًا، مثل النمط (x(?=y)(?=z الذي يعني: جد X. تحقق من وجود Y مباشرةً بعد X، وتجاوز التطابق إن لم يتحقق ذلك. تحقق أيضًا من وجود Z مباشرةً بعد X، وتجاوز التطابق إن لم يتحقق ذلك. إذا نجح الاختباران السابقان فأعد قيمة X، وإلا فتابع البحث. أي أننا نبحث عن X التي تتبعها القيمتان Y وZ في نفس الوقت. وسيبحث النمط (d+(?=\s)(?=.*30\ عن عدد +d\ يتبعه فراغ (s\=?)، ثم العدد 30 في مكان ما من النص: let str = "1 turkey costs 30€"; alert( str.match(/\d+(?=\s)(?=.*30)/) ); // 1 التطابق الوحيد المناسب في المثال السابق هو 1. لنفترض بأننا سنحتاج إلى الكمية وليس السعر من النص نفسه، وبالتالي سنحتاج إلى عدد +d\ غير متبوع بالإشارة €، يمكن إنجاز ذلك باستعمال النمط الاختباري (x(?!y، أي "انظر أمام النمط، يجب أن لا يتحقق الشرط" بمعنى ابحث عن X التي لا يتبعها Y مباشرةً. let str = "2 turkeys cost 60€"; alert( str.match(/\d+\b(?!€)/g) ); // 2 (the price is not matched) انظر خلف التعبير النمطي يشابه هذا النمط نمط التحقق مما يلي التعبير، لكنه يضع شرطًا على ما قبل التعبير المتطابق ويمكن أن نستخدمه بالصيغ التالية: "التحقق من مطابقة ما يسبق التعبير" Positive lookbehind عبر الصيغة Y)X=>?): أوجد X إذا سبقتها Y. "التحقق من عدم مطابقة ما يسبق التعبير" Negative lookbehind عبر الصيغة Y)X!>?): أوجد X إذا لم تسبقها Y. لنغير على سبيل المثال السعر إلى دولار، حيث تكون إشارة الدولار $ عادةً قبل السعر، فللبحث عن 30$ سنستخدم النمط +d\($\=>?): let str = "1 turkey costs $30"; // \$ سنتجاوز الإشارة alert( str.match(/(?<=\$)\d+/) ); // 30 وإذا أردنا الكمية، فسنبحث عن عدد لا تسبقه الإشارة $، وبالتالي نستخدم البحث السلبي خلفًا +d\($\!>?): let str = "2 turkeys cost $60"; alert( str.match(/(?<!\$)\b\d+/g) ); // 2 إدخال الشرط ضمن نتيجة التطابق لا يظهر محتوى ما بين القوسين جزءًا من النتيجة عادةً، فلن تكون الإشارة € مثلًا ضمن نتيجة التطابق مع النمط (€=?)+d\. وهذا أمر طبيعي، لأننا نبحث عن العدد الذي قبلها، وهي مجرد اختبار، لكن قد نحتاج في بعض الحالات إلى ما حول التطابق أو إلى جزء منه، ويمكن إنجاز ذلك بوضع الجزء المطلوب ضمن قوسين إضافيين، سنعيد في المثال التالي السعر مع إشارة العملة (kr|€): let str = "1 turkey costs 30€"; let regexp = /\d+(?=(€|kr))/; // €|kr أقواس إضافية حول alert( str.match(regexp) ); // 30, € إليك مثالًا مع البحث خلفًا: let str = "1 turkey costs $30"; let regexp = /(?<=(\$|£))\d+/; alert( str.match(regexp) ); // 30, $ الراية y والبحث في موقع محدد تسمح الراية y بالبحث في موقع محدد ضمن النص الأصلي، ولفهم عمل هذه الراية وأساليب استخدامها في التعابير النمطية جيدًا سنطرح مشكلةً عمليةً، حيث تعتبر عملية التحليل التجريدي لنص lexical analysis من المهام الأساسية لمحركات التعابير النمطية، ففي نصوص الشيفرة في لغات البرمجة تلعب محركات التعابير الدور الأساسي في إيجاد العناصر البنيوية، مثل وسوم HTML والسمات التي في داخلها، وستساعد في JavaScript على إيجاد المتغيرات والدوال وغيرها، ولن ندخل في موضوع إنشاء المحللات التجريدية فهي خارج نطاق موضوعنا، لكن توجد مهمة مشتركة وهي إيجاد شيء ما في موقع محدد، لنفترض مثلًا أنّ لدينا الشيفرة التالية "let varName = "value في صيغة نص، ونحتاج إلى تحديد اسم المتغير الذي سيبدأ في الموقع الرابع، سنبحث عن اسم المتغير من خلال التعبير +w\، وطبعًا نحتاج إلى تعابير أكثر تعقيدًا للبحث الدقيق عن أسماء المتغيرات في JavaScript لكن لن نهتم بذلك حاليًا. سيجد التابع (/+str.match(/\w الكلمة الأولى فقط في السطر وهي (let)، وطبعًا ليست هي الكلمة المطلوبة. ولو استخدمنا الراية g فسيبحث التابع عن جميع الكلمات في النص، لكننا لا نحتاج سوى الكلمة الموجودة في الموقع الرابع. كيف سنبحث عن نمط محدد ابتداءً من موقع محدد؟ لنحاول استخدام التابع (regexp.exec(str، حيث سيبحث هذا التابع عن التطابق الأول بطريقة مشابهة للتابع (str.match(regexp عندما يُستخدم دون الرايتين g أو y، لكنه سيبحث في النص ابتداءً من الموقع المخزن في الخاصية regexp.lastIndex عندما نفعّل الراية g، فإذا وجد تطابقًا فسيخزن الموقع الذي يلي التطابق في الخاصية regexp.lastIndex، أي ستكون الخاصية regexp.lastIndex نقطة بداية للبحث، وسيغير كل استدعاء للتابع (regexp.exec(str قيمتها إلى القيمة الجديدة -بعد إيجاد تطابق-، وبالتالي سيعيد الاستدعاء المتكرر للتابع (regexp.exec(str التطابقات واحدًا تلو الآخر، وإليك مثالًا: let str = 'let varName'; // لنبحث عن كل الكلمات في هذه السلسلة let regexp = /\w+/g; alert(regexp.lastIndex); // 0 ( lastIndex=0) let word1 = regexp.exec(str); alert(word1[0]); // let (الكلمة الأولى) alert(regexp.lastIndex); // 3 (الموقع بعد التطابق) let word2 = regexp.exec(str); alert(word2[0]); // varName (الكلمة الثانية) alert(regexp.lastIndex); // 11 (الموقع بعد التطابق) let word3 = regexp.exec(str); alert(word3); // null (لا تطابقات) alert(regexp.lastIndex); // 0 (يصفر قيمة الخاصية) ويمكن الحصول على كل التطابقات من خلال حلقة: let str = 'let varName'; let regexp = /\w+/g; let result; while (result = regexp.exec(str)) { alert( `Found ${result[0]} at position ${result.index}` ); // Found let at position 0, then // Found varName at position 4 } يمثل استخدام التابع regexp.exec أسلوبًا بديلًا عن str.matchAll مع بعض السيطرة على مجرى العملية. بالعودة إلى مهمتنا، سنتمكن من تحديد الموقع بإسناد القيمة 4 إلى الخاصية lastIndex لنبدأ البحث من الموقع المطلوب بالشكل التالي: let str = 'let varName = "value"'; let regexp = /\w+/g; // "g" دون استخدام lastIndex يتجاهل المحرك الخاصية regexp.lastIndex = 4; let word = regexp.exec(str); alert(word); // varName لقد حُلِّت المشكلة! والنتيجة صحيحة، لكن ينبغي أن نتروّى قليلًا.. يبدأ التابع regexp.exec البحث انطلاقًا من الموقع lastIndex، ثم يستأنف البحث، فإذا لم يكن التطابق موجودًا في هذا الموقع بل في موقع يأتي بعده فسيجده أيضًا: let str = 'let varName = "value"'; let regexp = /\w+/g; // ابدأ البحث في الموقع 3 regexp.lastIndex = 3; let word = regexp.exec(str); // سيجد التطابق في الموقع 4 alert(word[0]); // varName alert(word.index); // 4 إنّ هذا الأمر خاطئ في العديد من المهام ومنها التحليل التجريدي، إذ يجب إيجاد التطابق في الموقع المحدد تمامًا وليس بعده، وهذا سبب وجود الراية y. حيث تجبر الراية y التابع regexp.exec على البحث في الموقع lastIndex بالتحديد وليس انطلاقًا منه. إليك مثالًا: let str = 'let varName = "value"'; let regexp = /\w+/y; regexp.lastIndex = 3; alert( regexp.exec(str) ); // null (يوجد فراغ في هذا الموقع وليس كلمة) regexp.lastIndex = 4; alert( regexp.exec(str) ); // varName (الكلمة في الموقع 4) لاحظ أننا لن نحصل على التطابق مع التعبير w+/y\/ في الموقع 3 (على عكس g) لكن في الموقع 4، وليس هذا فحسب، بل توجد فائدة أخرى لاستخدام الراية y، تخيل أن لدينا نصًا طويلًا ولا توجد فيه أية تطابقات للنمط الذي نبحث عنه، سيبحث المحرك عند تفعيل الراية g حتى نهاية النص ولن يجد نتيجةً، وهذا ما سيستهلك الوقت مقارنةً باستخدام الراية y التي تتحقق فقط في الموقع المحدد. سنواجه في المهام التي تشبه التحليل التجريدي عمليات بحث متعددةً في مواقع محددة، للتحقق مما هو موجود، وسيكون استخدام الراية y المفتاح لإنجاز مثل هذه العمليات بدقة وفعالية. خلاصة يفيدنا التحقق مما يلي التطابق ويسبقه في إيجاد تطابق بناءً على ما قبله أو بعده. يمكن استخدام تعابير نمطية بسيطة لإنجاز ذلك يدويًا، كأن نبحث عن كل التطابقات المطلوبة ونرشحها من خلال الحلقات وفق السياق المطلوب. تذكر أن التابع str.match (مع الراية g) والتابع str.match (دائمًا) سيعيدان مصفوفةً لها الخاصية index، وبالتالي سنعلم تمامًا موقع التطابق في النص، ثم نتحقق مما قبله أو بعده. إذا نفِّذ البحث خلفًا أو قدمًا آليًا فسيكون أفضل من الطريقة اليدوية، مثلما فعلنا في هذا المقال: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } النمط نوع البحث ماذا يُطابق؟ (X(?=Y بحث إيجابي قُدمًا X إذا جاءت بعدها Y (X(?!Y بحث سلبي قدمًا X إذا لم تكن بعدها Y y)x=>?) بحث إيجابي خلفًا X إذا جاءت بعد Y y)x!>?) بحث سلبي خلفًا X إذا لم تأت بعد Y تجبر الراية y التابع regexp.exec على البحث في الموقع lastIndex بالتحديد وليس انطلاقًا منه. مهام لإنجازها إيجاد لغات البرمجة اكتب تعبيرًا نمطيًا يبحث عن أسماء لغات البرمجة الموجودة في النص Java JavaScript PHP C++ C let regexp = /your regexp/g; alert("Java JavaScript PHP C++ C".match(regexp)); // Java JavaScript PHP C++ C الحل: تقتضي الفكرة الأولى بترتيب اللغات بحيث يفصل بينها المحرف|، لكن هذا الحل لن ينفع: let regexp = /Java|JavaScript|PHP|C|C\+\+/g; let str = "Java, JavaScript, PHP, C, C++"; alert( str.match(regexp) ); // Java,Java,PHP,C,C سيبحث محرك التعابير النمطية عن البدائل واحدًا تلو الآخر، فلو تحقق بداية من "Java" ووجدها، فلن يجد بالطبع "JavaScript" وكذلك الأمر بالنسبة للغتين "C" و "++C". هنالك حلين للمشكلة: غير ترتيب اللغات لتكون اللغة ذات الاسم الأطول أولًا: JavaScript|Java|C\+\+|C|PHP ادمج اللغات التي لها نفس البداية : Java(Script)?|C(\+\+)?|PHP إليك الشيفرة: let regexp = /Java(Script)?|C(\+\+)?|PHP/g; let str = "Java, JavaScript, PHP, C, C++"; alert( str.match(regexp) ); // Java,JavaScript,PHP,C,C++ إيجاد زوجي وسوم [tag]…[/tag] لنفترض وجود زوج الوسوم [tag]...[/tag]، حيث يمثّل tag أحد العناصر b أو url أو quote، إليك مثالًا: [b]text[/b] [url]http://google.com[/url] قد تكون هذه الوسوم متداخلةً، لكن لا يمكن أن يتداخل العنصر مع نفسه: Normal: [url] [b]http://google.com[/b] [/url] [quote] [b]text[/b] [/quote] Can't happen: [b][b]text[/b][/b] ليس بالضرورة أن تكون وسوم البداية والنهاية لعنصر على نفس السطر: [quote] [b]text[/b] [/quote] اكتب تعبيرًا نمطيًا للبحث عن تلك الوسوم ومحتوياتها، إليك مثالًا: let regexp = /your regexp/flags; let str = "..[url]http://google.com[/url].."; alert( str.match(regexp) ); // [url]http://google.com[/url] إذا كانت الوسوم متداخلةً فنريد فقط العنصر الخارجي: let regexp = /your regexp/flags; let str = "..[url][b]http://google.com[/b][/url].."; alert( str.match(regexp) ); // [url][b]http://google.com[/b][/url] الحل: يُعطى التعبير النمطي للبحث عن وسم البداية بالشكل: \[(b|url|quote)] ولإيجاد المحتوى كاملًا حتى بلوغ وسم النهاية سنستخدم النمط ?* مع الراية s لالتقاط أي محرف بما في ذلك محرف بداية السطر ثم سنضيف مرجعًا للخلف إلى وسم النهاية. \[(b|url|quote)\].*?\[/\1] إليك الشيفرة: let regexp = /\[(b|url|quote)].*?\[\/\1]/gs; let str = ` [b]hello![/b] [quote] [url]http://google.com[/url] [/quote] `; alert( str.match(regexp) ); // [b]hello![/b],[quote][url]http://google.com[/ur إيجاد ما بين إشارتي التنصيص "…." أنشئ تعبيرًا نمطيًا لإيجاد سلسلة نصية بين إشارتي تنصيص"….". يجب أن يدعم النص فكرة تجاوز المحارف تمامًا كما في جافا سكربت. أي يمكن أن نضع علامة التنصيص بالشكل "\ وأن نضع علامة السطر الجديد بالشكل n\ والشرطة المائلة بالشكل \\. let str = "Just like \"here\"."; وانتبه إلى أن علامة التنصيص التي يجري تجاوزها "\ لن تعتبر نهاية للسلسلة النصية. وبالتالي لا بدّ من البحث من إشارة تنصيص إلى أخرى متجاهلين علامات التنصيص التي يجري تجاوزها. أمثلة عن نصوص وتطابقاتها: .. "test me" .. .. "Say \"Hello\"!" ... (داخلها علامة تنصيص جرى تجاوزها) .. "\\" .. (داخلها شرطة مائلة مزدوجة) .. "\\ \"" .. (داخلها شرطة مائلة مزدوجة وعلامة تنصيص جرى تجاوزها) لا بد في جافاسكربت من مضاعفة الشرطات المائلة لتمريرها إلى النصوص كالتالي: let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. '; // the in-memory string alert(str); // .. "test me" .. "Say \"Hello\"!" .. "\\ \"" .. الحل: /"(\\.|[^"\\])*"/g نتبع الآتي خطوةً بخطوة: نبحث أولًا عن إشارة تنصيص البداية ". إن كان هناك شرطة عكسية \\ لا بد من مضاعفتها في التعبير لأنها محرف خاص. بعدها يمكن أن يأتي أي محرف. باستثناء الحالتين السابقتين يمكن أن نأخذ أية محارف ما عدا إشارة التنصيص (التي تعني نهاية النص) والشرطة العكسية (تستخدم الشرطة العكسية فقط مع رموز أخرى بعدها لمنع ظهورها مفردة): [^"\\] وهكذا حتى نصل إلى إشارة تنصيص نهاية النص. إليك الشيفرة: let regexp = /"(\\.|[^"\\])*"/g; let str = ' .. "test me" .. "Say \\"Hello\\"!" .. "\\\\ \\"" .. '; alert( str.match(regexp) ); // "test me","Say \"Hello\"!","\\ \"" إيجاد وسم بأكمله اكتب تعبيرًا نمطيًا لإيجاد الوسم <style> كاملًا. قد لا يضم الوسم أية صفات attributes أو عددًا منها: <style type="..." id="..."> لا يجب أن يطابق التعبير الوسم <styler> مثلًا: let regexp = /your regexp/g; alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test="..."> الحل: من المؤكد أن بداية النمط هي <style. لكن لا يمكن أن تكون بقية النمط ببساطة <?*.style> لأن وسمًا مثل <styler> سيكون نتيجة محتملة. نحتاج إلى فراغ بعد <style ثم محارف اختيارية بعده أو النهاية، وبالتالي سيكون النمط المطلوب: <style(>|\s.*?>) إليك الشيفرة: let regexp = /<style(>|\s.*?>)/g; alert( '<style> <styler> <style test="...">'.match(regexp) ); // <style>, <style test إيجاد أعداد صحيحة موجبة لدينا سلسلة نصية من الأعداد الصحيحة . أنشئ تعبيرًا نمطيًا يبحث عن الأعداد الموجبة فقط (يُسمح بالتقاط الصفر) مثال عن الاستخدام: let regexp = /your regexp/g; let str = "0 12 -5 123 -18"; alert( str.match(regexp) ); // 0, 12, 123 الحل: التعبير النمطي المناسب لإيجاد عدد صحيح هو : \d+ يمكن التخلص من الأعداد السالبة باستخدام النظر خلفًا إلى الإشارة السالبة : (?<!-)\d+ لو استخدمنا هذا النمط فقد نلاحظ ظهور نتيجة إضافية: let regexp = /(?<!-)\d+/g; let str = "0 12 -5 123 -18"; console.log( str.match(regexp) ); // 0, 12, 123, 8 كما ترى فقد وجد 8 بدلًا من -18. لحل المشكلة، لا بدّ من التحقق أن التعبير النمطي لن يبدأ المطابقة من منتصف رقم آخر غير مطابق. يمكن إنجاز الأمر بتخصيص عملية بحث أخرى للخلف عن الإشارة السالبة : (?<!-)(?<!\d)\d+ يتحقق النمط (?<!\d) أن المطابقة لن تبدأ بعد رقم آخر، بل فقط ما نحتاجه، ويمكن ضمهما أيضًا في عملية بحث للخلف واحدة: . let regexp = /(?<![-\d])\d+/g; let str = "0 12 -5 123 -18"; alert( str.match(regexp) ); // 0, 12, 123 إضافة شيفرة بعد الوسم Head لدينا نص ضمن مستند HTML. اكتب تعبيرًا نمطيًا يدخل الشيفرة <h1>Hello</h1> بعد الوسم <body> مباشرة ولا تنس أنّ هذا الوسم قد يحوي صفات داخله. إليك مثالًا: let regexp = /your regular expression/; let str = ` <html> <body style="height: 200px"> ... </body> </html> `; str = str.replace(regexp, `<h1>Hello</h1>`); يجب أن تكون قيمة str بعد ذلك : <html> <body style="height: 200px"><h1>Hello</h1> ... </body> </html> الحل: علينا أولًا إيجاد الوسم <body>وذلك باستخدام التعبير النمطي: <body.*?> لا حاجة لتعديل الوسم <body> بل فقط إضافة النص بعده. إليك ما يمكن فعله: let str = '...<body style="...">...'; str = str.replace(/<body.*?>/, '$&<h1>Hello</h1>'); alert(str); // ...<body style="..."><h1>Hello</h1>... يعني النمط في النص المُستبدل التطابق الذاتي أي الجزء من النص الأصل المرتبط بالتعبير النمطي <?*. body> حيث سيُستبدل بنفسه إضافة إلى "<h1>Hello</h1>". يقتضي الحل البديل استخدام البحث خلفًا: let str = '...<body style="...">...'; str = str.replace(/(?<=<body.*?>)/, `<h1>Hello</h1>`); alert(str); // ...<body style="..."><h1>Hello</h1>... لاحظ وجود جزء البحث خلفًا فقط في هذا التعبير النمطي حيث يعمل كالتالي: تحقق عند كل موقع في النص إن تبعه النمط <?*. body>. إن كان الأمر كذلك فقد حصلنا على التطابق. لن يُعاد الوسم <?*. body> وستكون النتيجة نصًا فارغًا يتطابق الموقع الذي يتبعه النمط <?*. body>. عندها سيُستبدل النص الفارغ بالنمط <?*. body> يليه النص <h1>Hello</h1>. يمكن الاستفادة من الرايات s و i في النمط كالتالي: /<body.*?>/si تدفع الراية s المحرف "." إلى مطابقة سطر جديد وتدفع الراية i النص <body> لمطابقة النص <BODY> (تحسس لحالة الأحرف). ترجمة -وبتصرف- للفصول Backreferences in Patten وAlternation OR وlookahead and lookbehind وsticky flag y, searching at position من سلسلة The Modern JavaScript Tutorial اقرأ أيضًا التعابير النمطية Regular Expressions في جافاسكريبت مقدمة في التعابير النمطية Regular Expressions التعابير النمطية (regexp/PCRE) في PHP
-
تُعد أسماء النطاقات جزءًا مفتاحيًا من أجزاء البنية التحتية للإنترنت، فهي التي تزوّد المستخدم بعناوين مقروءة لأي خادم ويب موجود على الإنترنت. يمكن الوصول إلى أي حاسوب متصل بالإنترنت من خلال عنوان آي بي IP Address سواء بنسخته الرابعة IPv4 (مثل 173.194.121.32) أو السادسة IPv6 (مثل 2027:0da8:8b73:0000:0000:8a2e:0370:1337 ). يمكن أن تتعامل الحواسيب مع هذه العناوين بسهولة، ولكن الأمر صعب بالنسبة للمستخدم الذي يحاول إيجاد من يدير الخادم أو ما هي الخدمات التي يقدمها موقع ويب، فعناوين آي بي صعبة التذكر ويمكن أن تتغير مع الوقت. لحل هذه الإشكالات نستخدم عناوين سهلة على المستخدمين ومقروءة تدعى أسماء النطاقات. إذًا، نهدف في هذا المقال إلى التعرف على مفهوم اسم النطاق Domain Name وكيف يعمل وأهميته في تقانة الويب. ننصحك قبل الشروع في قراءة المقال أن تطلع على مقال كيف تعمل شبكات الإنترنت؟، وأن تكون تفاصيل عنوان URL واضحة بالنسبة لك. بنية أسماء النطاقات يتكون اسم النطاق من بنية بسيطة مكونة من عدة أجزاء، ويمكن تتكون من جزء واحد فقط أو اثنين أو ثلاثة …إلخ، وتفصل بينها نقاط وتقرأ من اليمين إلى اليسار: يزودنا كل جزء من هذه الأجزاء بمعلومات محددة عن اسم النطاق عمومًا. نطاق المستوى الأعلى TLD توضح نطاقات المستويات الأعلى Top-Level Domain واختصارًا TLD الغاية من الخدمة التي يقدمها النطاق عمومًا. لا تتطلب أكثر النطاقات العليا شيوعًا (com. وorg. وnet.) أية خدمات ويب لتحقق معايير محددة، بالمقابل تطبق بعض النطاقات العليا الأخرى سياسات أكثر صرامة، وبالتالي ستكون الغاية من استخدام هذه النطاقات واضحة أكثر. يمكن أن تفرض النطاقات العليا المحلية (الخاصة بالدول) مثل us. أو fr. أو se. أن تكون الخدمات أو الموارد بلغة محددة، أو مُستاضفة في بلد محدد، بينما لا يُسمح باستخدام النطاقات العليا gov. إلّا للمنظمات الحكومية، وكذلك الأمر بالنسبة للنطاقات التي تتضمن edu، فهي مسموحة للاستخدام مع المؤسسات التعليمية والأكاديمية فقط. تتكون أسماء النطاقات العليا من 36 محرفًا كحد أقصى علمًا أن معظمها مكون من محرفين أو ثلاثة، كما يمكن أن تضم محارفًا خاصة مثل الأحرف اللاتينية. يمكنك الاطلاع على القائمة الكاملة بأسماء النطاقات العليا على موقع ICANN. العنوان (أو المكون) تأتي العناوين بعد أسماء النطاقات العليا مباشرة على شكل سلسلة من المحارف غير الحساسة لحالة الأحرف والتي يمكن أن يتراوح عددها بين محرف وحتى 63 محرفًا. تتضمن هذه المحارف الحروف الأبجدية اللاتينية من A حتى Z والأرقام من 0 حتى 9 والمحرف - الذي لا يجوز أن يتواجد في بداية أو نهاية سلسلة المحارف. يمكن أن يكون العنوان على سبيل المثال a أو 97 أو hello-strange-person-16-how-are-you. يُدعى العنوان الأقرب إلى النطاق الأعلى بنطاق المستوى الثاني Secondary Level Name واختصارًا SLD، ويمكن لاسم النطاق أن يضم عدة عناوين (أو مكونات). ليس من الضروري إطلاقًا أن يتكون اسم النطاق من ثلاثة عناوين، فاسم النطاق www.inf.ed.ac.uk هو اسم صحيح، كما أن اسم النطاق hsoub.com صحيح أيضًا. وتجدر الإشارة إلى إمكانية إنشاء نطاقات فرعية Subdomains وهي مواقع ويب لها محتوياتها الخاصة ضمن الموقع ذاته باستخدام العناوين كالتالي: Academy.hsoub.com أو accounts.hasoub.com. شراء اسم نطاق بعد أن تعرفنا على هيكلية أسماء النطاقات لنتعرف كيف يمكننا شراء نطاق معين. من يمتلك أسماء النطاقات؟ يمكن شراء أي اسم نطاق غير محجوز وفي حال كان اسم نطاق مستخدم بالفعل فلا يمكنك شراءه حتى يتخلى صاحبه عنه، بالاضافة إلى ذلك لا يمكننا شراء النطاق إلى الأبد لأن ذلك سيؤدي لامتلاء الويب بأسماء غير مستخدمة ومغلقة لا يمكن استخدامها مجددًا. بدلًا من شراء أسماء النطاقات للأبد يمكنك دفع مبلغ معين مقابل حصولك على حق استخدام اسم النطاق الذي تريده لمدة زمنية محددة (عام أو أكثر)، كما يمكنك تجديد حق الاستخدام والذي سيمنح الأولوية لطلبك على طلبات الآخرين، ولكن تذكر بأنك لا تملك اسم النطاق هذا للأبد. تستخدم بعض الشركات التي تُدعى "مُسجِّلات Registrar" سجلات أسماء النطاقات لتتبع المعلومات التقنية والإدارية التي تربطك باسم النطاق الذي تحجزه. العثور على اسم نطاق لتتأكد أن اسم النطاق الذي تختاره متاح للاستخدام أم محجوز اتبع الخطوات التالية: ادخل على موقع شركة مسجّلة فهي تقدم خدمة تدعى “whois” أو "هو إز" تخبرك عن توفر اسم نطاق معين أم لا. إن كنت تستخدم نظام تشغيل مزوّد بمفسّر أوامر Shell، افتح المفسر واكتب الأمر whois يتبعه اسم النطاق ثم اضغط الزر "Enter". إليك مثالًا عمليًا وليكن اسم النطاق الذي نريد البحث عنه هو hsoub.com سيكون الأمر والنتيجة على الشكل التالي: $ whois Hsoub.com Domain Name: HSOUB.COM Registry Domain ID: 1623716742_DOMAIN_COM-VRSN Updated Date: 2019-09-29T14:11:16Z Creation Date: 2010-11-03T21:39:38Z Registrar Registration Expiration Date: 2028-11-03T21:39:38Z Registrar: Name.com, Inc. Registrar IANA ID: 625 Reseller: Domain Status: clientTransferProhibited Registry Registrant ID: Not Available From Registry Registrant Name: Whois Agent Registrant Organization: Domain Protection Services, Inc. Registrant Street: PO Box 1769 Registrant City: Denver Registrant State/Province: CO Registrant Postal Code: 80201 Registrant Country: US Registrant Phone: +1.7208009072 Registrant Fax: +1.7209758725 Registrant Email: https://www.name.com/contact-domain-whois/hsoub.com كما ترى، لا يمكن تسجيل اسم النطاق لأن شركة "حسوب Hsoub" حجزته بالفعل. لنرى إن كان بالإمكان تسجيل اسم النطاق afunkydomainname.org: $ whois afunkydomainname.org NOT FOUND كما ترى لا وجود لاسم النطاق هذا في قاعدة بيانات whois (حتى لحظة الكتابة) وبالتالي يمكنك الاستعلام عنه وحجزه! الحصول على اسم نطاق العملية مباشرة وفق الخطوات التالي: افتح موقع ويب إحدى الشركات المسجِّلة. ستجد عادة زر أو رابط دائمًا بعنوان "احصل على اسم نطاق Get a domain name". إملأ الاستمارة التي تظهر بالمعلومات المطلوبة، وتأكد من أنك لم تخطئ كتابة اسم النطاق الذي ترغب بحجزه، فبمجرد أن تدفع لن تتمكن من التراجع! ستعلمك الشركة المسجّلة متى أصبح اسم النطاق الذي حجزته مسجّلًا وجاهزًا للاستخدام. خلال عدة ساعات ستتعرف كل خوادم أسماء النطاقات Domain Name System اختصارًا DNS على معلومات اسم النطاق الذي حجزته. تحديث خوادم أسماء النطاقات تُخزن قواعد بيانات دي إن إس في جميع خوادم أسماء النطاقات في العالم وترتبط هذه الخوادم جميعها بعدة خوادم خاصة تدعى "خوادم أسماء النطاقات الموثقة Authoritative Name Servers" أو "خوادم دي إن إس المستوى الأعلى Top-Level DNS Servers" وهذه الخوادم بمثابة المدراء للمنظومة ككل. عندما تسجل أي شركة مسجّلة اسم نطاق أو تحدث اسمًا موجودًا فلا بدّ من تحديث المعلومات في قواعد البيانات لجميع خوادم أسماء النطاقات. فعندما يعرف خادم أسماء النطاقات بوجود اسم نطاق ما فسيخزنُ معلوماته لبعض الوقت قبل أن يتأكد من سلامتها ثم يحدّثها (يستعلم خادم أسماء النطاقات من خادم أسماء النطاقات الموثوقة عن اسم النطاق ويحضر المعلومات المحدثة منه). لهذا تستغرق العملية بعض الوقت ليحصل خادم أسماء النطاقات على المعلومات المحدثة لاسم نطاق مسجل لديه (يعلم بوجوده). آلية عمل استعلام دي إن إس DNS كما رأينا سابقًا من السهل أن تكتب اسم النطاق في شريط عنوان المتصفح عندما تريد الوصول إلى صفحة ويب بدلًا من عنوان آي بي لها. لنلق نظرة على العملية: اكتب مثلًا Hsoub.com في شريط عنوان متصفحك. يسأل المتصفح حاسوبك إن كان على دراية بعنوان آي بي الذي يحدده اسم النطاق الذي كتبته (من خلال البحث في ذاكرة دي إن إس مؤقتة على الحاسوب). إن وجدت المعلومات يترجم المتصفح اسم النطاق إلى عنوان آي بي الموجود في الذاكرة المؤقتة ثم يحضر المتصفح المحتوى المطلوب من الخادم المحدد بعنوان آي بي. إن لم يتعرف حاسوبك على عنوان آي بي المرتبط باسم النطاق الذي كتبته سيتوجه المتصفح إلى خادم أسماء النطاقات لسؤاله، لأن عمله بالتحديد هو معرفة عنوان آي بي لكل اسم نطاق مسجل لديه. يعرف حاسوبك الآن عنوان آي بي المطلوب وبالتالي سيتمكن المتصفح من إحضار المحتوى المطلوب من خادم ويب الصحيح. ترجمة -وبتصرف- للمقال What is a Domain Name. اقرأ أيضًا الفرق بين صفحة الويب وموقع الويب وخادم الويب ومحرك البحث. ما هي الأدوات المستخدمة في بناء مواقع ويب؟ ما التكلفة المادية الكاملة لبناء موقع ويب؟ ما هي أدوات مطوري الويب المدمجة في المتصفحات؟
-
يُعد مفهوم الروابط التشعبية (تُدعى معظم الأحيان "روابط" فقط) مفهومًا جوهريًا ساهم في تأسيس الويب. فإن أردنا شرح هذا المفهوم لابد من العودة إلى أكثر الأساسيات بساطة في معمارية الويب. تحدث تيم بيرنرز-لي مخترع الويب عام 1989 عن الأعمدة الثلاث التي تستند إليها الويب: محدد موقع المورد URL: هو نظام عنونة يحدد مكان وجود ملفات الويب. HTTP (بروتوكول نقل النصوص التشعبية): بروتوكول نقل يستخدم لإيجاد الملفات عندما يعطي عنوانها بشكل URL. لغة HTML: وهي لغة توصيفية لإنشاء صفحات الويب وتطبيقات الويب وتسمح بتضمين الروابط التشعبية داخل الصفحات. تدل الأعمدة الثلاث أن كل ما هو موجود على الويب مرتبط بالمستندات وكيفية الوصول إليها، لأن الغرض الأساسي من الويب هو الوصول إلى الملفات النصية وقراءتها والتنقل بينها. لكن تطور الويب مع الزمن مكّن المستخدمين من الوصول إلى الصور والفيديوهات والبيانات الثنائية وبالكاد أثرت هذه التغيرات على الأعمدة الثلاث التي ترتكز عليها ويب. لم يكن سهلًا الوصول إلى المستندات والتنقل بينها قبل وجود الويب بالرغم من أن عناوين URL بشكلها المفهوم للمستخدم سهلت الأمر. إلا أنه من الصعب كتابة أو تذكر عنوان طويل كلما أردت الوصول إلى مستند، ولهذا السبب ظهرت الروابط التشعبية Hyperlinks التي أحدثت نقلة ثورية في تقانة الويب. إذ يمكن للروابط أن تقرن أي نص بعنوان URL ولن يبقى على المستخدم سوى النقر على الرابط ليصل مباشرة إلى المستند المطلوب. تُميَّز الروابط التشعبية عن النص المحيط بها بلونها الأزرق والخط الذي يُرى أسفلها. انقر أو المس الرابط حتى تنتقل إلى محتوى الرابط، أو يمكنك الضغط على الزر "TAB" إن كنت تستخدم لوحة المفاتيح حتى يُحدَّد الرابط (يصل تركيز الدخل إليه) ثم يمكنك النقر على الزر "ENTER" أو "SPACE" للولوج إلى محتوى الرابط. أحدثت الروابط التشعبية نقلة نوعية جعلت الويب أكثر فائدة ونجاحًا. سنناقش في بقية المقال الأنواع المختلفة للروابط وفائدتها في التصميم الحديث للويب. سنطلع في هذا المقال على الروابط التشعبية Hyperlinks، وسنتعرف على عملها وأهميتها في تقانة الويب. ننصحك قبل قراءة المقال بأن تطلع على مقال كيف تعمل شبكة الإنترنت؟ وأن يكون الفرق بين صفحة الويب وموقع الويب وخادم الويب ومحرك البحث واضحًا بالنسبة لك. أنواع الروابط الرابط، كما أشرنا سابقًا، هو سلسلة نصية مرتبطة بعنوان URL، يستخدم لتسهيل الانتقال من مستند إلى آخر. لكن هناك بعض الفوارق البسيطة التي لا بدّ من عرضها. رابط داخلي Internal Link: وهو رابط بين صفحتي ويب تنتميان إلى نفس الموقع. وبالطبع لن تجد موقع ويب ليس فيه هذا النوع من الروابط (ما عدا الموقع وحيد الصفحة). رابط خارجي External link: وهو رابط بين صفحة ويب على موقعك وصفحة ويب على موقع آخر. بدون هذا النوع من الروابط ليس هناك ويب على الإطلاق لأنها في الواقع شبكة من صفحات الويب المترابطة. يستخدم هذا النوع لتزويد الزائر بمعلومات إضافية إلى جانب المحتوى الذي تقدمه صفحة الويب. رابط وارد Incoming Link: وهو رابط من صفحة خارج موقعك إلى صفحة في موقعك، وهي الحالة المعاكسة للرابط الخارجي. وليس ضروريًا بالطبع ربط صفحتك بكل صفحة خارجية تضع رابطًا لموقعك. ركّز على الروابط الداخلية عندما تبني موقع ويب لأنها تسهّل استخدام الموقع، وحاول أن توازن في عدد الروابط المستخدمة، فلا تكثر من الروابط ولا تجعلها قليلة في نفس الوقت. سنتحدث لاحقًا عن تصميم آليات التنقل داخل مواقع الويب، ولكن وكقاعدة عامة يمكن اعتمادها، حيثما تنشئ صفحة ويب في موقعك لا بدّ من وجود رابط واحد على الأقل بين الصفحة الجديدة وإحدى الصفحات الموجودة مسبقًا. لكن بالمقابل ستأتي هذه القاعدة بنتائج عكسية عندما يزيد عدد الصفحات في الموقع عن العشرة. لأنه لا يمكن ربط كل الصفحات مع بعضها بعضًا. لا تعر اهتمامًا كبيرًا للروابط الداخلية والخارجية إن كنت في بداية طريقك في تصميم المواقع، لكن سترى أن وجودها لاحقًا هام جدًا إن أردت من محركات البحث العثور على صفحات موقعك. المرابط تربط معظم الروابط بين صفحتي ويب، بينما تربط المرابط Anchors (وتدعى مراسي أيضًا) بين قسمين من مستند واحد. عندما تفتح مربطًا ما سينتقل المتصفح إلى جزء آخر من المستند الحالي بدلًا من تحميل مستند جديد أي سيُشير إلى مكان معين ضمن نفس المستند. ولا تختلف طريقة استخدام المربط عن الروابط الأخرى. الروابط ومحركات البحث للروابط أهمية كبيرة للمستخدم ولمحركات البحث، فكلما وصل محرك بحث إلى صفحة جديدة سيفهرس الموقع الذي يحتويها بالتنقل عبر الروابط الموجودة ضمن هذه الصفحة. لا تلاحق محركات البحث الروابط لتكتشف فقط الصفحات المختلفة للموقع، بل لتستخدم العبارات النصية للرابط في تحديد معايير البحث الملائمة للوصول إلى هذه الصفحة. تؤثر الروابط على سهولة الوصول إلى صفحتك عبر محركات البحث، لكن المشكلة تكمن في صعوبة تحديد ما يفعله محرك البحث (لأن معظم محركات البحث تخفي طريقة تصنيفها الدقيقة لكي لا يساء استخدامها للاحتيال على هذه المحرك). تحاول معظم الشركات أن تتصدر مواقعها نتائج محركات البحث، وسنطلعك على بعض ما نعرفه عن الآلية تقييم محرك البحث لموقع الويب: يؤثر النص المرئي للرابط على معايير البحث التي تتطابق مع عنوان URL. يرفع ترتيب ظهور موقعك في نتائج محركات البحث عند وجود عدد كبير من الروابط الواردة إلى موقعك. تؤثر الروابط الخارجية على ترتيب كلٍ من الصفحتين المصدر والهدف، ولكن لا نعرف تحديدًا مقدار هذا التأثير. قد ترغب الآن بإعداد بعض صفحات الويب وتزويدها بالروابط، لذلك لا بد من زيادة خلفيتك المعرفية النظرية بالروابط أولًا، ولذا ننصحك بمعرفة ما هي عناوين URL. ترجمة -وبتصرف- للمقال What are hyperlinks اقرأ أيضًا تطبيقات الشبكات الحاسوبية: شبكة الويب العالمية تطبيقات الشبكات الحاسوبية: خدمات الويب ما هو خادم الويب؟ ما هي الأدوات المستخدمة في بناء مواقع ويب؟
-
يأتي مجال الويب كغيره من المجالات المعرفية بالكثير من المصطلحات التي لن يسعنا الحديث عنها جميعًا في مقالنا، لكن لا بدّ من معرفة بعض المصطلحات الأساسية في نهاية الأمر، لأنك ستصادفها دائمًا عندما تكمل قراءة هذه السلسلة. ويمكن أن تختلط عليك هذه المصطلحات أحيانًا لأنها تشير إلى نواح وظيفية مترابطة ولكن ستتوضح لك الفكرة أكثر وأكثر مع قراءتك للمقالات التي نشير إليها. كما أن الاستخدام الخاطئ لهذه المصطلحات في نشرات الأخبار يجعلها مبهمة أكثر وغير مفهومة. سنشرح لاحقًا هذه المصطلحات والتقانات بتفاصيل أكثر مع تقدمنا في السلسلة، وستكون التعريفات التالية بداية جيدة للانطلاق: صفحة ويب Web Page: هي مستند يمكن استعراض محتوياته من خلال المتصفحات مثل فايرفوكس وغوغل كروم وأوبِرا ومايكروسوفت إكسبلورير وإيدج أو متصفح سفاري من آبل. تدعى هذه المستندات أحيانًا "صفحات" فقط. موقع ويب Website: وهو مجموعة من صفحات الويب تُجمَّع مع بعضها، وترتبط عادة بطرق مختلفة. تدعى أحيانًا "موقع ويب" وأحيانا "موقع" فقط. خادم ويب Web Server: وهو حاسوب يستضيف موقع ويب على الإنترنت. محرك البحث Search Engine: وهو خدمة ويب تساعدك في العثور على صفحات الويب الأخرى. نذكر من هذه المحركات غوغل Google وبينغ Bing وياهو Yahoo ودك-دك-غو DuckDuckGo. تُستخدم هذه المحركات عادةً مباشرة عبر متصفحات الويب (أي بكتابة ما تريد إيجاده مباشرة في شريط عنوان المتصفح في فايرفوكس أو كروم أو غيره وسيبحث مباشرة في أحد محركات البحث)، كما يمكن الدخول إلى صفحة الويب الخاصة بمحرك البحث ثم كتابة العبارة المراد البحث عنها. لتوضيح مفهوم خادم الويب سنأخذ مثالًا بسيطًا وهو المكتبة فما تفعله عادة عند زيارة المكتبة يتلخص بالتالي: إيجاد فهرس لأسماء الكتب الموجودة في المكتبة والبحث فيها عن عنوان الكتاب المطلوب. تسجيل رقم التصنيف الخاص بالكتاب. الانتقال إلى القسم الذي يضم الكتاب والبحث عن رقم التصنيف الذي دوّنته ثم إحضار الكتاب. لنوازن الآن بين المكتبة وخادم الويب: تتشابه المكتبة مع خادم الويب، إذ تتضمن عدة أقسام يمكن تشبيهها بمواقع الويب المتعددة التي يستضيفها الخادم. تتشابه الأقسام المختلفة للمكتبة (علوم، رياضيات، تاريخ،..) مع مواقع الويب. وسيكون كل قسم من أقسام المكتبة بمثابة موقع ويب فريد (فلا يمكن أن يضم قسمان مختلفان الكتب نفسها). تتشابه الكتب وفق هذا المنظور مع صفحات الويب، إذ يضم كل موقع ويب عدة صفحات ويب، سيضم قسم العلوم مثلًا (الذي يمثل موقع الويب) كتبًا عن الحرارة والصوت الترموديناميك والإحصاء وغيرها (والتي تمثل صفحات ويب)، يمكن الوصول إلى أي صفحة ويب من خلال عنوان فريد URL. يتشابه فهرس أسماء الكتب مع محرك البحث، لكل كتاب موقعه الخاص في المكتبة، فلا يمكن وضع كتابين متطابقين في المكان ذاته والذي يحدده رقم التصنيف الخاص بالكتاب. سنشرح في هذا المقال بعض المفاهيم المتعلقة بالويب مثل: صفحات الويب ومواقع الويب وخوادم الويب ومحركات البحث لأنها يمكن أن تُربك المستخدمين الجدد للويب أو يمكن أن يستخدموها بطريقة غير صحيحة، وسنتحدث عن معنى كلٍّ منها. وبالتالي سيصبح القارئ على دراية تامة بالفوارق بين تلك المصطلحات. ننصحك بالاطلاع على مقال كيف تعمل شبكة الإنترنت؟ قبل إكمال قراءة المقال. إذًا، سنبحث بشيء من التفصيل عن الترابط بين المصطلحات السابقة وعن الالتباسات التي تحدث عند استخدامها. دورة تطوير واجهات المستخدم ابدأ عملك الحر بتطوير واجهات المواقع والمتاجر الإلكترونية فور انتهائك من الدورة اشترك الآن صفحة الويب صفحة الويب هي مستند يمكن استعراضه من خلال المتصفح. تكتب هذه المستندات بلغة HTML (سنتحدث عنها بتفاصيل أكثر لاحقًا. يمكن أن تتضمن صفحات الويب أنواعًا مختلفة من الموارد مثل: معلومات عن تنسيق الصفحة Style Information: وتتحكم بمظهر الصفحة وطريقة عرضها. سكربتات Scripts: تجعل الصفحة أكثر تفاعلًا مع مستخدمها. وسائط Media: وهي الصور أو الأصوات أو الفيديوهات. يمكن الوصول إلى أي صفحة ويب موجودة على الويب من خلال عنوان فريد يمكن كتابته في شريط عنوان المتصفح: موقع الويب موقع الويب هو مجموعة من صفحات الويب المترابطة (بالإضافة إلى الموارد التي تستخدمها) التي تشترك باسم نطاق Domain name فريد. يمكن الوصول إلى أي صفحة ضمن موقع ما من خلال روابط محددة عادة ما تكون جزء قابل للنقر من نص يسمح للمستخدم عند نقره بالانتقال من صفحة إلى أخرى ضمن الموقع. للوصول إلى موقع معين، ما عليك سوى كتابة اسم النطاق في شريط عنوان المتصفح الذي سيقودك مباشرة إلى الصفحة الرئيسية للموقع Homepage ويشار إليها أحيانًا بالرئيسية Home. يمكن الخلط بين مفهومي "صفحة ويب" و"موقع ويب" على وجه الخصوص في المواقع المكوّنة من صفحة واحدة والتي تُدعى عادة بالمواقع وحيدة الصفحة Single-Page Website ويرمز لها اختصارًا SPW. احصل على موقع إلكتروني مخصص لأعمالك أبهر زوارك بموقع احترافي ومميز بالاستعانة بأفضل خدمات تطوير وتحسين المواقع على خمسات أنشئ موقعك الآن خادم الويب خادم الويب هو حاسوب يستضيف موقع ويب أو أكثر. تعني كلمة "استضافة Hosting" أنّ جميع صفحات الويب والملفات التي تدعمها متاحة على هذا الحاسوب. عندما يطلب المستخدم صفحة ويب معينة يرسل خادم الويب هذه الصفحة من موقع ويب إلى متصفح المستخدم. لا تخلط بين مواقع الويب وخوادم الويب، فعندما تسمع مثلًا عبارة "موقع الويب الخاص بي لا يستجيب"، فهي تعني في الواقع أن خادم الويب الذي يستضيف الصفحة لا يستجيب وبالتالي لن يكون الموقع متاحًا. والناحية الأهم في معرض حديثنا أنّ خادم الويب لا يتحدد بموقع ويب، الأمر الذي يُحدثُ اختلاطًا في المفاهيم. فلو قلنا في مثالنا السابق أنّ "خادم الويب الخاص بي لا يستجيب"، يعني ذلك أن عدة مواقع ويب على ذلك الخادم لا تستجيب لأن غالبية المخدمات تستضيف عدة مواقع. محرك البحث تُسبب محركات البحث إرباكًا من ناحية الاصطلاح في عالم الويب، فهي نوع خاص من مواقع الويب، تساعد المستخدمين في العثور على صفحات ويب موجودة في مواقع ويب أخرى. ستجد على الويب العديد من محركات البحث مثل غوغل Google وبينغ Bing وياهو Yahoo ودك-دك-غو DuckDuckGo وغيرها. بعضها محركات بحث عامة والأخرى متخصصة بمواضيع محددة، لذلك فالخيار يعود لك في استخدام ما يناسبك. يخلط العديد من المستخدمين الجدد للويب بين محركات البحث والمتصفحات، لذلك سنوضح الأمر قليلًا. المتصفح Browser هو برنامج يحضر صفحة الويب ويعرضها، بينما يُعد محرك البحث موقع ويب يساعد المستخدمين في إيجاد صفحات ويب ضمن مواقع أخرى، ويأتي مصدر اختلاط المفهومين عندما يفتح مستخدم جديد متصفحًا لأول مرة، سيعرضُ المتصفح غالبًا محرك بحث في الصفحة الرئيسية للمتصفح، وهذا أمر منطقي لأن أول ما تفكر به عند استخدام المتصفح هو البحث عن صفحات ويب معينة وعرضها، لذلك لا تخلط بين البنية التحتية (التي يمثلها المتصفح) والخدمة (التي يمثلها محرك البحث). سيكون التمييز بين المفهومين مفيدًا لك، ولكن لا تقلق فبعض المحترفين أيضًا يرتكبون هفوات كهذه عندما يتحدثون في هذه المواضيع. تعرض الصورة التالية متصفح فايرفوكس وضمنه صندوق محرك البحث غوغل كصفحة بداية افتراضية: ترجمة -وبتصرف- للمقال What is the difference between webpage, website, web server, and search engine اقرأ أيضًا دورة تطوير واجهة المستخدم ما هو عنوان URL في الويب؟ ما هو خادم الويب؟ مفهوم الروابط التشعبية في مواقع الويب.
- 1 تعليق
-
- 2
-
المجموعة الملتقطة capturing group هي الجزء الذي يضم محارف بين قوسين (...) في أي تعبيير نمطي RegEx، ولها تأثيران اثنان: تسمح بالحصول على جزء من التطابق مثل عنصر مستقل ضمن مصفوفة النتائج. يُطبق المحدد الكمي quantifier على مجموعة الالتقاط كلها إذا وضع بعد القوسين مباشرةً. أمثلة عن مطابقة عدة مجموعات لنتعرف كيفية عمل الأقواس من خلال الأمثلة. مثال gogogo يفيد النمط +go دون أقواس في إيجاد المحرف g يليه المحرف o مكررًا مرةً أو أكثر، مثل goooo أو goooooo، وإذا وضعنا محارف النمط السابق بين قوسين +(go)، فسيعني ذلك go أو gogo أو gogogo وهكذا. alert( 'Gogogo now!'.match(/(go)+/ig) ); // "Gogogo" مثال نطاق موقع ويب نحتاج إلى تعبير نمطي للبحث عن نطاق موقع ويب، مثل النطاقات التالية: mail.com users.mail.com smith.users.mail.com يتألف النطاق من كلمات متتالية تفصل بينها نقاط، ويقابل ذلك التعبير النمطي +w+\.)+\w\): let regexp = /(\w+\.)+\w+/g; alert( "site.com my.site.com".match(regexp) ); // site.com,my.site.com سيعمل النمط السابق، لكنه سيواجه مشكلةً عندما تحتوي الكلمات على شرطة قصيرة -، مثل my-site.com، فلا يعتبر هذا المحرف من محارف الصنف w\، وسنحل هذه المشكلة باستبدال التعبير w\ بالمجموعة [-w\]، في كل كلمة عدا الأخيرة، وسيصبح النمط بالشكل +w-]+\.)+\w\]). مثال البريد الإلكتروني يمكن توسيع المثال السابق لإنشاء تعبير نمطي لبريد إلكتروني اعتمادًا على النمط السابق، وما دام للبريد الإلكتروني الشكل name@domain، فيمكن أن تكون أي كلمة هي الاسم، حيث يُسمح ضمنها بالشرطة القصيرة أو النقاط، وبلغة التعبير النمطي ستكون [.-w\]، وسيكون التعبير النمطي للبريد الإلكتروني /+[-w.-]+@([\w-]+\.)+[\w\]/ let regexp = /[-.\w]+@([\w-]+\.)+[\w-]+/g; alert("my@mail.com @ his@site.com.uk".match(regexp)); // my@mail.com, his@site.com.uk ليس هذا التعبير مثاليًا، لكنه سيعمل في معظم الأحيان، وسيساعدك في التخلص من الأخطاء الكتابية، وتأكد أن الطريقة الحقيقية الوحيدة للتحقق من بريد هو استلامه للرسالة التي أرسلتها! المحتوى الموجود بين قوسين عند البحث عن تطابق تُرقّم الأقواس من اليسار إلى اليمين، وسيتذكر المحرك المحتوى الذي يتطابق مع كل قوس، ويسمح بالحصول على هذه التطابقات ضمن النتيجة، ويبحث التابع (str.match(regexp عن التطابق الأول، ويعيد النتيجة في مصفوفة عندما لا تُستخدم الراية g ضمن التعبير regexp، حيث ستجد ضمن المصفوفة: التطابق بشكله الكامل في الموقع 0. محتوى القوس الأول في الموقع 1. محتوى القوس الثاني في الموقع 2. وهكذا… فلو أردنا مثلًا البحث عن وسوم HTML من النمط <?*.>، ثم معالجة النتائج، فمن المناسب أن نحصل على محتوى كل وسم ضمن متغير خاص به، وعندما نغلّف المحتوى الداخلي للوسم ضمن قوسين، بالشكل التالي <(?*.)>، فسنحصل على الوسم كاملًا، وليكن <h1>، وعلى محتوى هذا الوسم (أي النص h1) ضمن النتيجة: let str = '<h1>Hello, world!</h1>'; let tag = str.match(/<(.*?)>/); alert( tag[0] ); // <h1> alert( tag[1] ); // h1 المجموعات المتداخلة يمكن أن تتداخل الأقواس، وعندها ستُرقَّم أيضًا من اليسار إلى اليمين، فعندما نبحث عن وسم ضمن الوسم <span class="my"> مثلًا، فلربما نريد الحصول على: محتوى الوسم كاملًا "span class="my. اسم الوسم: span. سمات الوسم: "class="my. لنضف الأقواس إلى النمط <(([a-z]+)\s*([^>]*))>، لاحظ كيف تُرقَّم الأقواس من اليسار إلى اليمين: let str = '<span class="my">'; let regexp = /<(([a-z]+)\s*([^>]*))>/; let result = str.match(regexp); alert(result[0]); // <span class="my"> alert(result[1]); // span class="my" alert(result[2]); // span alert(result[3]); // class="my" سنجد دائمًا التطابق الكامل في الموقع صفر من المصفوفة، ثم المجموعات مرقمةً من اليسار إلى اليمين بواسطة القوس المفتوح، حيث تُعاد المجموعة الأولى في الموقع الأول [result[1، تليها الثانية الناتجة عن القوس المفتوح الثاني (+[a-z]) ضمن [result[2، ثم نتيجة التطابق مع النمط ([^>]*) ضمن [result[3، وستكون نتيجة كل مجموعة بصيغة نص. المجموعات الاختيارية حتى لو كانت المجموعة اختياريةً وغير موجودة ضمن التطابق، كأن يكون لها المحدد الكمي ?(...)، فسيبقى مكانها محجوزًا ضمن المصفوفة، وقيمته هي undefined، فلو تأملنا مثلًا التعبير ?(a(z)?(c، فسنجد أنه يبحث عن "a" متبوعًا -اختياريًا- بالحرف "z"، ومتبوعًا -اختياريًا أيضًا- بالحرف "c"، لأن المحدد الكمي ? يعني محرفًا أو لا شيء، فلو طبّقنا التعبير السابق على نص مكون من الحرف a فقط، فستكون النتيجة: let match = 'a'.match(/a(z)?(c)?/); alert( match.length ); // 3 alert( match[0] ); // a (whole match) alert( match[1] ); // undefined alert( match[2] ); // undefined سيكون طول المصفوفة 3 علمًا أن كل المجموعات فارغة! لكن لو كان النص هو ac: let match = 'ac'.match(/a(z)?(c)?/) alert( match.length ); // 3 alert( match[0] ); // ac (whole match) alert( match[1] ); // undefined, because there's nothing for (z)? alert( match[2] ); // c سيبقى طول المصفوفة 3، لكنك لن تجد تطابقًا يقابل المجموعة (z)?، وستكون النتيجة ["ac", undefined, "c"]. البحث عن كل التطابقات ضمن المجموعات: التابع matchAll لن يعيد التابع match محتوى المجموعات إذا استخدم للبحث بوجود الراية g، والتي تعني إيجاد كل التطابقات، وسنحاول في المثال التالي إيجاد كل الوسوم في النص: let str = '<h1> <h2>'; let tags = str.match(/<(.*?)>/g); alert( tags ); // <h1>,<h2> لاحظ أن النتيجة هي مصفوفة تحتوي على التطابقات كاملةً لكن دون تفاصيل، أي دون محتوى كل تطابق، لكننا نحتاج عمليًا إلى ذلك المحتوى، وسيساعدنا البحث باستخدام التابع (str.matchAll(regexp على استخلاص ذلك المحتوى، فقد أضيف هذا التابع إلى JavaScript بعد فترة طويلة من إضافة match مثل نسخة جديدة ومحسنة منه. يشابه matchAll التابع match، مع وجود ثلاثة اختلافات، وهي: لا يعيد مصفوفةً، بل كائنًا قابلًا للتكرار iterable object. يعيد كل تطابق مثل مصفوفة تحتوي مجموعات عند استخدام الراية g. عندما لا يجد تطابقات فلن يعيد null، بل كائنًا فارغًا قابلًا للتكرار. إليك مثالًا: let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi); // النتائج ليست مصفوفة بل كائن قابل للتعداد alert(results); // [object RegExp String Iterator] alert(results[0]); // undefined (*) results = Array.from(results); // تحويل النتيجة إلى مصفوفة عادية alert(results[0]); // <h1>,h1 (1st tag) alert(results[1]); // <h2>,h2 (2nd tag) إنّ الاختلاف الأول مهم جدًا كما يوضّحه السطر "(*)"، فلا يمكن الحصول على التطابق في الموقع [results[0، لأن الكائن لا يمثل مصفوفةً زائفةً pseudoarray، ويمكننا تحويلها إلى مصفوفة حقيقية باستخدام Array.from، وستجد العديد من التفاصيل عن المصفوفات الزائفة والكائنات القابلة للتكرار في المقال Iterables. لا حاجة لتحويل المصفوفة باستخدام Array.from إذا كنا سنشكل حلقةً من النتائج: let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi); for(let result of results) { alert(result); // first alert: <h1>,h1 // second: <h2>,h2 } أو عند استخدام التفكيك destructuring: let [tag1, tag2] = '<h1> <h2>'.matchAll(/<(.*?)>/gi); يشابه تنسيق كل تطابق يعيده التابع matchAll التنسيق الذي يعيده match دون الراية g، وهذا التنسيق هو مصفوفة مع خصائص إضافية index التي تطابق الفهرس في النص، وinput الذي يعني النص الأصلي: let results = '<h1> <h2>'.matchAll(/<(.*?)>/gi); let [tag1, tag2] = results; alert( tag1[0] ); // <h1> alert( tag1[1] ); // h1 alert( tag1.index ); // 0 alert( tag1.input ); // <h1> <h2> انتبه، لماذا ستكون نتيجة التابع كائنًا قابلًا للتكرار وليس مصفوفةً؟ السبب بسيط وهو التحسين، فلن يُنفِّذ الاستدعاء عملية البحث، بل سيعيد كائنًا قابلًا للتكرار لا يحتوي على النتيجة مبدئيًا، ويُنفَّذ البحث في كل مرة نكرره (ضمن حلقة مثلًا)، وبالتالي سيجد العدد المطلوب من النتائج تمامًا، فإذا كان من المحتمل مثلًا وجود 100 تطابق، لكننا وجدنا في حلقة for..of خمسةً فقط، وقررنا أن هذا كاف وأوقفنا الحلقة، فلن يستهلك المحرك وقتًا إضافيًا في إيجاد التطابقات 95 الباقية. المجموعات المسماة يصعب تذكر المجموعات بأرقامها، على الرغم من بساطته في الأنماط البسيطة، لكن عند البحث عن أنماط أكثر تعقيدًا فلن يكون ترقيم الأقواس أمرًا مناسبًا، وسنجد أن خيار تسمية الأقواس هو الأفضل، ونسمي الأقواس بوضع الاسم بالشكل التالي <name>? مباشرةً بعد القوس، ولنبحث مثلًا عن تاريخ وفق التنسيق "يوم-شهر-سنة": let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/; let str = "2019-04-30"; let groups = str.match(dateRegexp).groups; alert(groups.year); // 2019 alert(groups.month); // 04 alert(groups.day); // 30 حيث سنجد المجموعات عبر الخاصية groups. لكل تطابق، ويمكن إيجاد جميع التواريخ باستخدام الراية g، كما ينبغي استخدام التابع matchAll للحصول على التطابق كاملًا بالإضافة إلى المجموعات: let dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g; let str = "2019-10-30 2020-01-01"; let results = str.matchAll(dateRegexp); for(let result of results) { let {year, month, day} = result.groups; alert(`${day}.${month}.${year}`); // first alert: 30.10.2019 // second: 01.01.2020 } مطابقة مجموعات ثم تنفيذ عملية استبدال يسمح التابع (str.replace(regexp, replacement الذي يستبدل محتوى الأقواس ضمن النص replacement، بكل التطابقات regexp التي يجدها في النص str، وينفذ ذلك باستخدام الرمز n$، حيث n هو رقم المجموعة، وإليك مثالًا: let str = "John Bull"; let regexp = /(\w+) (\w+)/; alert( str.replace(regexp, '$2, $1') ); // Bull, John ستتغير العملية باستخدام <name>$ في الأقواس المسماة، ولتغيير تنسيق التاريخ مثلًا من "يوم -شهر-سنة" إلى "سنة.شهر.يوم": let regexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/g; let str = "2019-10-30, 2020-01-01"; alert( str.replace(regexp, '$<day>.$<month>.$<year>') ); // 30.10.2019, 01.01.2020 استثناء التقاط المجموعات باستخدام :? نحتاج أحيانًا إلى الأقواس لتطبيق المحددات الكمية بطريقة صحيحة، لكننا لا نحتاج إلى محتواها ضمن النتائج، لذلك يمكن استثناء المجموعات باستخدام :? في البداية، فإذا أردنا مثلًا إيجاد النمط +(go)، لكننا لا نحتاج إلى محتوى الأقواس (go) في عنصر مصفوفة مستقل، فيمكننا صياغة النمط بالشكل `+(go?:)، وإليك مثالًا: let str = "Gogogo John!"; // ?: exludes 'go' from capturing let regexp = /(?:go)+ (\w+)/i; let result = str.match(regexp); alert( result[0] ); // Gogogo John (تطابق كامل) alert( result[1] ); // John alert( result.length ); // 2 (لا مزيد من العناصر ضمن المصفوفة) الخلاصة تُجمِّع الأقواس أجزاءً من التعبير النمطي ليُطبَّق المحدد الكمي عليها مثل زمرة واحدة. تُرقَّم أقواس المجموعات من اليسار إلى اليمين، كما يمُكن أن تُسمّى اختياريًا باستخدام النمط (...<name>?). يمكن الحصول على المحتوى الموجود داخل الأقواس -الذي يحقق التطابق- بصورة مستقلة ضمن النتيجة، حيث: يعيد التابع str.match المجموعة الملتقطة عند استخدام الراية فقط. يعيد التابع str.matchAll المجموعات الملتقطة دومًا. إذا لم تُسمَّ الأقواس فسنحصل على محتوياتها ضمن المصفوفة وفقًا لتسلسل ترقيمها، كما يمكن الحصول على محتويات الأقواس المسماة من خلال الخاصية groups.. يمكن استخدام محتويات الأقواس في النص البديل للتابع، إما عبر أرقامها من خلال n$، أو أسمائها من خلال <name>$. يمكن استثناء مجموعة من الترقيم باستخدام النمط :? قبلها، وذلك عندما نريد تطبيق مُحصٍ quantifier على كامل المجموعة، لكننا لا نريد أن تظهر محتويات المجموعة -الموجودة بين قوسين- في عنصر مستقل ضمن مصفوفة النتيجة، كما لا يمكن الإشارة إلى هذه الأقواس عند استخدام تابع الاستبدال. مهام لإنجازها تحقق من عنوان MAC يتكون عنوان MAC لواجهة اتصال مع الشبكات من 6 أرقام ست عشرية ذات خانتين تفصل بينها نقطتان ":"، مثل العنوان التالي '01:32:54:67:89:AB'، اكتب تعبيرًا نظاميًا يتحقق من أن النص هو عنوان MAC. let regexp = /your regexp/; alert( regexp.test('01:32:54:67:89:AB') ); // true alert( regexp.test('0132546789AB') ); // false (no colons) alert( regexp.test('01:32:54:67:89') ); // false (5 numbers, must be 6) alert( regexp.test('01:32:54:67:89:ZZ') ) // false (ZZ at the end) الحل: يُعطى التعبير النمطي الذي يبحث عن عدد ست عشري من خانتين بالشكل: {2}[0-9a-f] مفترضين استخدام الراية i. سنحتاج الآن إلى هذا النمط وخمسة أنماط أخرى مشابهة له، وبالتالي سيكون التعبير النمطي على الشكل: [0-9a-f]{2}(:[0-9a-f]{2}){5} ولكي نجبر التعبير على التقاط كامل النص الموافق، لابد من وضع محرفي ارتكاز البداية والنهاية $...^ إليك شيفرة الحل بشكلها الكامل: let regexp = /^[0-9a-f]{2}(:[0-9a-f]{2}){5}$/i; alert( regexp.test('01:32:54:67:89:AB') ); // true alert( regexp.test('0132546789AB') ); // false (no colons) alert( regexp.test('01:32:54:67:89') ); // false (5 numbers, need 6) alert( regexp.test('01:32:54:67:89:ZZ') ) // false (ZZ in the end) أوجد الألوان التي تنسّق بالشكل abc# أو abcdef اكتب تعبيرًا نمطيًا يبحث عن الألوان المكتوبة وفق أحد التنسيقين abc# أو abcdef#، أي المحرف # يليه ثلاث أو ست أرقام ست عشرية. let regexp = /your regexp/g; let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; alert( str.match(regexp) ); // #3f3 #AA00ef لاحظ أنه لا ينبغي الحصول على تطابقات تحوي أربع أو خمس أرقام ست عشرية، بل 3 أو 6 فقط. الحل: سيكون التعبير النمطي المناسب للبحث عن شيفرة لون ثلاثية الأرقام كالتالي: /#[a-f0-9]{3}/i يمكننا أيضًا إضافة ثلاث أرقام ست عشرية أخرى بالضبط فلن نحتاج أكثر أو أقل، لكون الشيفرة اللونية مزيج من ثلاث أو ستة أرقام. لنستخدم إذًا المكمم {1,2} بعد وضع الصيغة السابقة للتعبير النمطي بين قوسين: /#([a-f0-9]{3}){1,2}/i لاحظ الشيفرة: let regexp = /#([a-f0-9]{3}){1,2}/gi; let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; alert( str.match(regexp) ); // #3f3 #AA00ef #abc تواجهنا مشكلة صغيرة هنا، فسيجد التعبير النمطي المحارف abc# ضمن abcd#. يمكن وضع النمط b\ في النهاية لحل المشكلة: let regexp = /#([a-f0-9]{3}){1,2}\b/gi; let str = "color: #3f3; background-color: #AA00ef; and: #abcd"; alert( str.match(regexp) ); // #3f3 #AA00ef أوجد كل الأرقام اكتب تعبيرًا نمطيًا يبحث عن كل الأعداد العشرية، بما فيها الصحيحة والعشرية ذات الفاصلة العائمة أو السالبة. let regexp = /your regexp/g; let str = "-1.5 0 2 -123.4."; alert( str.match(regexp) ); // -1.5, 0, 2, -123.4 الحل: يُعطى نمط العدد العشري الموجب بوجود القسم العشري منه على الشكل: \d+(\.\d+)? لنضع الإشارة السالبة - لتكون اختيارية في بداية النمط: let regexp = /-?\d+(\.\d+)?/g; let str = "-1.5 0 2 -123.4."; alert( str.match(regexp) ); // -1.5, 0, 2, -123.4 فسر العبارات الرياضية تتكون العملية الحسابية من عددين بينهما إشارة عمليات، مثل: 1 + 2 1.2 * 3.4 -3 / -6 -2 - 2 قد تكون العملية "+"أو "-" أو "*" أو "/"، وقد توجد مساحات فارغة قبل أو بعد أو بين الأجزاء. أنشئ تابعًا (parse(expr يقبل العبارة معاملًا، ويعيد مصفوفةً من ثلاثة عناصر، وهي: العدد الأول. العامل الرياضي (إشارة العملية). العدد الثاني. let [a, op, b] = parse("1.2 * 3.4"); alert(a); // 1.2 alert(op); // * alert(b); // 3.4 الحل: يُعطى التعبير النمطي لإيجاد عدد كما رأينا في المهمة السابقة كالتالي: -?\d+(\.\d+)? تُعطى العملية الرياضية وفق النمط التالي [/*+-] ولابد من وضع المحرف - في بداية الأقواس المربعة لأنها ستعنى مجالًا من المحارف إن وضعت في المنتصف ونحن نريد فقط المحرف - بحد ذاته. لايد أيضًا من تجاوز المحرف /في التعبير النمطي كالتالي /.../ وهذا ما سنفعله لاحقًا. نحتاج إلى عدد ثم عملية ثم عدد آخر وقد تكون هناك مساحات فارغة اختيارية بينهم وبالتالي سيكون التعبير النمطي كاملًا: -?\d+(\.\d+)?\s*[-+*/]\s*-?\d+(\.\d+)? يتكون التعبير النمطي من ثلاثة أقسام يفصل بينها s*\: /-?\d+(\.\d+)?/ // العدد الأول /[-+*/],/ // رمز العملية /-?\d+(\.\d+)?/ // العدد الثاني يمثل القسم الأول العدد الأول والقسم الثاني العملية الحسابية والثالث العدد الثاني، ولكي يظهر كل قسم كنتيجة مستقلة ضمن مصفوفة النتيجة، سنضع كل قسم ضمن قوسين: /(-?\d+(\.\d+)?)\s*([-+*/])\s*(-?\d+(\.\d+)?)/ إليك الشيفرة: let regexp = /(-?\d+(\.\d+)?)\s*([-+*\/])\s*(-?\d+(\.\d+)?)/; alert( "1.2 + 12".match(regexp) ); تتضمن النتائج: التطابق كاملًا : "result[0] == "1.2 + 12 المجموعة الأولى (-?\d+(\.\d+)?) وتمثل العدد الأول مع أجزائه العشرية: "1.2" المجموعة الثانية (\.\d+)? وتمثل الجزء العشري الأول: ".2" المجموعة الثالثة ([-+*\/]) وتمثل العملية الحسابية: "+" المجموعة الرابعة (-?\d+(\.\d+)?) وتمثل العدد الثاني: "12" المجموعة الخامسة (\.\d+)? والتي تمثل الجزء العشري من العدد الثاني وهو في الواقع غير موجود undefined نحتاج في الواقع إلى العددين والعملية الحسابية دون الأجزاء العشرية لذلك سنجعل التعبير أكثر وضوحًا ليلائم ما نريده. سنزيل العنصر الأول من المصفوفة والذي يمثل التطابق الكامل بإجراء انزياح لمصفوفة النتيجة array.shift. يمكن التخلص من الأجزاء العشرية (.\d+) في المجموعة الثانية والرابعة (أي النقط 3 و 4) من المصفوفة بوضع المحرف ? في بداية كل مجموعة. إليك الحل النهائي: function parse(expr) { let regexp = /(-?\d+(?:\.\d+)?)\s*([-+*\/])\s*(-?\d+(?:\.\d+)?)/; let result = expr.match(regexp); if (!result) return []; result.shift(); return result; } alert( parse("-1.23 * 3.45") ); // -1.23, *, 3.45 ترجمة -وبتصرف- للفصل Capturing Groups من سلسلة The Modern JavaScript Tutorial. اقرأ أيضًا المقال السابق: المحددات الكمية وأنماط استخدامها في التعابير النمطية مقدمة في التعابير النمطية Regular Expressions التعبيرات النمطية Regular expressions في Cpp التعابير النمطية في البرمجة
-
تُعد شبكة الإنترنت العمود الفقري للويب والبنية التحتية التقنية التي جعلت الويب واقعًا معاشًا. ببساطة شديدة يمكن أن نعرّف الإنترنت بأنها شبكة ضخمة من الحواسيب التي تتواصل مع بعضها. يلف الغموض نوعًا ما تاريخ الإنترنت نظرًا لبدايته كمشروع بحثي مولَه الجيش الأمريكي في ستينات القرن الماضي، ثم تحوّل إلى بنية تحتية عمومية في ثمانينات القرن ذاته بدعم من عدة جامعات حكومية وشركات خاصة. تطورت مع الوقت تقنيات متنوعة تدعم الإنترنت، لكن آلية عملها لم تتغير كثيرًا: فالإنترنت وسيلة لربط الحواسيب مع بعضها بعضًا وتضمن بقاء هذه الحواسب متصلة ببعضها مهما حدث. سنناقش في هذا المقال ماهية الإنترنت وآلية عمله. لفهم مبادئ البنية التحتية التقنية للويب ولمعرفة الفرق الدقيق بينها وبين الإنترنت. الشبكة البسيطة عندما تريد من حاسوبين التواصل، لابد من وصلهما سلكيًا عبر كابل شبكة (أو كما يُعرف باسم كابل إيثرنت) أو لاسلكيًا عبر أنظمة اتصالات لاسلكية مثل تقنية الواي-فاي أو البلوتوث. وستجد أن معظم الحواسب الحديثة تدعم كل وسائل الاتصال التي ذكرناها. يمكن لهذه الشبكة أن تربط حاسوبين مع أي عدد تريده من الحواسب، لكن الوضع سيتعقد بسرعة. فلو حاولت أن تربط 10 حواسب مثلًا ستحتاج إلى 45 كابلًا وتسعة مقابس لوصلها لكل حاسوب. لحل هذه المشكلة يتصل كل حاسوب على الشبكة بجهاز حاسوبي صغير خاص يُدعى الموجّه Router، وهذا الجهاز له وظيفة واحدة: وهي التأكد أنّ الرسالة التي يرسلها حاسوب معين ستصل إلى وجهتها الصحيحة؛ أي إلى الحاسوب الصحيح. فعند ارسال رسالة من الحاسوب A إلى الحاسوب B ستخرج الرسالة من الحاسوب A إلى الموجّه الذي سيحوّل الرسالة بدوره إلى الحاسوب B ويتأكد أن الرسالة لن تحوّل إلى حاسوب آخر مثل C. عند إضافة موجّه إلى الشبكة المكونة من عشرة حواسب، سينخفض عدد الكابلات المطلوبة إلى 10 ومقبس واحد لكل حاسوب، كما سنحتاج إلى موجّه بعشرة مقابس. شبكة مكونة من عدة شبكات نلاحظ أن الأمر بسيط مع عشرة حواسيب، ولكن ماذا عن وصل مئات أو آلاف أو ملايين الحواسيب؟ لن يكفي موّجه واحد بالطبع، وطالما أن الموجّه هو حاسوب كبقية الحواسيب، فما المانع من وصل موجّهين معًا؟ لا شيء. عندما نوصل الحواسيب إلى موجهات ونوصل الموجهات ببعضها يمكننا توسيع الشبكة إلى ما لانهاية نظريًا. تتشابه هذه الشبكة التي بنيناها لشبكة الإنترنت، لكن ينقصها شيء ما. فعندما بنينا شبكتنا الخاصة التي تخدم أغراضنا، نسينا الشبكات الأخرى التي سيبنيها الآخرون لأن كل شخص قادر على بناء شبكته الخاصة ومن غير الممكن مثلًا توصيل كابلات بين منزلك وكل الشبكات المحيطة، فكيف سنحل الأمر إذًا؟ في الحقيقة هناك كابلات متعددة تصل بالفعل إلى منزلك مثل كابل الطاقة الكهربائية وكابل الهاتف. فالبنية التحتية للاتصالات الهاتفية تربط منزلك بالفعل مع أي شخص في العالم وبالتالي سيكون كابل الهاتف الخيار المثالي الذي نحتاج إليه. ولوصل شبكتنا بالبنية التحتية الهاتفية نحتاج إلى جهاز يدعى المودم Modem وهو يحول المعلومات التي توّلدها شبكتنا إلى معلومات تستطيع البنية التحتية الهاتفية فهمها وإدارتها والعكس بالعكس. بعد اتصالنا بالبنية التحتية الهاتفية ستكون الخطوة التالية هي توصيل الرسائل من شبكتنا إلى الشبكة التي نريدها. ولإتمام هذا الأمر سنصل شبكتنا أولًا بما يسمى مزود خدمة الإنترنت Internet Service Provider واختصارًا ISP. ومزود خدمة الإنترنت هي شركة تدير مجموعة خاصة من الموجهات المرتبطة ببعضها ولديها القدرة على الولوج إلى الموجهات الموجودة في مزودات خدمة أخرى. وهكذا تنتقل الرسائل من شبكتنا إلى الشبكة التي نريدها عبر شبكات مزود خدمة الإنترنت. تتكون الإنترنت من هذه البنية التحتية من الشبكات. إيجاد حاسوب معين إذا أردنا إرسال رسالة إلى حاسوب ما، علينا أن تحدد تمامًا من هو مستقبل الرسالة. لهذا السبب يمتلك كل حاسوب متصل إلى الشبكة عنوانًا فريدًا يميزه عن غيره من الحواسيب يدعى عنوان آي بي IP address (وهو اختصار للعبارة Internet Protocol أي بروتوكول إنترنت). يتكون عنوان آي بي من مجموعات مكونة من أربع أرقام تفصل بينها نقاط كالعنوان التالي: 192.168.2.10. تتذكر الحواسيب هذه العناوين جيدًا لكن يصعب على البشر تذكرها. ولتسهيل هذا الأمر يمكن تحويل هذه العناوين المؤلفة من أرقام إلى أسماء يفهمها البشر تُدعى أسماء النطاقات Domain names. فمثلًا (حتى لحظة كتابة هذه السطور ولأن عناوين آي بي تتغير) يرتبط اسم النطاق google.com بعنوان آي بي 142.250.190.78. وهكذا يُسهل اسم النطاق الوصول إلى حاسوب عبر الإنترنت. ما الفرق بين الإنترنت والويب؟ لا بدّ وأنك لاحظت أننا نستخدم أسماء النطاقات عادة عندما نتصفح الويب من خلال المتصفحات، فهل يعني ذلك أنّ الإنترنت والويب هو الشيء ذاته؟ في الحقيقة الأمر ليس بهذه البساطة. يعد الإنترنت كما رأيناه كبنية تحتية تقنية تسمح لمليارات الحواسيب بأن تتواصل مع بعضها، ويمكن لبعض هذه الحواسب والتي تدعى خوادم ويب Web servers أن توصل رسائل مفهومة بالنسبة لمتصفحات الويب، وبالتالي فالإنترنت هي البنية التحتية الأساسية بينما الويب هي خدمات مبنية على هذه البنية. ومن المفيد الإشارة إلى وجود خدمات أخرى مبنية على الإنترنت مثل خدمة البريد الإلكتروني وخدمة المحادثة المبنية على إنترنت Internet Relay Chat واختصارًا IRC. الشبكات الداخلية (إنترانيت) والخارجية (إكسترانت) الإنترانت Intranet هي شبكات خاصة تسمح لأشخاص محددين فقط من منظمة محددة الوصول إليها. تشكل هذه الشبكات بوابة خاصة تساعد الأعضاء على الوصول إلى موارد مشتركة بطريقة آمنة وتسمح لهم التعاون والتواصل فيما بينهم، فيمكن أن تستضيف منظمات شبكة إنترانت خاصة بها لمشاركة صفحات ومعلومات ضمن القسم أو الفريق ومشاركة السواقات لإدارة المستندات الهامة وبوابات لإدارة مهام تتعلق بالأعمال وأدوات للتعاون مثل المنشورات التعاونية wikis ومنصات النقاش ومنظومات الرسائل. تشبه الإكسترانت Extranet الإنترانت إلا أن الإكسترانت تتيح الوصول إلى شبكتها الخاصة كليًا أو جزئيًا للتعاون مع منظمات أخرى وتُستخدم بصورة أساسية لمشاركة المعلومات بأمان وسرية مع العملاء والمضاربين، وتتشابه وظيفيًا في الكثير من الأحيان مع الإنترانت فيما يتعلق بمشاركة الملفات والمعلومات وأدوات التعاون ومنصات النقاش وغيرها. تعمل كلتا الشبكتين على البنية التحتية نفسها التي تعمل عليها الإنترنت كما تستخدم البروتوكولات ذاتها، وبالتالي يمكن الوصول إليها من قبل الأعضاء المفوضين بذلك ومن مواقع فيزيائية مختلفة. ترجمة -وبتصرف- للمقال How does Internet work اقرأ أيضًا ما هو عنوان URL في الويب؟ الفرق بين صفحة الويب وموقع الويب وخادم الويب ومحرك البحث؟ ما هي أسماء النطاقات في شبكة الإنترنت؟
-
لنفترض أنّ لدينا النص التالي 67-45-123-(903)7+، وأننا نريد إيجاد كل الأعداد الموجودة ضمنه، سنحتاج هنا إلى أعداد كاملة، مثل 7 و903 و123 و45 و67، لا إلى كل رقم بمفرده، حيث يتألف العدد من رقم واحد أو تتابعٍ لأرقام d\، ولتحديد عدد الأرقام التي نحتاجها سنستخدم محددًا كميًا أو مكممًا quantifier. محدد الكمية {n} إنّ أبسط المحددات الكمية هو رقم ضمن أقواس معقوصة {n}، حيث يضاف المكمم إلى المحرف أو صنف المحرف أو الأقواس المربعة وغيرها؛ ليحدد العدد الذي نحتاجه منها، وللمكممات بعض الأشكال المتقدمة التي سنستعرض أمثلةً عنها: العدد الدقيق: مثل {5}، والذي يحدد العدد الدقيق من المحارف التي نحتاجها، فمثلًا يحدد {5}d خمسة أرقام تمامًا، كما لو كتبنا d\d\d\d\d\، ويعرض المثال التالي كيف نبحث عن خمسة أرقام متتالية: alert( "I'm 12345 years old".match(/\d{5}/) ); // "12345" ويمكن إضافة محرف حد الكلمة b\ لنستثني الأرقام الأطول. المجال: مثل {3,5}، ويحدد الأعداد التي تضم من 3 إلى 5 أرقام، ولإيجاد ذلك توضع حدود المجال ضمن أقواس معقوصة: alert( "I'm not 12, but 1234 years old".match(/\d{3,5}/) ); // "1234"` يمكننا تجاهل الحد الأعلى ليصبح التعبير على الشكل {,d{3\، وسيبحث عن أعداد مؤلفة من ثلاثة أرقام أو أكثر: alert( "I'm not 12, but 345678 years old".match(/\d{3,}/) );// "345678"` لنعد الآن إلى النص 67-45-123-(903)7+، حيث يتألف العدد من رقم واحد أو من تتابعٍ لأرقام في صف، فالتعبير النمطي المناسب هو {,d{1\: let str = "+7(903)-123-45-67"; let numbers = str.match(/\d{1,}/g); alert(numbers); // 7,903,123,45,67 اختصارات المحددات الكمية لمعظم المكممات اختصارات، وهي: +: يعني "واحد أو أكثر" مرة، تمامًا مثل النمط {,1}، إذ يبحث النمط +d\ عن الأعداد: let str = "+7(903)-123-45-67"; alert( str.match(/\d+/g) ); // 7,903,123,45,67` ?: يعني "صفر أو واحد" مرة، تمامًا مثل النمط {0,1}، أي يجعل الرمز اختياريًا، إذ يبحث النمط ou?r مثلًا عن المحرف o متبوعًا بمحرف u واحد أو غير متبوع به، ومن ثم المحرف r، لنبحث عن الكلمتين color أو colour: let str = "Should I write color or colour?"; alert( str.match(/colou?r/g) ); // color, colour *: يعني "صفر أو أكثر"، تمامًا مثل النمط {,0}، أي يبحث عن عدم وجود محرف أو تكراره مرةً واحدةً أو أكثر، إذ يبحث النمط *d0\ مثلًا عن رقم متبوع بأي تكرار للمحرف 0 (عدم وجوده أو تكراره مرةً أو عدة مرات): alert( "100 10 1".match(/\d0*/g) ); // 100, 10, 1` alert( "100 10 1".match(/\d0+/g) ); // 100, 10 // لا تطابق مع 1 لأن النمط 0+ سيتطلب أكثر من صفر أمثلة أكثر تستخدم المكممات كثيرًا، وتُعدّ البنية الرئيسية للتعابير النمطية المعقدة، وسنطلع على بعض الأمثلة. التعبير النمطي للكسور العشرية (عدد بفاصلة عشرية) ويعطى بالشكل +d+\.\d\، إليك مثالًا: alert( "0 1 12.345 7890".match(/\d+\.\d+/g) ); // 12.345 التعبير النمطي لفتح وسم HTML لا يحوي سمات مثل أو الطريقة الأبسط: a-z]+>/i]>/ alert( "<body> ... </body>".match(/<[a-z]+>/gi) ); // <body> يبحث التعبير عن المحرف '>' يتبعه حرف لاتيني أو أكثر، ثم المحرف '<'. الطريق المحسنة: a-z][a-z0-9]*>/i]>/: إذ يمكن أن يحتوي وسم HTML -مثلما تنص المعايير- على رقم في أي موقع من الاسم، عدا بدايته مثل <h1>. alert( "<h1>Hi!</h1>".match(/<[a-z][a-z0-9]*>/gi) ); // <h1> التعبير النمطي لفتح أو إغلاق وسم HTML لا يحوي سمات استخدم التعبير التالي: a-z][a-z0-9]\*>/i]?/\>/، حيث وضعنا شرطةً مائلةً اختياريةً ?/ قرب بداية النمط، وكان لابد من تجاوزها بشرطة معكوسة، وإلا فستعتقد JavaScript أنها نهاية النمط. alert( "<h1>Hi!</h1>".match(/<\/?[a-z][a-z0-9]*>/gi) ); // <h1>, </h1> لاحظ أنه لكتابة تعابير نمطية أكثر دقةً لا بدّ من تعقيد التعبير أكثر، فيمكن أن ترى بأن القاعدة المشتركة بين تلك الأمثلة هي أنّ الدقة تأتي من تعقيد التعبير وطوله، ففي وسوم HTML كان يمكننا استخدام التعبير <+w\>، لكن التعبير الذي استخدمناه هو الأصلح؛ بما يتوافق مع تقييدات التسمية وقواعدها في HTML. هل يمكن استخدام <+w\> أم يجب استخدام النمط <[a-z][a-z0-9]*>؟ يُقبَل استخدام كلا النمطين في الواقع العملي، ويعتمد اختيارنا على قدرتنا على التعامل مع التطابقات الناتجة عن البحث، وصعوبة استخلاصها بطرق أخرى. مكممات البحث الجشعة والكسولة تبدو المكممات Quantifiers بسيطة الاستخدام للوهلة الأولى، لكنها قد تغدو مربكةً عند استخدامها، لذلك لا بدّ من فهم آلية البحث جيدًا إذا خططنا للبحث عن أشياء أكثر تعقيدًا من /+d\/، لنلق نظرةً على المهمة التالية مثلًا: لدينا نص ما ونريد استبدال الإشارتين «...» بإشارتي التنصيص "..."، لأنها مفضلة في تنسيقات الطباعة والكتابة في بلدان عدة، أي يجب أن يصبح النص "مرحبًا أحمد" مثلًا بالشكل «مرحبًا أحمد»، وسنجد أيضًا إشارتي التنصيص „!Witam, świat” في اللغة البولندية، أو الإشارتين 「你好,世界」 في اللغة الصينية، لكننا سنختار الإشارتين «...» فقط في مهمتنا. ستكون الخطوة الأولى تحديد النصوص الموضوعة ضمن إشارتي التنصيص، ثم استبدالهما، وقد يبدو التعبير النمطي التالي g/"+."/، والذي يعني (إشارة تنصيص ثم شيء ما مكرر ثم إشارة تنصيص أخرى) مناسبًا، لكنه ليس كذلك. انظر إلى المثال التالي: let regexp = /".+"/g; let str = 'a "witch" and her "broom" is one'; alert( str.match(regexp) ); // "witch" and her "broom" لم يعمل النمط كما هو مطلوب! فبدلًا من إيجاد الكلمتين "witch" و"broom"، وجد العبارة التالية:"witch" and her "broom". البحث الجشع Greedy search يتبع محرك التعابير النمطية الخوارزمية التالية في إيجاد التطابقات: من أجل كل موقع في النص: يحاول إيجاد التطابق عند هذا الموقع. إذا لم يجد تطابقًا فسينتقل إلى الموقع التالي. لكن هذه الكلمات غير قادرة على وصف سبب فشل التعبير بدقة، لذلك سنفصل آلية البحث عن النمط "+.": الخطوة الأولى، يحاول المحرك إيجاد المحرف الأول من النمط "عند الموقع 0 من النص a "witch" and her "broom" is one، لكنه بدلًا من ذلك سيجد المحرف a، لذلك سيتابع إلى الموقع التالي ويحاول إيجاد أول محارف النمط وسيخفق ثانيةً، وأخيرًا سيجد تطابقًا في الموقع الثالث (الموقع 2). الخطوة الثانية، يجد المحرك المحرف الأول ويتابع لإيجاد بقية محارف النمط، ويحاول أن يكتشف فيما لو تطابق بقية النص مع المحارف الباقية من النمط "+.، وسيكون المحرف التالي في النمط هو النقطة .، التي تشير إلى أي محرف عدا محرف السطر الجديد، لذلك ستجد التطابق مع الحرف 'w'. الخطوة الثالثة، سيتابع المحرك المطابقة مع النقطة نظرًا لوجود المكمم + بعدها، وسيطابق كل المحارف الأخرى ويضيفها إلى النتيجة حتى يصل إلى نهاية النص، لأن كل المحارف عدا محرف السطر الجديد ستمثل تطابقًا. الخطوة الرابعة، سينتهي المحرك من تكرار المحرف . ويحاول إيجاد المحرف التالي من النمط -وهي إشارة التنصيص "-، وهنا تظهر المشكلة فقد انتهى النص ولم يعد هناك المزيد من المحارف للبحث ضمنها، عندها سيفهم المحرك أنه ضم الكثير من المحارف .+، وسيبدأ بالتراجع، حيث يختصر نتيجة التطابق التي ولّدها المكمم بمقدار محرف إلى الخلف؛ مفترضًا أن المحارف ستنتهي قبل انتهاء النص بمحرف واحد، ثم يحاول إيجاد بقية محارف النمط انطلاقًا من هذا الموقع، وسينهي المحرك البحث إذا وجد المحرف، لكنه سيجد المحرف 'e'، وبالتالي لن تنتهي العملية. الخطوة الخامسة، سيقلل المحرك عدد المحارف الناتجة عن تكرار النقطة بمحرف واحد أيضًا، إلى أن يجد التطابق مع آخر محارف النمط '"' حتى يجده. الخطوة السادسة، سيكتمل البحث عند إيجاد التطابق. الخطوة السابعة، وهكذا ستكون النتيجة هي "witch" and her "broom". الخطوة الثامنة، مع وجود الراية g سيتابع البحث عن النمط ابتداءً من الموقع الذي انتهى فيه التطابق الأول، ولن يحصل على نتيجة طبعًا، فلن يجد إشارة تنصيص أخرى، طبعًا لم نتوقع ذلك، لكن الأمور تسير هكذا! يكرَّر المكمم في النمط الموسَّع -افتراضيًا- أكبر عدد ممكن من المرات، حيث يضيف المحرك القدر الأكبر من المحارف التي تتطابق مع +.، ثم يختصر النتيجة محرفًا تلو الآخر. النمط الكسول Lazy mode النمط الكسول أو المحدود هو نمط معاكس للنمط الموَّسع أو الجشع، ويعني أقل تكرار للمحارف، ويمكن أن نختار هذا النمط بوضع إشارة الاستفهام ? بعد المكمم حيث يصبح بالشكل ?* أو ?+ أو حتى ?? لــ?، ولنوضح الأمر علينا معرفة أن إشارة الاستفهام ? هي مكمم بحد ذاتها (صفر أو واحد)، وعندما تُضاف بعد مكمم آخر فسيصبح لها معنىً آخر، وهو تحويل نمط البحث إلى النمط المحدود الكسول، وسيعمل النمط g/"?+."/ في مهمتنا السابقة بالشكل الصحيح: let regexp = /".+?"/g; let str = 'a "witch" and her "broom" is one'; alert( str.match(regexp) ); // "witch", "broom" ولفهم التغيّر بوضوح سنتعقب عملية البحث خطوةً خطوةَ: الخطوة الأولى، تبقى الخطوة الأولى نفسها، وهي إيجاد المحرف '"' في الموقع الثالث من النص. الخطوة الثانية، وسيجد المحرك في الخطوة الثانية ما يطابق المحرف '.'. الخطوة الثالثة، سيظهر الاختلاف الآن، حيث لا يحاول المحرك في النمط المحدود إيجاد تطابق مع ما تمثله النقطة مرةً أخرى، بل يتوقف ويحاول إيجاد تطابقات مع بقية محارف النمط (أي '"') مباشرةً، وسيتوقف البحث عن النمط إذا وجد تطابقًا، لكنه سيجد الحرف i بدلًا منه. الخطوة الرابعة، سيزيد بعد ذلك محرك التعبير النمطي عدد التكرارات لمحرف النقطة مرةً واحدةً، ثم يحاول مطابقة بقية النمط، لكنه سيفشل ثانيةً، وسيزيد عدد مرات التكرار مرةً أخرى وأخرى… الخطوة الخامسة، سيتوقف البحث عندما يجد التطابق مع المحرف '"'. الخطوة السادسة، يبدأ البحث الجديد، نظرًا لوجود الراية g، من الموقع الذي انتهى عنده البحث السابق، وسيجد النتيجة الثانية. يعمل النمط الكسول بشكل مشابه للمكممات ?* و??، حيث يزيد محرك التعبير النمطي عدد التكرارات إذا لم يجد تطابقًا مع بقية محارف النمط في الموقع المُعطى. يُفعّل النمط الكسول للمكممات إذا تبعها المحرف ?، وإلا فستبقى في النمط الموّسع الجشع، إليك مثالًا: alert( "123 456".match(/\d+ \d+?/) ); // 123 4 يحاول النمط +d\ إيجاد أكبر عدد ممكن من الأعداد (نمط موسّع)، لذلك سيجد 123 ويتوقف، لأن المحرف التالي سيكون الفراغ ' '. سيجد المحرك المحرف التالي في النمط وهو الفراغ ' '. يحاول إيجاد الأعداد التالية وفقًا للنمط ?+d\ حيث يكون المكمم محدودًا، وسيجد العدد 4 ثم يتحقق من وجود بقية محارف النمط بعده، لكن محارف النمط قد انتهت. لا يكرر النمط الكسول أي شيء دون حاجة إلى ذلك، لذا سينتهي البحث بالنتيجة 4 123. لاحظ، تحسن محركات التعابير النمطية الحديثة خوارزميات البحث الداخلية لتعمل بسرعة أكبر، لذلك قد تعمل بطريقة مختلفة قليلًا عما وصفناه سابقًا، ولن نحتاج إلى معرفة تفاصيل الخوارزمية المحسَّنة عند كتابة التعابير النمطية وفهمها، لأنها تعمل داخليًا، وبما أن التعابير المعقدة صعبة التحسين، فسيجري البحث وفق الخطوات التي وصفناها سابقًا تمامًا. مقاربة بديلة نجد عادةً عند استخدام التعابير النمطية عدة طرق لتنفيذ الشيء نفسه، إذ يمكن في حالتنا السابقة إيجاد إشارات التنصيص دون الحاجة إلى البحث المحدود، بل من خلال النمط "+[^"]" let regexp = /"[^"]+"/g; let str = 'a "witch" and her "broom" is one'; alert( str.match(regexp) ); // "witch", "broom" سينجح هذا النمط لأنه يبحث عن إشارة تنصيص يتبعها تكرار لمحرف أو أكثر لا يمثل إشارة تنصيص، ثم يبحث عن إشارة تنصيص أخرى لإغلاق النص، وتجدر الملاحظة أن هذا المنطق في البحث لا يشكل بديلًا عن البحث المحدود، فهنالك حالات نحتاجه فيها. مثال عن إخفاق المكمم الكسول ونجاح الأسلوب البديل لنحاول مثلًا إيجاد رابط من الشكل <a href="..." class="doc"> بحيث يصلح لأي عنوان href، فما هو التعبير النمطي الذي سنستخدمه؟ قد تكون الفكرة الأولى هي: a href=".*" class="doc">/g>/، وسنتحقق منها: let str = '...<a href="link" class="doc">...'; let regexp = /<a href=".*" class="doc">/g; // !عملت alert( str.match(regexp) ); // <a href="link" class="doc"> ستعمل هذه الفكرة، لكن دعونا نتحقق منها عند وجود عدة روابط في النص: let str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...'; let regexp = /<a href=".*" class="doc">/g; // حصلنا على رابطين في نتيجة واحدة alert( str.match(regexp) ); // <a href="link1" class="doc">... <a href="link2" class="doc"> النتيجة خاطئة، لأنّ المكمم *. ضمّ الكثير من المحارف، وسيكون التطابق مشابهًا للوصف التالي: <a href="....................................." class="doc"> <a href="link1" class="doc">... <a href="link2" class="doc"> لنُعدِّل النمط بجعل المكمم كسولًا ( ?*.): let str = '...<a href="link1" class="doc">... <a href="link2" class="doc">...'; let regexp = /<a href=".*?" class="doc">/g; // Works! alert( str.match(regexp) ); // <a href="link1" class="doc">, <a href="link2" class="doc"> سينجح الأمر وسيجد الرابطين: <a href="....." class="doc"> <a href="....." class="doc"> <a href="link1" class="doc">... <a href="link2" class="doc"> لنختبر ذلك مع نص آخر: let str = '...<a href="link1" class="wrong">... <p style="" class="doc">...'; let regexp = /<a href=".*?" class="doc">/g; // أخفق أيضًا alert( str.match(regexp) ); // <a href="link1" class="wrong">... <p style="" class="doc"> لقد أخفق البحث مجددًا، فقد طابق البحث الكثير من محارف النص بما فيها <p>، فما السبب؟ إليك ما حدث: يجد محرك التعبير النمطي المحارف <"=a href> بدايةً. ثم يبحث عن المكمم المحدود ?*.، الذي يأخذ محرفًا ويتحقق من وجود تطابق مع بقية أحرف النمط "class="doc "، فلا يجد شيئًا. ثم يضيف محرفًا إلى نتيجة النمط ?*. ويستمر بالإضافة إلى أن يصل إلى "class="doc "، لكن المشكلة أن ما وجده سيكون خارج حدود الرابط <...a> وضمن وسم مختلف <P>. إليك وصفًا لما حدث: <a href="..................................." class="doc"> <a href="link1" class="wrong">... <p style="" class="doc"> لاحظ إخفاق كل من البحث الجشع والكسول في المهمة، لكن سيُنفذ النمط "*[^"]"=href المطلوب في حالتنا، لأنه سيبحث عن المحارف ضمن السمة href إلى أن يجد أقرب إشارة تنصيص، وهذا ما نريده. إليك مثالًا: let str1 = '...<a href="link1" class="wrong">... <p style="" class="doc">...'; let str2 = '...<a href="link1" class="doc">... <a href="link2" class="doc">...'; let regexp = /<a href="[^"]*" class="doc">/g; // Works! alert( str1.match(regexp) ); // null, لا تطابق وهذا صحيح alert( str2.match(regexp) ); // <a href="link1" class="doc">, <a href="link2" class="doc"> الخلاصة للمكممات Quantifiers نمطان للعمل: الواسع الجشع Greedy: وهو الخيار الافتراضي الذي يحاول فيه محرك التعبير النمطي تكرار المحرف المُحصى أكبر عدد ممكن من المرات، فالنمط +d\ مثلًا سيستهلك كل الأرقام الممكنة، وعندما لا يعود قادرًا على إضافة المزيد من المحارف سيتابع مطابقة بقية أحرف النمط، فإذا لم يجد تطابقًا فسيقلل عدد مرات التكرار مرةً واحدةً -يتراجع- ويحاول مجددًا. المحدود الكسول Lazy: ويُمكَّن بإضافة إشارة الاستفهام بعد المكمم، حيث يحاول المحرك إيجاد تطابق مع بقية محارف النمط قبل تكرار البحث عن المحرف المُحصى. ويمكن استخدام بحث موّسع ومعدّل بدقة وفق النمط "[^"]+" بديلًا عن الأسلوبين. مهام لإنجازها إيجاد ثلاث نقاط متتابعة أو أكثر اكتب نمطًا للبحث عن ثلاث نقاط متتابعة أو أكثر let regexp = /your regexp/g; alert( "Hello!... How goes?.....".match(regexp) ); // ..., ..... الحل: let regexp = /\.{3,}/g; alert( "Hello!... How goes?.....".match(regexp) ); // ..., ..... تذكر أن النقطة . محرف خاص ولابد من تجاوزه وحشر النمط .\. تحديد الألوان في HTML أنشئ تعبيرًا نمطيًا للبحث عن ألوان HTML المكتوبة على الشكل ABCDEF#، ابحث أولًا عن المحرف # تليه 6 محارف لأرقام ستة عشرية، إليك مثالًا: let regexp = /...your regexp.../ let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2 #12345678"; alert( str.match(regexp) ) // #121212,#AA00ef الحل: علينا أن نبحث عن المحرف # متبوعًا بستة محارف ست عشرية. يمكن وصف المحرف الست عشري وفق النمط [0-9a-fA-F]، أو وفق النمط [0-9a-f] عند استخدام الراية i. يمكننا عندئذ البحث عن ستة محارف ست عشرية باستخدام المكمم {6}. بالنتيجة سيكون التعبير النمطي المناسب من الشكل: /#[a-f0-9]{6}/gi إليك شيفرة الحل: let regexp = /#[a-f0-9]{6}/gi; let str = "color:#121212; background-color:#AA00ef bad-colors:f#fddee #fd2" alert( str.match(regexp) ); // #121212,#AA00ef المشكلة في هذا الحل أن التعبير يطابق سلسلة طويلة أكثر حتى لو كانت أكثر من 6 محارف مثل: alert( "#12345678".match( /#[a-f0-9]{6}/gi ) ) // #123456 يمكن حل هذه المشكلة بإضافة \b لنهاية التعبير: // لون alert( "#123456".match( /#[a-f0-9]{6}\b/gi ) ); // #123456 // ليس لون alert( "#12345678".match( /#[a-f0-9]{6}\b/gi ) ); // null إيجاد تطابق مع النمط /?+d+? d/ ما نتيجة التطابق في المثال الآتي: alert( "123 456".match(/\d+? \d+?/g) ); // ? الحل: النتيجة هي 4 123. يحاول النمط الكسول d+?\ أقل عدد ممكن من الأرقام لكنه سيصل إلى محرف المسافة الفارغة وبالتالي سيأخذ فقط 123، ثم يأخذ النمط الثاني d+?\ رقمًا واحدًا فقط ويكتفي. إيجاد تعليقات HTML أوجد كل التعليقات في النص التالي: let regexp = /your regexp/g; let str = `... <!-- My -- comment test --> .. <!----> .. `; alert( str.match(regexp) ); // '<!-- My -- comment \n test -->', '<!---->' الحل: علينا إيجاد نمط بداية التعليق --!> ثم نأخذ المحارف اللاحقة له حتى نصل إلى نمط نهاية التعليق <-- إذًا سيكون النمط من الشكل <--?*.--!> ، حيث يجبر المكمم الكسول المحرف . على التوقف قبل النمط <--. نحتاج أيضًا إلى الراية s مع النقطة لكي تستمر في ضم المحارف حتى لو انتقلنا إلى سطر جديد وإلا لن نتمكن من التقاط التعليقات الممتدة على أكثر من سطر. let regexp = /<!--.*?-->/gs; let str = `... <!-- My -- comment test --> .. <!----> ..`; alert( str.match(regexp) ); // '<!-- My -- comment \n test -->', '<!---->' إيجاد وسوم HTML أنشئ تعبيرًا نمطيًا لإيجاد كل وسوم HTML (للبداية والنهاية) مع سماتها. إليك مثالًا: let regexp = /your regexp/g; let str = '<> <a href="/"> <input type="radio" checked> <b>'; alert( str.match(regexp) ); // '<a href="/">', '<input type="radio" checked>', '<b>' نفترض هنا عدم وجود المحرفين < و> ضمن سمات الوسم، مما يسهل الأمر قليلًا. الحل سيكون التعبير النمطي الصحيح من الشكل: <[^<>]+> إليك شيفرة الحل: let regexp = /<[^<>]+>/g; let str = '<> <a href="/"> <input type="radio" checked> <b>'; alert( str.match(regexp) ); // '<a href="/">', '<input type="radio" checked>', '<b>' ترجمة -وبتصرف- للفصلين َQuntifiers +,*,? And {n} وَGreedy and Lazy Quantifiers من سلسلة The Moden JavaScript Tutorial. اقرأ أيضًا المقال السابق: المجموعات والمجالات في التعابير النمطية أساسيات البحث باستخدام التعابير النمطية في جافاسكربت المجموعات والمجالات في التعابير النمطية
-
وجود عدة محارف أو أصناف محارف ضمن قوسين مربعين […] يعني البحث عن أي محرف بينها. سيكمل هذا المقال الغوص في التعابير النمطية بعد أن تطرقنا إلى مقدمة شاملة عنها في المقال السابق بعنوان أساسيات التعابير النمطية في جافاسكربت. المجموعات يعني النمط [eao] البحث عن أيٍّ من المحارف الثلاثة 'a' أو 'e' أو 'o'، ويُدعى هذا النمط بالمجموعة، ويمكن استخدام المجموعات ضمن التعابير النمطية ومع محارف نظامية: //"op" ثم [t أو m] جد alert( "Mop top".match(/[tm]op/gi) ); // "Mop", "top" لاحظ أنه وعلى الرغم من وجود عدة محارف ضمن المجموعة، فإنها سترتبط بمحرف واحد تمامًا عند إعادة النتيجة، فمثلًا لن يعيد البحث التالي أي نتيجة: //"la" ثم [o أو i] ثم "V" جد alert( "Voila".match(/V[oi]la/) ); // null, no matches سيبحث النمط عن: V ثم أحد الحرفين [oi]. ثم la. لذا ستكون النتيجة إما Vola أو Vila، والنصان غير موجودين. المجالات يمكن أن تضم الأقواس المربعة مجالات من المحارف، مثل المجال [a-z]، الذي يحدد المحارف بين a وz، أو المجال [0-5] الذي يحدد الأرقام بين 0 و5، وسنبحث في المثال التالي عن "x" متبوعًا برقمين أو حرفين من A إلى F: alert( "Exception 0xAF".match(/x[0-9A-F][0-9A-F]/g) ); // xAF ستجد مجالين ضمن الأقواس المربعة [A-F0-9]، وسنبحث عن عدد من 0 إلى 9 أو حرف من A إلى F، ويمكن البحث عن أحرف بالحالة الصغيرة بإضافة المجال a-f، بحيث يصبح النمط [A-F0-9a-f] أو باستخدام الراية i، كما يمكن استخدام أصناف المحارف داخل الأقواس المربعة، فلو أردنا مثلًا البحث عن محرف كلمة أو شرطة قصيرة، فستكون المجموعة المناسبة هي [-w\]، ويمكن الجمع أيضًا بين أكثر من صنف محارف، مثل المجموعة [s\d\]، التي تعني البحث عن محرف فراغ أو رقم انتبه إلى أن أصناف المحارف هي اختصارات لمجموعات محددة من المحارف، فمثلًا: d\: هي نفسها المجموعة [9-0]. w\: هي نفسها المجموعة [a-zA-Z0-9]. s\: هي نفسها المجموعة [t\n\v\f\r\ ]، بالإضافة إلى بعض محارف فراغ Unicode الخاصة. مثال: محرف الكلمة w\ للغات متعددة يمكن لصنف المحارف w\، كونه اختصارًا للنمط [a-zA-Z0-9]، أن يجد المحارف العربية أو الصينية وغيرها، لذلك يمكننا كتابة أنماط من التعابير لإيجاد محارف في أي لغة باستخدام خصائص الترميز Unicode مثل التعبير النمطي التالي: [{p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C\] ولنفسر الآن هذا النمط، حيث ننشئ فيه مجموعةً خاصةً بنا تتضمن محارف لها خصائص Unicode التالية: Alpha: للأحرف. M: للعلامات فوق الأحرف. Nd: للأعداد بالنظام العشري. Pc: للشرطة السفلية (_) والمحارف المشابهة. Join_C: للمحارف الخاصة 200c و200d التي تستخدم في لغات غير لاتينية، مثل العربية. إليك مثالًا: let regexp = /[\p{Alpha}\p{M}\p{Nd}\p{Pc}\p{Join_C}]/gu; let str = `Hi 你好 12`; // جد كل الأحرف والأرقام: alert( str.match(regexp) ); // H,i,你,好,1,2 يمكن بالطبع تعديل هذه النمط بإضافة خصائص جديدة أو إزالتها، وسنفصّل في هذا الخصائص لاحقًا. انتبه إلى أن خصائص يونيكود Unicode أي p{...} غير مدعومة في متصفح إنترنت إكسبلورر IE، فلا يدعم متصفح IE خصائص Unicode، فإذا احتجنا إليها فيمكننا استخدام المكتبة XRegExp، ومن الممكن أيضًا استخدام مجالات لأحرف اللغة المطلوبة، مثل المجال [ا-ي] في اللغة العربية. مجالات الاستثناء ستجد أيضًا مجالات لاستثناء محارف معينة تبدو بالشكل […^]، فهي معلّمة بالمحرف ^ في البداية، وتطابق أي محرف عدا المحارف المعطاة، وإليك بعض الأمثلة: [aeyo^]: أي محرف عدا المحارف 'a' أو 'e' أو 'y' أو 'o'. [0-9^]: أي محرف عدا الأرقام، لها وظيفة الصنف D\. [s\^]: أي محرف لا يمثل محرف فراغ. يبحث المثال التالي عن أي محارف عدا الأحرف أو الأرقام أو الفراغات: alert( "alice15@gmail.com".match(/[^\d\sA-Z]/gi) ); // @ and . التجاوز عند استخدام […] رأينا سابقًا أننا نتجاوز (نهرِّب) الوظيفة الخاصة لمحرف باستخدام الشرطة المعكوسة .\، وإذا أردنا البحث عن الشرطة المعكوسة بذاتها نكررها مرتين \\، ويمكن استخدام معظم المحارف الخاصة ضمن الأقواس المربعة دون الحاجة إلى تجاوزها: لاتحتاج الرموز . + ( ) إلى تجاوز. لا يحدث تجاوز للشرطة - في بداية ونهاية النص، أي عندما لا تحدد مجالًا. يحدث تجاوز للعلامة ^ في بداية النص فقط، عندما تعني الاستثناء. يحدث تجاوز دائم لقوس الإغلاق المربع [، إن أردنا البحث عنه بحد ذاته. وبعبارة أخرى يُسمح باستخدام المحارف الخاصة جميعها دون الحاجة للتجاوز؛ إلا عندما تعني شيئًا محددًا ضمن الأقواس المربعة، فالنقطة ضمن الأقواس المربعة تعني نقطة، فمثلًا سيبحث النمط [.,] مثلًا عن نقطة أو فاصلة. يبحث النمط [-().^+] في المثال التالي عن أي محرف من المحارف +().^-: // لا حاجة للتجاوز let regexp = /[-().^+]/g; alert( "1 + 2 - 3".match(regexp) ); // تطابق +, - لكن إذا قررت تجاوز هذه المحارف للاحتياط، فلا مشكلة في ذلك: // تجاوز كل شيء let regexp = /[\-\(\)\.\^\+]/g; alert( "1 + 2 - 3".match(regexp) ); // سينجح أيضًا +, - المجالات واستعمال الراية "u" إذا وجد محرف بصيغة زوج بديل surrogate pair، وهو تفسير بعض الخصائص للمحرف ذي البايتات الأربع، فلا بدّ من استخدام الراية u للتعامل مع هذه المحارف. لنبحث مثلًا عن النمط [??] ضمن النص "?": alert( '?'.match(/[??]/) ); // [?] سيظهر محرف غريب مثل // جرى البحث بطريقة غير صحيحة وأعاد نصف المحرف فقط ستكون النتيجة خاطئةً، لأن التعبير النمطي لا يعلم بوجود زوج بديل افتراضيًا، إذ يعتقد محرك التعبير النمطي أن النمط [??] مكون من أربعة محارف، وليس محرفين: الأول هو النصف اليساري من ?. الثاني هو النصف اليميني من ?. ,الثالث هو النصف اليساري من ?. الرابع هو النصف اليميني من ?. ويمكننا الاطلاع على ترميزها بالشكل التالي: for(let i=0; i<'??'.length; i++) { alert('??'.charCodeAt(i)); // 55349, 56499, 55349, 56500 }; إذًا سيجد المثال السابق النصف الأيسر فقط من ? ويعرضه، لكن سنحل المشكلة عند إضافة الراية u: alert( '?'.match(/[??]/u) ); // ? ونستخدم الحل نفسه عندما نبحث عن مجال يحوي محارف بأزواج بديلة، مثل [?-?]، وسينتج خطأً إذا لم نستخدم الراية u. '?'.match(/[?-?]/); // خطأ: تعبير نظامي غير صحيح والسبب أنّ الزوج البديل سيعامل مثل محرفين دون الراية u، وهكذا سيُفسَّر النمط [?-?] على الشكل التالي: [<56500><55349>-<56499><55349>] حيث يُبدَّل كل زوج بترميزه، وهكذا سيكون المجال 56499-55349 خاطئًا، لأن الرمز النصي 56499 لبداية المجال أكبر من الرمز النصي 55349 لنهايته، وهذا هو سبب الخطأ، وسيعمل النمط بشكل صحيح بوجود الراية u: // ? إلى ? البحث عن محارف من alert( '?'.match(/[?-?]/u) ); // ? ترجمة -وبتصرف- للفصل Sets and Ranges من سلسلة The Moden JavaScript Tutorial. اقرأ أيضًا التعابير النمطية (regexp/PCRE) في PHP مقدمة في التعابير النمطية Regular Expressions كيفية التعامل مع النصوص في البرمجة
-
التعابير النمطية Regular expressions هي أنماط تزودنا بأسلوب فعّال للبحث عن النصوص التي تطابق نمطًا محددًا واستبدالها في JavaScript، يتيح الكائن RegExp استخدام هذه التعابير، وتُدمج مع التوابع التي تتعامل مع النصوص. مقدمة إلى التعابير النمطية تتألف التعابير النمطية من نمط pattern، ورايات flags اختيارية، ولإنشاء كائن تعبير نمطي يمكن استخدام صيغتين، صيغة طويلة وصيغة قصيرة، أما الصيغة الطويلة فتكتب بالشكل: regexp = new RegExp("pattern", "flags"); وأما الصيغة القصيرة فتكتب باستخدام المحرف "/" بالشكل: regexp = /pattern/; // لا رايات regexp = /pattern/gmi; // g و m و i باستخدام الرايات تخبر الخطوط المائلة JavaScript بأننا ننشئ تعبيرًا نمطيًا، فهي تلعب دورًا مماثلًا لعلامة الاقتباس في النصوص، وفي كلتا الصيغتين سيصبح الكائن regexp نسخةً عن الصنف المدمج RegExp. لا يُسمح بإدخال متغيرات تمثل الأنماط ضمن صيغة الخطوط المائلة /.../ باستخدام القوالب {...}$ الديناميكية مثلًا كما نفعل في النصوص، فهي ثابتة بشكل كامل، وهذا هو الاختلاف الرئيسي بين الصيغتين، إذ نستخدم الخطوط المائلة عندما نريد استخدام تعابير نمطية محددة ومعروفة بالنسبة لنا عند كتابة الشيفرة، وهذه هي الحالة الأكثر شيوعًا، بينما نستخدم الصيغة new RegExp عادةً عندما نحتاج إلى إنشاء تعابير نمطية أثناء التنفيذ انطلاقًا من نص أُنشئ ديناميكيًا. let tag = prompt("What tag do you want to find?", "h2"); let regexp = new RegExp(`<${tag}>`); // "h2" إن كان الجواب /<h2>/ نفس نتيجة الرايات للتعابير النمطية رايات تؤثر على نتيجة البحث، وتستخدم JavaScript ستةً منها، وهي: i: سيكون البحث غير حساس لحالة الأحرف مع هذه الراية، أي لن يكون هناك فرق بين "a" و"A". g: سيعيد البحث جميع النتائج المتطابقة مع هذه الراية، وإلا فسيعيد النتيجة الأولى. m: سيعمل البحث في نمط الأسطر المتعددة، وسنغطي ذلك لاحقًا. s: ستمكّن نمط "dotall" الذي يسمح بأن نعدَّ النقطة . هي محرف نهاية السطر n\، وسنغطيه لاحقًا. u: سيمكّن الدعم الكامل لترميز Unicode، وبالتالي المعالجة الصحيحة لمحتويات الأقواس المعقوصة {..}، وسنغطيه لاحقًا. y: سيمكّن النمط اللاصق "Sticky"، حيث يبحث في المكان المحدد تمامًا من النص، وسنغطيه لاحقًا. البحث باستخدام التابع str.match تتكامل التعابير النمطية مع توابع النصوص، إذ يبحث التابع (str.match(regexp عن كل ما يطابق التعبير regexp في النص str، ويعمل وفق ثلاثة أنماط: النمط الأول، التعبير النمطي مع الراية g، ويعيد مصفوفةً تضم النتائج المطابقة: let str = "We will, we will rock you"; alert( str.match(/we/gi) ); // سيعيد مصفوفة من الحالتين المتطابقتين للبحث وانتبه إلى أنّ البحث سيعيد النتيجتين "we" و"We" لأن راية حالة الأحرف i مفعّلة. النمط الثاني، إذا لم نفعّل الراية g، فسيعيد البحث النتيجة الأولى فقط ضمن مصفوفة، مع وضع التطابق في الدليل 0، بالإضافة إلى تفاصيل أخرى في الخصائص: let str = "We will, we will rock you"; let result = str.match(/we/i); // g دون الراية alert( result[0] ); // We (التطابق الأول) alert( result.length ); // 1 // Details: alert( result.index ); // 0 (موقع التطابق) alert( result.input ); // We will, we will rock you (النص الأصلي) قد تحتوي المصفوفة على عناصر أخرى بالإضافة إلى العنصر ذي الدليل 0؛ إذا كان جزء من التعبير النمطي ضمن قوسين مغلقين وسنغطي ذلك في المقالات القادمة. النمط الثالث، إذا لم توجد أي تطابقات، فسيعيد التابع القيمة null، بوجود الراية g أو عدم وجودها، لذا انتبه أننا لن نحصل في هذه الحالة على مصفوفة فارغة، بل على القيمة null، وسيؤدي نسيان هذه الحقيقة إلى مشاكل عدة: let matches = "JavaScript".match(/HTML/); // = null if (!matches.length) { //length خطأ لا يمكن قراءة الخاصية alert("Error in the line above"); } إذا أردنا أن نحصل دائمًا على مصفوفة، فيمكننا أن ننفذ الأمر بالشكل التالي: let matches = "JavaScript".match(/HTML/) || []; if (!matches.length) { alert("No matches"); // ستعمل الآن } الاستبدال باستخدام التابع str.replace يستبدل التابع (str.replace(regexp, replacement التطابقات الموافقة للكائن regexp في النص str بقيمة replacement، وستستبدل كل التطابقات عند استخدام الراية g، وإلا فسيستبدل أول تطابق فقط، وإليك مثالًا: // g دون الراية alert( "We will, we will".replace(/we/i, "I") ); // I will, we will // مع الراية alert( "We will, we will".replace(/we/ig, "I") ); // I will, I will يمكن استخدام محارف خاصة ضمن الوسيط الثاني replacement للبحث عن أجزاء من معيار التطابق: table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } الرمز ما يفعله ضمن النص replacment &$ يمثل نص التطابق `$ يمثل النص الواقع قبل نص التطابق. '$ يمثل النص الواقع بعد نص التطابق. n$ يمثل التطابق ذا الرقم n من مجموعة التطابق (الموجود ضمن قوسي تجميع"()" ) . $<name> يمثل التطابق ذا الاسم name من مجموعة التطابق (الموجود ضمن قوسي تجميع"()" ). $$ يمثل المحرف $ مثال باستخدام &$: alert( "I love HTML".replace(/HTML/, "$& and JavaScript") ); // I love HTML and JavaScript الاختبار باستخدام التابع regexp.test يبحث التابع (regexp.test(str عن تطابق واحد على الأقل -إن وُجد- ويعيد القيمة true، وإن لم يجد تطابقًا فسيعيد القيمة false. let str = "I love JavaScript"; let regexp = /LOVE/i; alert( regexp.test(str) ); // true سندرس لاحقًا في هذا القسم تعابير نمطيةً أكثر، وسنتعرف على توابع أخرى لاحقًا. أصناف المحارف لنتأمل مهمةً تتطلب تحويل رقم هاتف، مثل "67-45-123-(903)7+"، إلى رقم صرف، أي 79031234567، لتنفيذ ذلك يمكننا تتبع وحذف أي محرف لا يمثل رقمًا، وستساعدنا أصناف المحارف في ذلك، حيث تمثل إشارات خاصةً تطابق أي رمز ضمن مجموعة معينة، وسنتعرف بدايةً على الصنف "digit"، الذي يُعبَّر عنه بالشكل d\، ويرتبط بأي رقم مفرد، فمثلًا لنجد الرقم الأول في رقم الهاتف: let str = "+7(903)-123-45-67"; let regexp = /\d/; alert( str.match(regexp) ); // 7 سيبحث التابع عن كل الأرقام التي سيجدها في النص بإضافة الراية g، وسيعيدها ضمن مصفوفة: let str = "+7(903)-123-45-67"; let regexp = /\d/g; alert( str.match(regexp) ); // 7,9,0,3,1,2,3,4,5,6,7 // لنشكل عددًا من أرقام المصفوفة: alert( str.match(regexp).join('') ); // 79031234567 سنستعرض الآن بعض الأصناف الأكثر استعمالًا، وهي: d\: من كلمة digit رقم، ويعيد أي رقم بين 0 و9. \s: من كلمة space فراغ، وتضم محارف الفراغ، مثل: مسافة الجدولة t\، والسطر الجديد n\، وغيرها من الرموز النادرة الاستخدام مثل r\ وv\ وf\. w\: من كلمة word كلمة، وتعيد محارف قد تشكل كلمةً، مثل الأحرف اللاتينية أو الأرقام أو الشرطة السفلية _، ولا تنتمي الأحرف غير اللاتينية، مثل العربية، إلى هذا الصنف، إذ تشير الرموز d\s\w\ مثلًا إلى رقم متبوع بمحرف فراغ، ويليه محرف كلمة مثل 1 a. يمكن أن يضم الكائن محارف تعابير نمطية بالإضافة إلى الأصناف، فمثلًا سيبحث التعبير CSS\d عن الكلمة CSS متبوعةً برقم: let str = "Is there CSS4?"; let regexp = /CSS\d/ alert( str.match(regexp) ); // CSS4 كما يمكن استخدام أصناف محارف عدة: alert( "I love HTML5!".match(/\s\w\w\w\w\d/) ); // ' HTML5' الأصناف المعكوسة لكل صنف من أصناف المحارف صنف معكوس inverse class، يرمز له بالحرف نفسه لكن بحالة حرف كبير uppercase، ويعني ذلك الحصول على أي محرف عدا المحرف الذي يعيده الصنف عادةً، أي: D\: لا يعيد رقمًا، وإنما يعيد أي محرف عدا المحارف التي يعيدها d\، مثل الأحرف اللاتينية. S\: لا يعيد محرف فراغ، وإنما يعيد أي محرف عدا المحارف التي يعيدها s\، مثل الأحرف اللاتينية. W\: لا يعيد محرف كلمة، وإنما يعيد أي محرف عدا المحارف التي يعيدها w\، مثل الأحرف غير اللاتينية أو الفراغات. لاحظ كيف سنستخدَم هذه الأصناف في تحويل رقم الهاتف في المثال السابق إلى رقم صرف وبطريقة أقصر، وذلك بإزالة المحارف التي لا تمثل أرقامًا D\: let str = "+7(903)-123-45-67"; alert( str.replace(/\D/g, "") ); // 79031234567 يمثل المحرف نقطة أي محرف يمثل المحرف . صنفًا مميزًا من المحارف، ويطابق أي محرف عدا محرف السطر الجديد، إليك مثالًا: alert( "Z".match(/./) ); // Z كما يمكن إدراجه وسط التعبير regexp: let regexp = /CS.4/; alert( "CSS4".match(regexp) ); // CSS4 alert( "CS-4".match(regexp) ); // CS-4 alert( "CS 4".match(regexp) ); // CS 4 (الفراغ هو محرف أيضًا) لاحظ أن النقطة تطابق أي محرف، لكنها لن تبحث عن محرف غير موجود، فلا بدّ من وجود محرف حتى يعيده هذا الصنف: alert( "CS4".match(/CS.4/) ); // null, لن يعيد الصنف شيئًا لعدم وجود محرف لاحظ أن محرف النقطة يمثل أي محرف بالفعل بوجود الراية "s"، فلا تعيد النقطة عادةً محرف السطر الجديد، إذ يعيد التعبير A.B الحرفين A وB وأي محارف بينهما عدا محرف السطر الجديد n\: alert( "A\nB".match(/A.B/) ); // null (لا تطابقات) لكننا في عدة حالات نريد للنقطة أن تعني أي محارف فعلًا، بما في ذلك السطر الجديد، لذلك سنستخدم الراية s وسنحصل على المطلوب: alert( "A\nB".match(/A.B/s) ); // A\nB (match!) انتبه إلى أن المتصفح IE لا يدعم الراية s، لكن يمكن استخدام التعبير النمطي [\s\S] الذي يعيد أي محرف في أي مكان، وسنغطي ذلك لاحقًا. alert( "A\nB".match(/A[\s\S]B/) ); // A\nB (match!) ويعني التعبير [\s\S] البحث عن (محرف فراغ أو محرف لا يمثل فراغًا)، وهذا يمثل عمليًا أي محارف، ويمكن استخدام أي أزواج من الأصناف وعكسها، مثل [\d\D]، كما يمكن استخدام التعبير [^] الذي يعني البحث عن أي محرف عدا "لا شيء"، ويمكن استخدام الحيلة ذاتها إن أردنا أن يؤدي المحرف "نقطة" . كلا الوظيفتين، أي مطابقة أي محارف عدا السطر الجديد . أو مع السطر الجديد [\s\S]. انتبه أيضًا إلى الفراغات، إذ لا نكترث عادةً لوجود فراغات زائدة، وسنعتبر النص 1-5 مماثلًا للنص 1 - 5، لكن قد يخفق التعبير النمطي regexp إن لم يأخذ هذه الفراغات في الحسبان. لنحاول مثلًا أن نجد أرقامًا مفصولةً بشرطة صغيرة (-): alert( "1 - 5".match(/\d-\d/) ); // null, لا تطابق سنصلح ذلك بإضافة فراغات إلى التعبير النمطي d - \d\: alert( "1 - 5".match(/\d - \d/) ); // 1 - 5, سنجد الآن تطابقًا // \s أو يمكن استخدام الصنف: alert( "1 - 5".match(/\d\s-\s\d/) ); // 1 - 5, سيعمل أيًا فالفراغ هو محرف، ولا يمكننا إضافة أو إزالة الفراغات من تعبير نمطي، ثم نتوقع أن يعمل كما هو مطلوب، فكل المحارف التي ندخلها في التعبير النمطي ستُؤخذ في الحسبان عند البحث، بما فيها الفراغات. تعامل التعابير النمطية مع ترميز يونيكود Unicode تستخدم JavaScript ترميز يونيكود Unicode لتمثيل النصوص، حيث تُرمّز معظم المحارف باستخدام 2 بايت، وهذا ما يسمح بترميز 65536 محرف كحد أقصى، ولا يعتبر العدد السابق كافيًا لترميز كل المحارف الممكنة، لذلك ستجد أن بعض المحارف الخاصة رمِّزت باستخدام 4 بايت، مثل المحرف ? -التمثيل الرياضي للمحرف X- أو ? -الابتسامة- وبعض المحارف الهيروغليفية وغيرها، وإليك قيم Unicode لبعض المحارف الخاصة: المحرف ترميز Unicode عدد البايتات المستخدمة a 0x0061 2 ≈ 0x2248 2 ? 0x1d4b3 4 ? 0x1d4b4 4 ? 0x1f604 4 تشغل محارف -مثل a- بايتين اثنين، ومحارف -مثل ? و?- أربعة بايتات. كان ترميز Unicode في الأيام الأولى لظهور JavaScript أكثر بساطةً، حيث لم توجد محارف من أربعة بايتات، لذلك ستجد أن بعض ميزات اللغة ستتعامل معها بشكل غير صحيح، فتعتقد الخاصية length مثلًا أنها عبارة عن محرفين: alert('?'.length); // 2 alert('?'.length); // 2 والسبب في ذلك أنّ هذه الخاصية ستعامل المحرف ذا البايتات الأربعة على أنه محرفان لكل منهما بايتان فقط، وهذا أمر خاطئ، حيث ينبغي اعتبارها محرفًا واحدًا، وتُدعى "الزوج البديل" surrogate pair، وسنرى ذلك لاحقًا. تعامل التعابير النمطية regular expression المحارف الطويلة ذات البايتات الأربع مثل زوج من البايتات الثنائية، وقد يقود ذلك إلى أخطاء كما هو الحال في النصوص، لكن -وعلى خلاف النصوص- للتعابير النمطية الراية u التي تعالج هذه المشكلة، إذ يعالج التعبير المحارف ذات البايتات الأربع بشكل صحيح عند وجود هذه الراية، وسيصبح البحث متاحًا ضمن خصائص الترميز Unicode كما سنرى تاليًا. خصائص ترميز Unicode باستعمال {…}p\ لمحارف Unicode العديد من الخصائص التي تصف الفئة التي ينتمي إليها المحرف، كما تقدم معلومات متنوعةً عنها، فلو كان للمحرف الخاصية Letter فسيعني ذلك أنه ينتمي إلى أبجدية ما، كما يعني امتلاكه الخاصية Number أنه رقم، وقد يكون رقمًا عربيًا أو صينيًا وهكذا، ويمكن البحث عن محرف يمتلك خاصيةً معينةً باستخدام الصنف {...}p\، ولاستخدام هذا الصنف لا بد أن نفعّل الراية u في التعبير النمطي. يبحث التعبير {p{Letter\مثلًا عن حرف في أي لغة، كما يمكن كتابته بالشكل {p{L\، وهو اسم مستعار للخاصية Letter، ولمعظم الخصائص أسماء مستعارة قصيرة. سيجد البحث في المثال التالي ثلاثة أنواع من الأحرف: انكليزي وجورجي وكوري. let str = "A ბ ㄱ"; alert( str.match(/\p{L}/gu) ); // A,ბ,ㄱ alert( str.match(/\p{L}/g) ); // null (no matches, \p doesn't work without the flag "u") إليك الفئات الأساسية للمحارف، وفئاتها الفرعية: الحرف Letter: واسمها المستعار L. Ll: حرف صغير. Lm: حرف معدِّل modifier. Lt: عنوان. Lu: حرف كبير. Lo: غير ذلك. العدد Number: واسمها المستعار N. Nd: رقم بالصيغة العشرية. Nl: رقم حرف. No: غير ذلك. علامات الترقيم Punctuation: واسمها المستعار P. Pc: واصلة. Pd: خط فاصل dash. Pi: علامة اقتباس فاتحة. Pf: علامة اقتباس غالقة. Ps: مفتوح. Pe: مغلق. Po: غير ذلك. العلامة أو الحركة Mark: واسمها المستعار M، مثل العلامات أو الحركات فوق الأحرف وغيرها. Mc: علامة ضم مع فراغ spacing combining، وعلامة الضم هي محرف يعدّل محرفًا آخر بإضافة علامة أو حركة فوقه أو تحته وهكذا. Me: علامة ضم محيطة enclosing. Mn: علامة ضم دون فراغ non-spacing. الرمز Symbol: واسمه المستعار S. Sc: رموز عملة. Sk: رمز مُعدِّل. Sm: رمز رياضي. So: رمز آخر. الفواصل Separator: واسمها المستعار Z. Zl: خط. Zp: مقطع. Zs: فراغ. فئات أخرى: واسمها المستعار C. Cc: تحكم. Cf: تنسيق. Cn: لم يحدد بعد. Co: استخدام خاص. Cs: بديل. فلو أردنا مثلًا أحرفًا كبيرة فسنستخدم {p{Ll\، بينما نستخدم {p{p\ لعلامات الترقيم وهكذا. ستجد أيضًا بعض الفئات المشتقة، مثل: Alphabetic: واسمها المستعار Alpha، وتضم الأحرف L، والأرقام على شكل أحرف Nl، مثل Ⅻ وهو 12 بالرومانية. Hex_Digit: وتضم الأرقام الست عشرية: 0-9 وa-f. يدعم الترميز Unicode خصائص عديدةً، وسيزيد ذكرها من حجم المقالة بشكل كبير، لكن يمكنك الاطلاع على المراجع التالية: قائمة بكل الخصائص وفقًا للمحرف. قائمة بكل المحارف وفقًا للخاصية. قائمة بالاسماء المستعارة القصيرة للخصائص. قاعدة كاملة بمحارف Unicode بتنسيق نصي مع كامل خصائصها. مثال: الأعداد الست عشرية لننظر مثلًا إلى الأعداد الست عشرية المكتوبة بالشكل xFF، حيث F هو رقم ست عشري (0-1 أو A-F)، ويمكن الإشارة إلى الرقم الست عشري بالشكل {p{Hex_Digit\: let regexp = /x\p{Hex_Digit}\p{Hex_Digit}/u; alert("number: xAF".match(regexp)); // xAF مثال: الكتابة التصويرية الصينية في ترميز Unicode خاصية تحدد نظام الكتابة، ويمكن أن تأخذ قيمًا مثل: Cyrillic وGreek وArabic وHan (الصينية) وغيرها من القيم المشابهة، وللبحث عن محرف في نظام كتابة محدد علينا استخدام الخاصية <Script=<value، فللعربية مثلًا نستخدم {p{sc=Arabic\، وللصينية نستخدم {p{sc=Han\ وهكذا. let regexpHan = /\p{sc=Han}/gu; // يعيد التصويرية الصينية let regexpArab = /\p{sc=Arabic}/gu; // يعيد العربية let str = `Hello Привет 你好 جميل 123_456`; alert( str.match(regexpHan) ); // 你,好 alert( str.match(regexpArab) ); // ج,م,ي,ل مثال: رموز العملة للمحارف التي ترمز إلى العملات -مثل $ و€ و¥- خاصية في ترميز Unicode، هي {p{Currency_Symbol\، واسمها المختصر {p{Sc\، لنستخدم هذه الخاصية في الحصول على سعر منتج ضمن تنسيق يحوي رمز عملة يليه رقم: let regexp = /\p{Sc}\d/gu; let str = `Prices: $2, €1, ¥9`; alert( str.match(regexp) ); // $2,€1,¥9 وسنتعرف لاحقًا في مقالات قادمة على آلية البحث عن أعداد تحتوي عدة أرقام. محارف الارتكاز: محرفا بداية النص ^ ونهايته $ يحمل المحرفان ^ و$ معانٍ خاصةً في التعابير النمطية regular expression، وتسمى بالمرتكزات أو المرابط Anchors، حيث تبحث العلامة ^ عن تطابق في بداية النص، بينما تبحث $ عن تطابق في نهايته، لنر مثلًا إن بدأ نص ما بالكلمة "Mary": let str1 = "Mary had a little lamb"; alert( /^Mary/.test(str1) ); // true يعني النمط أن نبحث عن الكلمة "Mary" في بداية السطر حصرًا، وبشكل مشابه يمكن أن نستخدم $ للتحقق من وجود الكلمة "snow" مثلًا في نهاية نص: let str1 = "it's fleece was white as snow"; alert( /snow$/.test(str1) ); // true يمكن استخدام توابع التعامل مع النصوص startsWith/endsWith في هذه الحالات بالتحديد، لكن ينبغي استخدام التعابير النمطية في الحالات الأكثر تعقيدًا. اختبار التطابق الكامل يستخدم المرتكزان السابقان معًا $...^ لاختبار تطابق نص بشكل كامل مع النمط، مثل التحقق من مطابقة ما يدخله المستخدم للتنسيق المطلوب، لنتحقق مثلًا أن نصًا له التنسيق الزمني التالي 12:34، وهو عدد من رقمين، تليه نقطتان ":"، ثم عدد من رقمين، لذا سنستخدم التعبير d\d:\d\d\ في عالم التعابير النمطية: let goodInput = "12:34"; let badInput = "12:345"; let regexp = /^\d\d:\d\d$/; alert( regexp.test(goodInput) ); // true alert( regexp.test(badInput) ); // false يجب أن يبدأ التطابق مباشرة ًعند بداية النص ^، وينتهي بنهايته $، ويجب أن يطابق تنسيق النص تنسيق التعبير النمطي، فإذا وجدَت أي اختلافات أو محارف زائدة فستكون النتيجة "false"، وتسلك المرتكزات سلوكًا مختلفًا بوجود الراية m، وسنطلع على ذلك لاحقًا. انتبه إلى أن ليس للمرتكزات عرض، فالمرتكزات محارف اختبار، وليس لها عرض، فهي لا تطابق محارف محددةً، بل تجبر محرك التعبير النمطي للتحقق من الشرط (بداية ونهاية نص). نمط الأسطر المتعددة: تأثير الراية "m" على المرتكزان ^ و $ يُفعّل نمط الأسطر المتعددة باستخدام الراية m، التي تؤثر على سلوك المرتكزين $ و^ فقط، فلا تطابق المرتكزات في نمط الأسطر المتعددة بداية ونهاية النص فقط، بل بداية ونهاية السطر. البحث عند بداية سطر ^ يحتوي النص في المثال التالي على عدة أسطر، وسيأخذ النمط d/gm\^/ رقمًا من بداية كل سطر: let str = `1st place: Winnie 2nd place: Piglet 3rd place: Eeyore`; alert( str.match(/^\d/gm) ); // 1, 2, 3 سنحصل دون الراية m على الرقم الموجود في أول سطر فقط يطابق النمط: let str = `1st place: Winnie 2nd place: Piglet 3rd place: Eeyore`; alert( str.match(/^\d/g) ); // 1 والسبب أنّ العلامة ^ ستطابق افتراضيًا بداية النص فقط، بينما ستطابق في نمط الأسطر المتعددة بداية أي سطر. انتبه، فتعني بداية السطر رسميًا المحرف الذي يأتي بعد محرف السطر الجديد، وهكذا ستطابق العلامة ^ في نمط الأسطر المتعددة كل المحارف التي يسبقها محرف السطر الجديد n\. البحث عند نهاية سطر $ يسلك المرتكز $ السلوك السابق نفسه، إذ يجد التعبير النمطي $d\ مثلًا آخر رقم في كل سطر: let str = `Winnie: 1 Piglet: 2 Eeyore: 3`; alert( str.match(/\d$/gm) ); // 1,2,3 سيطابق المرتكز $ نهاية النص ككل دون استخدام الراية m، وبالتالي سنحصل فقط على آخر رقم. انتبه، فتعني نهاية السطر رسميًا المحرف الذي يأتي قبل محرف السطر الجديد، وهكذا ستطابق العلامة $ في نمط الأسطر المتعددة كل المحارف التي تسبق محرف السطر الجديد n\ مباشرةً. البحث عن محرف السطر الجديد n\ بدل المرتكز $ يمكن البحث عن سطر جديد باستخدام المحرف n\ بدلًا من البحث عن المرتكزين ^ و$، لكن ما الفرق؟ سنبحث في المثال التالي عن d\n\بدلًا من $d\: let str = `Winnie: 1 Piglet: 2 Eeyore: 3`; alert( str.match(/\d\n/gm) ); // 1\n,2\n ستجد الشيفرة تطابقين بدلًا من ثلاث، وذلك لعدم وجود سطر جديد بعد الرقم 3، على الرغم من وجود مرتكز نهاية النص الذي يطابق $، أما الاختلاف الآخر فهو أنّ n\محرف، وسيظهر ضمن النتيجة، وليس مثل المرتكزين ^ و$ اللذين يحددان شرط البحث في بداية ونهاية النص، وبالتالي سنستخدم n\ إذا أردنا ظهور محرف السطر الجديد في النتيجة، وإلا استخدمنا ^ و$. حدود الكلمة: b\ حدود الكلمة اختبار، مثل ^ و$، حيث يشير وجود العلامة b\ بأنّ الموقع الذي وصل إليه محرك بحث التعابير النمطية هو حد لكلمة، وتوجد ثلاثة مواقع مختلفة يمكن اعتبارها حدودًا لكلمة، وهي: في بداية نص، إذا كان أول محرف نصي فيه هو محرف كلمة w\. بين محرفين في نص، إذا كان أحدهما محرف كلمة w\ والآخر ليس كذلك. في نهاية نص، إذا كان آخر محرف فيه هو محرف الكلمة w\. يمكن أن تجد التعبير bJava\b\ مثلًا في النص !Hello, Java عندما تكون الكلمة Java منفصلةً عن غيرها، لكنك لن تحصل على نفس النتيجة في النص !Hello, JavaScript alert( "Hello, Java!".match(/\bJava\b/) ); // Java alert( "Hello, JavaScript!".match(/\bJava\b/) ); // null في الشكل التالي ستمثل المواقع المشار إليها العلامة b\ في النص !Hello, Java: لذلك فهي تطابق النمط bHello\b\ لأن: بداية النص تتطابق مع العلامة b\. ثم تتطابق مع الكلمة Hello. ثم تتطابق مع العلامة b\ مرةً أخرى، كما لو أنها بين المحرف o والفاصلة. إذًا سنعثر على النمط bHello\b\، لكن ليس النمط bHell\b\، لعدم وجود علامة حد الكلمة b\ بعد المحرف l، كما لن نحصل على النمط Java!\b لأن علامة التعجب ليست محرف كلمة w\، وبالتالي لا وجود لعلامة حد الكلمة b\ بعده. alert( "Hello, Java!".match(/\bHello\b/) ); // Hello alert( "Hello, Java!".match(/\bJava\b/) ); // Java alert( "Hello, Java!".match(/\bHell\b/) ); // null (no match) alert( "Hello, Java!".match(/\bJava!\b/) ); // null (no match) يمكن استخدام b\ مع الأرقام أيضًا، إذ يبحث النمط b\d\d\b\ مثلًا عن عدد من رقمين -عدد بخانتين- منفصل عن غيره، أي يبحث عن عدد بخانتين غير محاط بمحارف الكلمة w\، مثل محارف الفراغات أو علامات الترقيم أو مرتكزات بداية ونهاية نص. alert( "1 23 456 78".match(/\b\d\d\b/g) ); // 23,78 alert( "12,34,56".match(/\b\d\d\b/g) ); // 12,34,56 انتبه، لا تعمل العلامة b\ مع الأبجديات غير اللاتينية، إذ تتحقق العلامة b\ من وجود محرف كلمة w\ في طرف نص وعدم وجوده في الطرف الآخر، وبما أنّ محرف الكلمة w\ سيدل على الأحرف اللاتينية a-z أو الأرقام أو الشرطة السفلية فقط، فلن يعمل مع محارف اللغات الأخرى، مثل العربية أو الصينية. المحارف الخاصة وطريقة تجاوزها يُستخدم المحرف \ كما رأينا للإشارة إلى أصناف المحارف -مثل d\- فهو إذًا محرف خاص، وستجد محارف خاصةً أخرى تحمل معنىً خاصًا في التعابير النمطية، وتستخدم لتنفيذ عمليات بحث أكثر قدرةً، وهذه المحارف هي: [ ]\ ^ $ . | ? * + ( )، ولا حاجة طبعًا لتذكر هذه القائمة، فسنتعامل مع كل محرف منها بشكل منفصل وستحفظها تلقائيًا. التجاوز لنفترض أننا نريد البحث عن محرف النقطة بذاته، وليس أي محرف، في هذه الحالة -وبقية الحالات التي نريد فيها البحث عن محرف خاص بحد ذاته كما لو أنه محرف عادي- سنضع المحرف \ قبله، أي .\ في حالة النقطة، وتُدعى هذه العملية بالتجاوز أو تهريب محرف escaping a character، إليك مثالًا: alert( "Chapter 5.1".match(/\d\.\d/) ); // 5.1 (تطابق!) alert( "Chapter 511".match(/\d\.\d/) ); // null تعتبر الأقواس محارف خاصةً أيضًا، فلا بد من البحث عنها وفق النمط )\، ويبحث المثال التالي عن النص "()g": alert( "function g()".match(/g\(\)/) ); // "g()" إذا أردنا البحث عن المحرف الخاص \ فلا بدّ من مضاعفته \\: alert( "1\\2".match(/\\/) ); // '\' الشرطة المائلة "/" لا تعتبر الشرطة المائلة '/' من المحارف الخاصة، لكنها تستخدم في JavaScript لفتح وإغلاق التعابير النمطية /...pattern.../ فلا بد من تجاوزها أيضًا، إليك الطريقة التي نبحث فيها عن الشرطة المائلة: alert( "/".match(/\//) ); // '/' لا داعي لتجاوز الشرطة المائلة في الحالة التي لا نستخدم فيها النمط /.../، بل ننشئ كائن تعبير نمطي باستخدام new RegExp: alert( "/".match(new RegExp("/")) ); // / يجد الإنشاء باستخدام new RegExp عندما ننشئ تعبيرًا نمطيًا باستخدام new RegExp، فلا حاجة لتجاوز المحرف / لكن يتحتم علينا تجاوز غيره. تأمل الشيفرة التالية: let regexp = new RegExp("\d\.\d"); alert( "Chapter 5.1".match(regexp) ); // null لقد نجح البحث المشابه في أمثلة سابقة عندما استخدمنا /\d\.\d/، لكن لن يعمل البحث باستخدام ("new RegExp("\d\.\d فما السبب؟ السبب أن الشرطة المائلة ستُستهلك من قبل النص، وكما نتذكر فللنصوص العادية محارفها الخاصة -مثل n\- وقد استخدمت الشرطة المعكوسة backslash للتهريب. إليك مثالًا: alert("\d\.\d"); // d.d تستهلك إشارتا التنصيص الشرطة المعكوسة وتفسرها بطريقتها، فمثلًا: تتحول n\ إلى محرف السطر الجديد. تتحول u1234\ إلى محرف Unicode بهذا الرمز. وفي حال لم يوجد معنىً لوجود الشرطة المعكوسة -مثل d\ أو z\- فستُزال هذه الشرطة ببساطة. يحصل new RegExp على نص بلا شرطات معكوسة، لهذا لن يفلح البحث، ولحل المشكلة لا بدّ من مضاعفة الشرطة المعكوسة، لأن إشارتي التنصيص تحولان \\ إلى \. let regStr = "\\d\\.\\d"; alert(regStr); // \d\.\d (عملية صحيحة) let regexp = new RegExp(regStr); alert( "Chapter 5.1".match(regexp) ); // 5.1 خلاصة يتألف التعبير النمطي من نمط pattern، ورايات flags اختيارية هي: g وi وm وu وs وy. لا تختلف عملية البحث دون استخدام الرايات والرموز الخاصة عن أي عملية بحث اعتيادية ضمن نص. يبحث التابع (str.match(regexp عن كل حالات التطابق التي يحددها التعبير regexp بوجود الراية g، وإلا فسيبحث عن أول تطابق فقط. يستبدل التابع (str.replace(regexp, replacement التطابقات التي يحددها التعبير regexp بالنص replacement، حيث يستبدل التطابقات كلها عند وجود الراية g، وإلا فسيستبدل أولها فقط. يعيد التابع (regexp.test(str القيمة true إن وجد تطابقًا على الأقل مع التعبير المستخدم، وإلا فسيعيد false. هنالك أصناف محارف متعددة، هي: d\: للأرقام. D\: لغير الأرقام. s\: لمحارف الفراغات والجدولة والسطر الجديد. S\: لكل المحارف عدا محارف الفراغات والجدولة والسطر الجديد. w\: كل الأرقام والأحرف اللاتينية والشرطة السفلية (_). W\: كل المحارف عدا ما ذكرناه في السطر السابق. .: أي محرف عدا محرف السطر الجديد، وأي محرف تمامًا عند استخدام الراية "s". تؤمن مجموعة المحارف Unicode التي تستخدمها JavaScript العديد من الميزات المتعلقة بالمحارف، مثل تحديد اللغة التي ينتمي إليها محرف معين، وهل يمثل المحرف علامة ترقيم وغيرها، ويتطلب ذلك استخدام الراية u. تدعم الراية u استخدام رموز Unicode في التعابير النمطية، ويعني ذلك أمرين اثنين: ستتعامل الميزات والخصائص مع المحارف المكونة من أربع بايتات بشكل صحيح ومثل محرف واحد، وليس مثل محرفين يتكون كل منهما من بايتين. يمكن استخدام خصائص Unicode في عمليات البحث من خلال {...}\p. يمكن البحث عن كلمات في لغة محددة باستخدام خصائص Unicode، بالإضافة إلى البحث عن محارف خاصة مثل إشارات التنصيص، العملات،…. للبحث عن المحارف الخاصة [ \ ^ $ . | ? * + ( ) بذاتها، لا بدّ من وضع الشرطة المائلة \ قبلها لتجاوز الوظيفة الخاصة للمحرف. ينبغي تجاوز الشرطة المعكوسة \ أيضًا في النمط /.../، لكن ليس ضمن new RegExp. عند تمرير نص إلى new RegExp فلا بدّ من مضاعفة الشرطة المعكوسة، لأن إشارة التنصيص \\ تستهلك إحداهما. ترجمة -وبتصرف- للفصول: Patterns and flags character classes Unicode: flag "u" and class \p{…} Anchors: string start ^ and end $ Multiline mode of anchors ^ $, flag "m" Word boundary: \b Escaping special characters من سلسلة The Modern JavaScript Tutorial. اقرأ أيضًا المقال السابق: مكونات الويب: التعامل مع شجرة DOM الخفية التعابير النمطية (regexp/PCRE) في PHP مقدمة في التعابير النمطية Regular Expressions كيفية التعامل مع النصوص في البرمجة
- 1 تعليق
-
- 1
-
الآن وبعد أن تعرّفت على سكراتش Scratch في المقال السابق، البرمجة باستخدام سكراتش Scratch في راسبيري، ستكتشف في هذا المقال كيفية كتابة شيفرة نصية باستخدام لغة البرمجة بايثون Python. سُميت اللغة بايثون Python تيمنًا بالفرقة الكوميدية مونتي بايثون Monty Python، وتطورت هذه اللغة التي أنشأها غويدو فان روسوم Guido van Rossum من مشروع هواة أُطلق للمرة الأولى عام 1991 إلى لغة البرمجة المحبوبة التي تقف خلف الكثير من البرمجيات والمشاريع حاليًا. بايثون هي لغة برمجةٍ نصية على عكس اللغة البصرية سكراتش؛ حيث تُكتب التعليمات باستخدام لغةٍ مبسطةٍ وبتنسيقٍ محددٍ ينفذها الحاسوب لاحقًا. تُعد بايثون خطوةً ثانيةً رائعةً في طريق احتراف البرمجة بالنسبة للذين اكتسبوا خبرةً في استخدام سكراتش، حيث تمُدّهم بمرونةٍ أكبر وبيئة عملٍ برمجية تقليدية. وليس الغرض من هذا الكلام إيصال فكرةٍ أن اللغة صعبة التعلم، حيث يمكن لأي مهتم وبقليلٍ من التدريب كتابة برامج بايثون لأغراضٍ مختلفة انطلاقًا من آلة حاسبة بسيطة، وصولًا إلى ألعاب على درجة من التعقيد. بُني هذا المقال على المفاهيم والمصطلحات التي قدمناها في مقال البرمجة باستخدام لغة سكراتش، لذلك ستجد هذا المقال أسهل إدراكًا إذا نفذت التمارين التي شُرحت في المقال السابق، وننصح بالعودة والعمل عليها إذا لم تفعل ذلك بعد. ثوني Thonny: بيئة تطوير بايثون المتكاملة تتألف الواجهة الرئيسية للبيئة من الأجزاء التالية: A- شريط الأدوات: يقدم لك "النمط البسيط simple mode" للبيئة شريط أدواتٍ بأيقوناتٍ مريحةٍ للمستخدم، تساعدك في إنشاء وحفظ وتحميل وتشغيل برامج بايثون، كما تساعدك على اختبار برامجك بطرقٍ عدة. B- منطقة كتابة الشيفرة: وهي المنطقة التي تكتب فيها تعليمات بايثون، وتقسم إلى منطقةٍ رئيسية لكتابة الشيفرة، ومنطقةٍ جانبية طولانية تشكل هامشًا لإظهار أرقام الأسطر. C- منطقة مفسّر أوامر بايثون Python shell: ويسمح لك المُفسِّر في كتابة تعليماتٍ مستقلةٍ، ثم تنفيذها بضغط المفتاح Enter، كما تزوّدك بمعلوماتٍ عن البرامج الجاري تنفيذها. D- منطقة المتغيرات: ويظهر فيها أي متغيرٍ تنشئه في برنامجك، بالإضافة إلى قيمته الحالية، لسهولة المراجعة. انطلق مع بايثون ستجد ثوني مثل غيره من البرامج المثبتة مسبقًا على راسبيان ضمن قائمة راسبيري باي. انقر على الأيقونة ثم انقل مؤشر الفأرة إلى قسم البرمجة Programming، ثم انقر على Thonny Python IDE. ستظهر بعد ثوانٍ واجهة النمط البسيط لثوني والتي تُحمَّل افتراضيًا. يُعَد ثوني Thonny حزمة برمجياتٍ تُعرف باسم بيئة التطوير المتكاملة Integrated Development Environment -أو اختصارًا IDE-. إن التسمية معقدةً في الواقع لكنها سهلة الشرح؛ فهي تجمع أو تُكامل جميع الأدوات المختلفة التي تحتاجها لكتابة وتطوير برمجيات ضمن واجهة عمل أو بيئةٍ واحدة. هناك الكثير من بيئات التطوير التي يدعم بعضها عدة لغات برمجةٍ مختلفة، بينما يدعم البعض الآخر لغةً واحدةً مثل ثوني. وعلى خلاف سكراتش التي تقدم لك كتلًا برمجيةً بصريةً تبني من خلالها برنامجك، ستكتب تعليماتك مثل أي لغة برمجةٍ تقليدية عند استخدام بايثون. ابدأ برنامجك الأول بالنقر ضمن نافذة Python shell أسفل ويسار نافذة ثوني، واكتب التعليمات التالية، ثم اضغط المفتاح Enter. print("Hello, World!") سيعمل البرنامج مباشرةً ويُظهر لك العبارة "!Hello, World" ضمن نفس النافذة، كما هو موضحٌ في الشكل التالي، وهذا ما طلبته تمامًا. فما يفعله مفسر سطر الأوامر shell هو قراءة تعليماتك وتفسير معناها. يًدعى هذه الأسلوب الأسلوب التفاعلي والذي يشابه إلى درجةٍ ما الحوار وجهًا إلى وجه مع أحدهم؛ فعندما تنهي ما تقوله سيرد عليك الشخص الآخر وينتظر حتى تقول شيئًا ما من جديد. شكل 1-5 يطبع بايثون الرسالة Hello, World في نافذة مفسر الأوامر يمكنك كتابة التعليمات في منطقة الشيفرة إلى يسار نافذة ثوني أيضًا. انقر على النافذة واكتب الشيفرة السابقة تمامًا، ثم اضغط المفتاح Enter. لن يحدث شيء هذه المرة، بل سينتقل المؤشر إلى سطرٍ جديد في نافذة الشيفرة، ولا بدّ أن تنقر أيقونة التشغيل Run الموجودة في شريط أدوات ثوني من أجل تنفيذ تعليماتك، حيث سيُطلب منك حفظ برنامجك، لهذا اختر اسمًا مناسبًا، مثل "Hello World"، ثم انقر زر الحفظ Save. ستُطبع الآن هذه العبارة في نافذة المفسِّر بمجرد حفظ برنامجك. >>>> %Run 'Hello World.py' Hello, World! يمثّل السطر الأول توجيهًا للمفسر بتشغيل البرنامج الذي حفظته لتوّك، بينما يمثل الثاني نتيجة التنفيذ. تهانينا، لقد كتبت ونفذت أول برامج بايثون بالاسلوبين التفاعلي والتقليدي (الكتابة في نافذة الشيفرة). شكل2-5 تشغيل برنامجك البسيط الحلقات وإزاحة التعليمات تستخدم سكراتش كما رأينا كتلًا تمثّل شيفراتٍ محضرةً مسبقًا، ثم تربط بين هذه الكتل مثل قطع لعبة الأحجية؛ بينما تمتلك بايثون طريقتها الخاصة في التحكم بتسلسل التعليمات التي يُبنى عليها البرنامج وهي الإزاحة indentation. أنشئ برنامجًا جديدًا بالنقر على الأيقونة جديد New في شريط أدوات ثوني، حيث لن يغلق ثوني برنامجك القديم بل سيفتح إلى جواره نافذةً فرعيةً جديدة. اكتب في النافذة الجديدة التعليمات التالية: print("Loop starting!") for i in range (10): تطبع التعليمة في السطر الأول رسالةً بسيطةً ضمن المفسِّر تمامًا كما فعل البرنامج السابق، بينما يبدأ السطر الثاني تشكيل حلقةٍ محدودةٍ تعمل بنفس الطريقة التي عرفناها في سكراتش؛ حيث يُسنَد العداد i إلى الحلقة ويُعطى سلسلةً من الأعداد عبر التعليمة range والتي تحدد رقم البداية 0 صعودًا حتى 9 دون بلوغ العدد 10. يُخبر رمز النقطتين المتعامدتين ":" بايثون أن التعليمات التالية ستكون جزءًا من الحلقة؛ حيث نلاحظ وجود التعليمات التي ينبغي أن تكون ضمن الحلقة فعلًا داخل الكتلة التي تشبه شكل الحرف "C" في سكراتش؛ بينما يكون الأمر مختلف في بايثون، حيث يجب أن تُزاح بداية أسطر التعليمات الموجودة داخل الحلقة بمقدار أربع مسافاتٍ فارغة بالموازنة مع سطر تعريف الحلقة، وهذا ما يفعله ثوني تلقائيًا عند الضغط على مفتاح Enter بعد السطر الثاني وكتابة التعليمة التالية: print("Loop number", i) ستدفع المسافات الفارغة هذا السطر إلى اليمين موازنةً بالأسطر السابقة، وهذا هو الأسلوب الذي تعتمده بايثون للتمييز بين التعليمات داخل وخارج الحلقة، ونقول عندها أن التعليمات متداخلة Nested. لاحظ أنه بمجرد ضغط المفتاح Enter للانتقال إلى السطر التالي، سيستمر ثوني بإزاحة السطر آخذًا بالحسبان أن السطر الجديد لا يزال ضمن الحلقة، وللتراجع عن ذلك ضع المؤشر في بداية السطر واضغط الزر BACKSPACE مرةً واحدة. print("Loop finished!") وهكذا يكتمل برنامجك ذو الأسطر الأربعة، حيث سيُنفَّذ السطر الأول مرةً واحدةً كونه خارج الحلقة وكذلك السطر الرابع، بينما سيُنفّذ السطر الثالث عشرة مرات لوقوعه ضمن الحلقة التي يُعرّفها السطر الثاني: print("Loop starting!") for i in range (10): print("Loop number", i) print("Loop finished!") انقر على أيقونة التشغيل Run، احفظ البرنامج، وانظر إلى نافذة المفسّر لترى نتيجة التنفيذ: Loop starting! Loop number 0 Loop number 1 Loop number 2 Loop number 3 Loop number 4 Loop number 5 Loop number 6 Loop number 7 Loop number 8 Loop number 9 Loop finished! شكل 3-5 تنفيذ الحلقة إزاحة التعليمات أحد مصادر قوة بايثون، وفي نفس الوقت السبب الأكثر شيوعًا لكي لا يعمل البرنامج كما هو متوقع؛ فعندما تبحث عن أخطاء برنامج بالعملية التي تُدعى تنقيح الأخطاء Debugging، تحقق دومًا من الإزاحة وخاصةً عند ترتيب حلقاتٍ ضمن حلقات. يدعم بايثون أيضًا الحلقات اللامتناهية التي تُنفّذ ما بداخلها من تعليمات دون توقف. لتشاهد ذلك بدّل السطر الثاني من البرنامج ليصبح: while True نفِّذ البرنامج الآن وستحصل على رسالة خطأ: name 'i' is not defined لأنك حذفت السطر الذي أنشأ المتغير i وأسند قيمةً إليه. لإصلاح المشكلة اجعل السطر الثالث على النحو التالي: print("Loop running!") انقر على أيقونة التشغيل Run، وقد ترى العبارة "!Loop starting" إذا كنت سريعًا بما يكفي، لأن ما ستشاهده تاليًا تكرارُ لا ينتهي للعبارة "!Loop running"، ولن ترى بالطبع العبارة "!Loop finished" لعدم وجود نهاية للحلقة، ولدى الانتهاء من طباعة رسالة "!Loop running" يعود مجددّا إلى البداية من جديد، وبالتالي الحلقة لا متناهية. شكل 4-5 حلقة لا متناهية ستستمر حتى توقف البرنامج بنفسك انقر على أيقونة إيقاف Stop في شريط أدوات ثوني لإيقاف عمل برنامجك (مقاطعة عمل البرنامج)، فستظهر لك رسالةً في نافذة المفسِّر وسيتوقف البرنامج دون أن يصل حتى إلى السطر 4. العبارات الشرطية والمتغيرات المتغيرات أدواتٌ برمجيةٌ هامةٌ جدًا في جميع اللغات ولا تقتصر مهمتها على التحكم بالحلقات فقط. ابدأ برنامجًا جديدًا بالنقر على الأيقونة جديد New في شريط أدوات ثوني، ثم اكتب الشيفرة التالية في نافذة الشيفرة: userName = input ("what is your name? ") انقر على الأيقونة Run واحفظ برنامجك بالاسم "Name Test"، ثم راقب ما يحدث في نافذة المفسِّر. ستُسأل عن اسمك، لذلك اكتبه في نافذة المفسِّر واضغط المفتاح Enter. وبما أن برنامجك مكوّنٌ من تعليمةٍ واحدة، فلن يحدث أكثر من ذلك، لكن ستلاحظ ظهور المتغير وقيمته تلقائيًا في نافذة المتغيرات إلى يمين نافذة ثوني كما هو موضحٌ في الشكل التالي، وستبقى المتغيرات في نافذتها حتى عند توقف البرنامج، ليسهل عليك إدراك ما فعله البرنامج. شكل 5-5 تساعدك نافذة المتغيرات على تتبع المتغيرات وقيمها وحتى يقدّم البرنامج شيئًا مفيدًا، ضع عبارة شرطية ضمنه بكتابة التعليمات التالية: if userName == "Clark Kent": print("You are Superman!") else: print("You are not Superman!") لاحظ أن ثوني سيزيح الشيفرة تلقائيًا إذا وجد ذلك ضروريًا، لكنه لا يعرف متى يتوقف عن إزاحة الشيفرة؛ لذلك عليك حذف المسافات الفارغة بنفسك. انقر على الأيقونة "Run" واكتب اسمك في نافذة المفسِّر؛ فإذا صدف وكان اسمك Clark Kent، سترى الرسالة "You are Superman". شغل البرنامج من جديد واحرص أن يكون اسمك Clark Kent بحرفي C وK كبيرين كما هو في الشيفرة تمامًا، سيميّز البرنامج عندها أنك Superman فعلًا، كما هو موضحٌ في الشكل التالي. شكل 6-5 ألا يحب أن تكون في الخارج تحمي العالم يُستخدم الرمز "==" في بايثون للموازنة المباشرة؛ أي سيرى إذا كانت قيمة المتغير userName مطابقة للنص الموجود في برنامجك. يمكنك استخدام عوامل موازنة comparison operators أخرى إذا كنت تتعامل مع الأعداد، مثل العامل "<"، الذي يعني أكبر من، و">"، الذي يعني أصغر من، و"<=" الذي يعني أكبر من أو يساوي، و">="، الذي يعني أصغر من أو يساوي، كما يوجد العامل "=!" ويعني لا يساوي وهو المعاكس المباشر للعامل "==". يمكن استخدام عوامل الموازنة ضمن الحلقات أيضًا. احذف الأسطر من 2 إلى 5 واكتب الشيفرة التالية: while userName != "Clark Kent": print("You are not Superman - try again!") userName = input ("What is your name? ") print("You are Superman!") انقر على الأيقونة Run مرةً أخرى، ستلاحظ سؤال البرنامج عن اسمك مرارًا وتكرارًا حتى تؤكد له أنك "Superman"، كما هو موضحٌ في الشكل التالي، أي حتى تكتب الاسم "Clark Kent" كما لو أنه كلمة سر من نوعٍ ما. إذًا، عليك كتابة الاسم "Clark Kent" حتى تخرج من الحلقة، أو تنقر الأيقونة Stop في شريط أدوات ثوني. تهانينا، لقد تعلمت الآن استخدام العبارات الشرطية وعوامل الموازنة. شكل 7-5 سيستمر البرنامج بسؤالك عن اسمك حتى تكتب Clark Kent المشروع الأول: السلحفاة والثلج طالما أنك تعرّفت على الطريقة التي تعمل بها بايثون فقد حان الوقت للعمل مع الرسوميات وتصميم بلورات الثلج باستخدام أداة تُدعى turtle "سلحفاة". تُصنَّع الأشكال الفيزيائية للروبوتات وفقًا لأسماء الحيوانات التي تحاكيها، لهذا تُصنّع السلاحف على شكل قلمٍ يتحرك وفق خطٍ مستقيم ويلتف ويرتفع أو ينخفض؛ وهذا يعني برمجيًا أن تتحرك وتقف وترسم خطًا عندما تتحرك. لا تمتلك بايثون أداة سلحفاةٍ مدمجة مثل غيرها من اللغات، مثل لغة Logo وتوزيعاتها، لكنها تأتي ضمن مكتبةٍ ذات إضافاتٍ برمجية لتزيد قدرة بايثون. وتُعرف المكتبات Libraries بأنها تجميعاتٌ Bundles لشيفراتٍ تضيف تعليماتٍ جديدة لتوسيع إمكانيات بايثون، ويمكن أن تضمها إلى برامجك من خلال الأمر import. أنشئ برنامجًا جديدًا بالنقر على الأيقونة New، ثم اكتب الشيفرة التالية: import turtle عند استخدام التعليمات التي تتيحها مكتبةٌ ما، لا بُدّ من كتابة اسم المكتبة تليها نقطةٌ، ثم اسم التعليمة. قد يغدو هذا الأمر مزعجًا مع الوقت، لذلك يمكن إسناد هذا الأمر إلى متغيرٍ باسمٍ مختصر قد يكون حرفًا واحدًا مثلًا، لكننا سنختار اسم حيوانٍ أليفٍ لهذه السلحفاة على النحو التالي: pat = turtle.Turtle() ولاختبار السلحفاة لا بدّ أن نعطيها عملًا لتنفّذه، لذلك اكتب الشيفرة التالية: pat.forward(100) انقر الآن على أيقونة التشغيل Run، ثم احفظ برنامجك باسم "Turtle Snowflakes". ستظهر نافذةٌ رسوميةٌ جديدةٌ باسم Turtle Graphics، عندما يعمل البرنامج لتعرض نتيجة التنفيذ، وسترى تحرٌّك السلحفاة "pat" للأمام مقدار 100 وحدةٍ راسمةً خطًا مستقيمًا، كما هو وضحٌ في الشكل التالي. شكل 8-5 تتحرك السلحفاة إلى الأمام وترسم خطًا مستقيمًا عُد إلى نافذة ثوني الرئيسية إذا حجبتها النافذة الرسومية، وذلك بالنقر على نافذة ثوني أو تصغير النافذة الرسومية. انقر على أيقونة الإيقاف Stop لإغلاق النافذة الرسومية. نظرًا لمشقة كتابة كل تعليمة من تعليمات حركة السلحفاة، سنستعمل حلقةً Loop، لذلك احذف السطر الثالث واكتب الشيفرة التالية مكانه: for i in range(2): pat.forward(100) pat.right(60) pat.forward(100) pat.right(120) شغل البرنامج لتشاهد متوازي الأضلاع الذي ترسمه السلحفاة والموضح بالشكل التالي. شكل 9-5 بدمج الانعطافات والحركات يمكن رسم الأشكال ولتحويل الشكل إلى بلورة ثلج، أوقف البرنامج، ثم ضع الحلقة السابقة ضمن حلقةٍ جديدة بتعديل السطر 3 ليصبح على النحو التالي: for i in range(10): ووضع سطر الأوامر التالي في نهاية البرنامج: pat.right(36) لن يعمل البرنامج حاليًا لعدم إزاحة الحلقة الداخلية بصورةٍ صحيحة، لذلك أزح الأسطر من 4 إلى 8 إلى الأمام بإضافة أربع مسافاتٍ فارغة قبل كلٍ منها. إليك الشيفرة الصحيحة: import turtle pat = turtle.Turtle() for i in range(10): for i in range(2): pat.forward(100) pat.right(60) pat.forward(100) pat.right(120) pat.right(36) انقر على أيقونة التشغيل Run وشاهد كيف ترسم السلحفاة متوازي الأضلاع كما فعلت سابقًا، ثم تستدير بزاوية 36 درجة وترسم متوازي أضلاعٍ آخر وتستدير بنفس المقدار وترسم آخرًا وهكذا حتى ترسم 10 أشكالٍ متداخلة، كما هو موضحٌ في الشكل التالي. شكل 10-5 تداخل متوازيات الأضلاع على الشاشة تعطي شكلًا مشابهًا لبلورة الثلج يمكن رسم السلحفاة في بايثون بعدة ألوان. أضف الأسطر التالية قبل السطر الثالث لتدفع الأسطر إلى الأسفل: turtle.Screen().bgcolor("blue") pat.color("cyan") شغَّل برنامجك من جديد لترى تأثير الشيفرة، حيث سيتغير لون خلفية النافذة الرسومية إلى الأزرق ولون بلورة الثلج لتصبح رمادية، كما هو مبينٌ في الشكل التالي. تغيير لون الخلفية ولون شذرة الثلج يمكن اختيار الألوان عشوائيًا من قائمةٍ أيضًا وذلك باستخدام المكتبة random. عُد إلى بداية برنامجك واكتب الشيفرة التالية لتصبح في السطر الثاني: import random غيَّر لون الخلفية في السطر الرابع من اللون الأزرق "blue" إلى اللون الرمادي "grey"، ثم أنشئ متغيرًا جديدًا يُدعى "colours" بكتابة الشيفرة التالية لتحل مكان السطر الخامس: colours = ["cyan", "purple", "white", "blue"] يُعرف النوع السابق من المتغيرات بالقائمة list ويُحدَّد بقوسين مربعين. وضعنا في القائمة بعض الألوان المحتملة لبلورة الثلج وعليك الآن أن تخبر بايثون باختيار لونٍ منها في كل مرةٍ تُكرر فيها الحلقة. أضف في آخر البرنامج الشيفرة التالية وانتبه أن تبتعد عن بداية السطر أربع مسافاتٍ فارغة لتكون ضمن الحلقة الخارجية: pat.color(random.choice(colours)) انقر الآن على أيقونة التشغيل Run وسيرسم البرنامج بلورة الثلج التي تشبه نجمة النينجا من جديد، ولكن ستختار بايثون هذه المرة لونًا مختلفًا لكل متوازي أضلاعٍ من قائمة الألوان السابقة، وهذا يعطي البلورة ألوانًا مبهجة، كما هو موضحٌ في الشكل التالي. شكل12-5 استخدام ألوان عشوائية لرسم بتلات شذرة الثلج ولتظهر البلورة أقرب إلى بلورة الثلج الحقيقية وليس إلى نجمة النينجا، أضف الشيفرة التالية ابتداءً من السطر السادس وتحت قائمة الألوان: pat.penup() pat.forward(90) pat.left(45) pat.pendown() ستدفع التعليمتان penup وpendown بالقلم نحو الورقة وخارجها في روبوت سلحفاة واقعي، لكنهما تخبران السلحفاة في بايثون بالبدء برسم الخط أو التوقف. سنستخدم الآن دالةً بدلًا من الحلقة؛ والدالة function هي مجموعةٌ من التعليمات الممكن استدعائها سويةً في أي وقت كما لو أنك تصمم تعليمة بايثون خاصةٍ بك. ابدأ بحذف شيفرة رسم بلورات الثلج المبنية على متوازي الأضلاع وهي الشيفرة الموجودة بين الأمر: pat.color("cyan") في السطر 10 ضمنًا وحتى الأمر: pat.right(36) في السطر 17. اترك الأمر: pat.color(random.choice(colours)) بعد أن تضع الرمز "#" في بداية السطر، ويُعرف ذلك بتعليق السطر؛ أي أن بايثون سيتجاهل تنفيذه. يمكن أيضًا استخدام "#" لإضافة ملاحظاتٍ عن شيفرتك، لجعلها سهلة الفهم وخاصةً عندما تعود لقرائتها بعد عدة أشهرٍ، أو إذا أردت إرسالها إلى شخصٍ آخر. أنشئ دالتك التي سنسميها ()branch، وذلك بكتابة الشيفرة التالية في السطر العاشر تحت تعليمة ()pat.pendown مباشرةً: def branch(): ستعرف التعليمة السابقة الدالة ()branch، وسيضيف ثوني تلقائيًا إنزياحًا للتعليمات التي ستأتي ضمن الدالة. اكتب الشيفرة التالية وانتبه جيدًا إلى الإزاحة، لأنك سترتّب شيفراتٍ متداخلة وفق ثلاث سوياتٍ من الإزاحة: for i in range(3): for i in range(3): pat.forward(30) pat.backward(30) pat.right(45) pat.left(90) pat.backward(30) pat.left(45) pat.right(90) pat.forward(90) أخيرًا، أضف حلقةً إلى نهاية برنامجك لكن قبل سطر الاختيار العشوائي للون، والذي وضعت الرمز "#" قبله، وذلك لاستدعاء الدالة التي أنشأتها: for i in range(8): branch() pat.left(45) ستبدو الشيفرة الكاملة للمشروع على النحو التالي: import turtle import random pat = turtle.Turtle() turtle.Screen().bgcolor("grey") colours = ["cyan", "purple", "white", "blue"] pat.penup() pat.forward(90) pat.left(45) pat.pendown() def branch(): for i in range(3): for i in range(3): pat.forward(30) pat.backward(30) pat.right(45) pat.left(90) pat.backward(30) pat.left(45) pat.right(90) pat.forward(90) for i in range(8): branch() pat.left(45) # pat.color(random.choice(colours)) انقر على أيقونة التشغيل Run وراقب السلحفاة وهي ترسم بلورة الثلج وفقًا لتعليماتك. تهانينا، تبدو بلورة الثلج الآن أكثر واقعية، وهذا موضحٌ في الشكل التالي. شكل 13-5 تمنح التفرعات الإضافية شذرة الثلج شكلًا أكثر واقعية المشروع الثاني: إيجاد الاختلافات المخيفة يمكن لبايثون التعامل أيضًا مع الصور والأصوات، وسنستخدم ذلك في تصميم لعبة إيجاد الاختلاف بين صورتين لكن مع نكهةٍ مرعبةٍ لتفاجئ اللاعب. يحتاج المشروع إلى صورتين، الأولى هي الصورة الرئيسية وتتكون من قسمين متناظرين مع اختلافاتٍ محدودةٍ جدًا، والثانية هي الصورة المرعبة التي ستظهر فجأة. كما نحتاج أيضًا إلى ملفٍ صوتي لتعزيز التأثير المرعب. انقر على أيقونة راسبيري باي وانتقل إلى المتصفح كروميوم، ثم اكتب في شريط الأدوات العنوان rpf.io/spot-pic وبعدها اضغط المفتاح Enter. بعد ان يحمل المتصفح الصور، انقر عليها بالزر اليميني للفأرة واختر "حفظ الصورة باسم Save image as"، ثم خزنها في المجلد home/pi/، وكرّر نفس الأمر لتنزيل الصورة الأخرى من العنوان rpf.io/scary-pic. يختلف الأمر بالنسبة للملف الصوتي الموجود على العنوان rpf.io/scream، حيث سيبدأ المتصفح كروميوم بتنزيل الملف تلقائيًا إلى مجلد "التنزيلات Downloads"، لذلك لا بُدّ من نقله إلى المجلد home/pi/. لنقل الملف، انقر على أيقونة راسبيري باي مرورًا بفئة البرامج الملحقة Accessories، ثم انقر على برنامج مدير الملفات File Manager، وافتح مجلد التنزيلات Downloads وابحث عن ملفٍ باسم "scream.wav". انقر على الملف بالزر اليميني، ثم اختر قص Cut، ثم انتقل إلى المجلد home/pi/ وانقر على أيّ مكانٍ فارغٍ من نافذته بالزر اليميني، ثم اختر لصق Paste. يمكنك الآن إغلاق كروميوم ومدير الملفات. انقر على الأيقونة جديد New في شريط أدوات ثوني لإنشاء مشروعٍ جديد، حيث سنستخدم هنا أيضًا مكتبةً جديدةً تدعى "Pygame" لتوسيع إمكانيات بايثون، ويُلمِّح اسم هذه المكتبة كما نرى إلى علاقتها بالألعاب. اكتب الشيفرة التالية: import pygame سنحتاج في هذا المشروع إلى إدراج أجزاءٍ محددةٍ من المكتبة Pygame بالإضافة إلى أجزاءٍ من مكتباتٍ أخرى، ولتنفيذ ذلك اكتب الشيفرة التالية: from pygame.locals import * from time import sleep from random import randrange تختلف التعليمة from عن التعليمة import بأنها تدرج جزءًا محددًا من المكتبة بدلًا من إدراجها بالكامل. أما الآن فعلينا إعداد المكتبة Pygame، وتدعى هذه العملية التهيئة initialisation، حيث تحتاج Pygame معرفة ارتفاع وعرض جهاز العرض الذي تستخدمه أو ما يُسمى دقة جهاز العرض، لذلك اكتب الشيفرة التالية: pygame.init() width = pygame.display.Info().current_w height = pygame.display.Info().current_h أما الخطوة النهائية في تهيئة Pygame فهي إنشاء نافذة العرض التي تُدعى شاشة وفق مصطلحات المكتبة. اكتب الشيفرة التالية لإنجاز ذلك: screen = pygame.display.set_mode((width, height)) pygame.quit() يشير الفراغ بين السطرين السابقين إلى أن شيفرة برنامجك ستكون بينهما. انقر على أيقونة التشغيل Run واحفظ برنامجك باسم"Spot the difference" ثم راقب التنفيذ. ستُنشئ المكتبة نافذةً بخلفيةٍ سوداء لا تلبث أن تختفي مباشرةً نظرًا لتنفيذ تعليمة الخروج **()quit**، كما ستلاحظ امتلاء نافذة المتغيرات بعددٍ كبيرٍ من المتغيرات الجديدة التي ولّدتها Pygame تلقائيًا، كما هو موضحٌ في الشكل التالي. لا تلقِ بالًا لهذا الآن. شكل 14-5 لا تلق بالًا للمتغيرات الجديدة التي ستظهر في نافذة المتغيرات لعرض الصورة الأولى من أجل إيجاد الاختلاف بين قسميها، اكتب في الفراغ بين سطري الشيفرة السابقين السطر الجديد التالي: difference = pygame.image.load('spot_the_diff.png') ولتملأ الصورة شاشة العرض، لا بدّ من تغيير أبعاد الصورة لتتناسب مع دقة جهاز العرض الذي تستخدمه، لهذا اكتب الشيفرة التالية: difference = pygame.transform.scale(difference, (width, height)) أصبحت الصورة جاهزةً الآن في الذاكرة وعليك عرضها على الشاشة بعملية تُعرف باسم نقل البتات blitting، لذلك اكتب الشيفرة التالية: screen.blit(difference, (0, 0)) pygame.display.update() ينقل السطر الأول الصورة إلى الشاشة، بينما يطلب السطر الثاني من Pygame تحديث الشاشة، وستبقى الصورة دون السطر الثاني في مكانها الصحيح، لكنها في الذاكرة ولن تتمكن من رؤيتها. انقر على أيقونة Run، ستظهر الصورة على الشاشة لبرهة وتختفي، كما هو موضحٌ في الصورة التالية. الشكل 15-5 الصورة الأساسية للعبة "جد الاختلافات" لعرض الصورة مدةً أطول، اكتب الشيفرة التالية: sleep(3) في السطر الذي يسبق التعليمة: pygame.quit() انقر على أيقونة التشغيل Run وسترى أن الصورة ستُعرض لفترةٍ أطول على الشاشة. أضف الآن الصورة المفاجِئة إلى برنامجك بكتابة الشيفرة التالية: zombie = pygame.image.load('scary_face.png') zombie = pygame.transform.scale(zombie, (width, height)) تمامًا قبل السطر: pygame.display.update() أضف تأخيرًا زمنيًا لتبقى صورة الزومبي المخيفة فترةً أطول بكتابة الشيفرة التالية: sleep(3) وانقل أخيرًا بتات صورتك إلى الشاشة وحدّثها ليرى اللاعب الصورة المخيفة. لتنفيذ ذلك، اكتب الشيفرة التالية: screen.blit(zombie, (0,0)) pygame.display.update() انقر على أيقونة التشغيل Run وراقب ما يحدث. ستظهر الصورة الأساسية للعبة لكن ستحل محلها صورة الزومبي المخيفة بعد ثلاث ثوان، كما هو موضحٌ في الشكل التالي. شكل 16-5 سترعب أحدهم بالتأكيد لكن ظهور الصورة المرعبة بعد ثلاث ثوانٍ سيجعل الأمر متوقعًا، ولتفادي ذلك بدّل السطر: sleep(3) الذي يقع قبل تعليمة نقل بتات الصورة المخيفة بالسطر التالي: sleep(randrange(5, 15)) تختار التعليمة السابقة عددًا عشوائيًا بين 5 و 15 وتؤخر البرنامج هذه المدة. سنحمّل الآن الملف الصوتي وذلك بإضافة السطر التالي: scream = pygame.mixer.Sound('scream.wav') فوق تعليمة التأخير الزمني الأخيرة مباشرةً. سنشغل الملف الصوتي بعد عرض الصورة المخيفة فورًا بكتابة التعليمة: scream.play() ثم ضع تعليمة إيقاف الصوت: scream.stop() قبل التعليمة الأخيرة في البرنامج: pygame.quit() انقر على أيقونة التشغيل Run واستمتع بإنجازك؛ فبعد عرض الصورة البريئة التي سيجد اللاعب الاختلافات بين قسميها، ستظهر فجأة صورة الزومبي المخيفة مع صوتٍ دمويٍ مخيف. إذا ظهرت صورة الزومبي قبل أن يبدأ الصوت، يمكنك تعديل ذلك بإضافة تأخيرٍ زمني بسيط وفق التعليمة: sleep(0.4) قبل السطر: screen.blit(zombie, (0,0)) ستبدو الشيفرة الكاملة للمشروع على النحو التالي: import pygame from pygame.locals import * from time import sleep from random import randrange pygame.init() width = pygame.display.Info().current_w height = pygame.display.Info().current_h screen = pygame.display.set_mode((width, height)) difference = pygame.image.load('spot_the_diff.png') difference = pygame.transform.scale(difference, (width, height)) screen.blit(difference, (0, 0)) pygame.display.update() zombie = pygame.image.load('scary_face.png') zombie = pygame.transform.scale (zombie, (width, height)) scream = pygame.mixer.Sound('scream.wav') sleep(randrange(5, 15)) scream.play() screen.blit(zombie, (0,0)) pygame.display.update() sleep(3) scream.stop() pygame.quit() كل ما عليك فعله الآن هو دعوة أصدقائك للعب، وتأكد من توصيل مكبرات الصوت. المشروع الثالث: المتاهة بعد أن تعرّفت أكثر على بايثون، حان الوقت لاستخدام مكتبة Pygame في إنجاز مشروعٍ أكثر تعقيدًا، وهو لعبة متاهة ووحشٌ يلاحق اللاعب، مبنيةٌ على تعليماتٍ نصية، حيث تُعرف هذه الألعاب بالمغامرات ذات الأوامر الكتابية أو الخيال التفاعلي، وتعود إلى الزمن الذي لم تتمكن فيه الحواسب من التعامل مع الرسوميات وأصرَّ حينها معجبوا هذه الأجهزة بأن الرسوميات الأكثر وضوحًا هي تلك التي نرسمها في مخيلتنا. يُعدُّ هذا البرنامج أكثر تعقيدًا من البرامج السابقة في هذا المقال، لذلك كُتبت شيفرته جزئيًا لتسهيل الأمر. نزّل شيفرة المشروع بفتح متصفح كروميوم وكتابة العنوان التالي rpf.io/rpg-code في شريط العناوين ثم الضغط على المفتاح Enter، حيث ستجد الشيفرة عند اكتمال التنزيل التلقائي في مجلد التنزيلات Downloads. عُد الآن إلى ثوني وانقر أيقونة التحميل Load، ثم ابحث عن الملف rpg-rpg.py في مجلد التنزيلات Downloads وانقر على زر التحميل Load. انقر الآن على أيقونة التشغيل Run لتتعرف على الطريقة التي تُنفَّذ فيها المغامرات الكتابية، وسيظهر الخرج في نافذة المُفسِّر أسفل نافذة ثوني. كبِّر نافذة ثوني لتسهل عليك القراءة. اللعبة بسيطةٌ جدًا كما تبدو الآن. هناك غرفتان دون أغراض، ويبدأ اللاعب في الصالة، وهي أولى الغرفتين. اكتب 'go south' للذهاب نحو المطبخ، ثم اضغط المفتاح Enter، كما هو موضحٌ في الشكل التالي؛ واكتب 'go north' للعودة من المطبخ إلى الصالة. يمكنك أيضًا كتابة أوامر، مثل 'go west' أو 'go east'، ولكن ستظهر لك رسالة خطأ لعدم وجود غرفٍ في هذه الاتجاهات. شكل17-5 هنالك غرفتان فقط حتى الآن انقر على أيقونة الإيقاف Stop لإيقاف البرنامج، وانظر إلى المتغير rooms ضمن نافذة المتغيرات، ويُدعى هذا النوع من المتغيرات القاموس، واسُتخدِم من أجل إخبار اللعبة بالغرف المتوفرة وإلى أيِّ غرفةٍ يقود طريقٌ معين. يمكنك الانتقال إلى السطر 29 من الشيفرة لرؤية النقطة التي أُنشئ فيها هذا المتغير وكيف استُعمل. لجعل اللعبة أكثر متعةً، أضِف غرفةً جديدةً هي غرفة الطعام شرق الصالة. لتنفيذ ذلك، ابحث عن المتغير rooms في الشيفرة ووسِّعه بوضع فاصلة "," بعد القوس "{" في السطر 38، وأضِف الشيفرة التالية، دون التدقيق كثيرًا على الإزاحة في القاموس: 'Dining Room' : { 'west' : 'Hall' } ستحتاج أيضًا إلى مخرج للغرفة فلن تولّده الشيفرة تلقائيًا؛ لذلك انتقل إلى بداية السطر 33 وأضِف فاصلة "," ثم أضِف السطر التالي: 'east' : 'Dining Room' انقر الآن على أيقونة التشغيل Run، واختبر الغرفة الجديدة من خلال كتابة الأمر 'go east' لتنتقل من الصالة إلى غرفة الطعام، واكتب الأمر 'go west' للعودة من غرفة الطعام إلى الصالة. تهانينا، صنعت غرفةً بنفسك. شكل 18-5 لقد صنعت غرفة بنفسك لن يكون الأمر ممتعًا إذا لم يكن في الغرفة أغراض؛ لذلك سنضيف شيئًا إلى الغرفة بتعديل قاموس الغرفة. أوقف البرنامج وابحث عن القاموس Hall في نافذة الشيفرة، ثم أضف فاصلةً "," في نهاية السطر: 'east' : 'Dining Room' ثم اضغط المفتاح ENTER واكتب مايلي: 'item' : 'key' انقر على أيقونة التشغيل Run مجددًا، حيث ستخبرك اللعبة بوجود غرضٍ جديدٍ، وهو المفتاح 'key'. يمكنك كتابة الأمر 'get key' لتتمكن من التقاطه وإضافته إلى قائمة الأغراض التي تحملها، ويُطلق على هذه القائمة اسم المقتنيات inventory، وهي تبقى معك أثناء تنقلك بين الغرف. شكل 19-5 سيضاف المفتاح إلى مقتنياتك انقر على أيقونة Stop لإيقاف اللعبة. علينا الآن جعل اللعبة أكثر تشويقًا من خلال إضافة وحشٍ إليها ينبغي تفاديه؛ ولهذا ابحث عن القاموس 'Kitchen'، وأضف إليه الغرض 'monster' الذي سيمثل الوحش كما أضفت الغرض السابق 'key'، وتأكد من وضع فاصلةٍ "," في نهاية السطر الذي يسبق السطر الجديد الذي ستضيفه: 'item' : 'monster' حتى يكون الوحش قادرًا على مهاجمة اللاعب، لا بدّ من إضافة منطقٍ لهذه العملية؛ لذلك انتقل إلى آخر سطرٍ في البرنامج، وأضِف الأسطر التالية بما فيها السطر الذي يبدأ بالرمز "#". ستساعدك أسطر التعليقات على فهم برنامجك عندما تعود إليه بعد فترةٍ من الزمن، ولا تنسى إزاحة الأسطر بصورةٍ مناسبة: # player loses if they enter a room with a monster if 'item' in rooms[currentRoom] and 'monster' in rooms[currentRoom]['item']: print('A monster has got you... GAME OVER!') break انقر على أيقونة Run مجددًا، ثم حاول الدخول إلى المطبخ، ستكون نهايتك. شكل 20-5 وحش في المطبخ لجعل اللعبة ذات معنىً، سنضيف أغراضًا جديدةً وغرفةً أخرى ووسيلةً للفوز تقتضي بمغادرة المنزل بسلام وبحوزتك كل الأغراض. ابدأ بإضافة غرفةٍ أخرى إلى المتغير rooms كما أضفت غرفة الطعام 'Dining Room' وسمِّها هذه المرة 'Garden'، وأضف مخرجًا إليها من غرفة الطعام 'Dining Room'؛ لهذا عليك أولًا وضع فاصلةٍ "," في نهاية السطر الأخير من القاموس 'Dining Room' ثم إضافة السطر التالي: 'south' : 'Garden' قبل القوس "{" في نهاية 'Dining Room' وأضف فاصلةً "," بعده. بعد أن وضعت مخرجًا إلى الغرفة 'Garden'، أضف هذه الغرفة بعد 'Dining Room' بكتابة الشيفرة التالية: 'Garden' : { 'north' : 'Dining Room' } أضف الغرض 'potion' إلى القاموس 'Dining Room' ولا تنسى الفاصلة "," في نهاية السطر الذي يسبقه: 'item' : 'potion' انتقل أخيرًا إلى نهاية البرنامج وأضف الشيفرة التي تتحقق من امتلاك اللاعب كل الأغراض لإبلاغه بالفوز. # player wins if they get to the garden with a key and a potion if currentRoom == 'Garden' and 'key' in inventory and 'potion' in inventory: print('You escaped the house... YOU WIN!') انقر أيقونة Run وحاول إنهاء اللعبة بالتقاط الغرضين 'key' و'potion' قبل التوجه إلى الحديقة. لا تدخل إلى المطبخ فالوحش موجودٌ هناك. ولإضفاء نكهةٍ إلى اللعبة، يمكنك إضافة إرشاداتٍ تساعد اللاعب على إنهاء اللعبة؛ ولتنفيذ الأمر انتقل إلى بداية البرنامج، وابحث عن الدالة ()showInstructions وأضف النص التالي ضمنها: Get to the Garden with a key and a potion Avoid the monsters! ستظهر الإرشادات السابقة في بداية برنامجك بمجرد تشغيل اللعبة. تهانينا، لقد أنجزت لعبة متاهةٍ مبنيةٍ على أوامر نصية. شكل 21-5 يعرف اللاعب الآن ما عليه فعله ترجمة -وبتصرف- للفصل الخامس Programming with python من كتاب THE OFFICIAL Raspberry Pi Beginner’s Guide. اقرأ أيضًا جولة في راسبيان: نظام تشغيل راسبيري باي تجميع راسبيري باي والتحضير لاستعماله تعرف على جهاز راسبيري باي Raspberry Pi
-
لا يقتصر استخدام راسبيري باي على التعامل مع البرامج التي صممها آخرون، بل بناء برمجياتٍ خاصةٍ بك لتلبية أية احتياجات. سواءٌ امتلكت خبرةً في إنشاء البرامج بكتابة الأوامر والتعليمات أم لا، فستجد منصة راسبيري باي ممتازةً لبناء البرامج واختبارها. تعد لغة سكراتش مفتاحًا إلى كتابة البرامج على باي بأسلوبٍ سهل، وهي لغةٌ برمجةٌ بصرية طوّرها معهد ماساشوسيتس للتكنولوجيا MIT. تعتمد لغات البرمجة التقليدية على كتابة تعليماتٍ نصية ينفّذها الحاسوب كما لو أنك تكتب وصفةً لتحضير كعكة، بينما تساعدك سكراتش scratch على كتابة برنامجك خطوةً بخطوة باستخدام الكتل البرمجية؛ وهي قطعٌ برمجية مكتوبة مسبقًا تُعرَض على شكل قطعٍ مشابهةٍ لقطع الأحجية الملوّنة، كما أن سكراتش scratch لغةٌ ممتازةٌ في بداية مسيرتك في البرمجة سواء كنت صغيرًا أو متقدمًا في السن، ولكن لا تنخدع بمظهرها الودود والملوّن؛ فهي بيئةٌ برمجيةٌ قويةٌ ومتكاملةٌ وظيفيًا وقادرةٌ على كتابة كل ما تتخيله من برمجيات، ابتداءً بالألعاب البسيطة والرسوميات المتحركة وانتهاءً بالبرمجيات التفاعلية المعقدة لمشاريع الروبوتات. ستتعلم في هذا المقال خطواتك الأولى في تعلم البرمجة باستخدام لغة سكراتش التي تعتمد على الكتل البرمجية. تعرف على سكراتش تمثّل المناطق التالية الواجهة الرئيسية للبرنامج بإصداره الثاني: A- المسرح: تتحرك شخصية البرنامج Sprite فيه وفقًا للبرنامج الذي تكتبه. B- الشخصية التفاعلية Sprite: وهي الشخصيات أو الكائنات التي تتحكم بها من خلال برنامج سكراتش وتظهر على المسرح. C- منطقة التحكم بالمسرح: وتُستخدم لتغيير المسرح مثل إضافة الصور والخلفيات. D- قائمة الشخصيات: وتظهر فيها كل الشخصيات التي أنشأتها أو حمّلتها في سكراتش. E- لوحة الكتل البرمجية: وتضم كل القطع البرمجية المتاحة لبرامجك، وتُرتب على شكل فئاتٍ ملونة وفقًا لوظائفها. F- الكتل البرمجية: تضم أسطرًا برمجيةً مكتوبةً مسبقًا ولها وظيفةُ محددة تسمح لك ببناء برنامجك خطوةً بخطوة. G- منطقة بناء البرنامج: حيث تسحب وتفلت الكتل البرمجية ضمنها لتكوين برنامجك. إصدارات سكراتش يأتي راسبيان حتى لحظة إعداد السلسلة بنسختي سكراتش، هما النسخة الأصلية والنسخة المحدّثة 2 وكلاهما ضمن قسم البرامج في قائمة راسبيان. كُتب هذا المقال باستخدام سكراتش 2، فلا تخطئ وتفتح نسخة سكراتش الأصلية، حيث من الممكن أن تختلط عليك الأمور جديًا، ولسوء الحظ لن يعمل سكراتش 2 على Pi Zero نموذج A أو +A أو B أو +B. وتجدر الإشارة أن أحدث الإصدارات هي سكراتش 3. اكتب برنامجك الأول باستخدام سكراتش لفتح سكراتش 2، انقر على أيقونة القائمة، ثم انقل المؤشر إلى فئة البرمجة وانقر على Scratch 2 وستظهر واجهة البرنامج خلال ثوانٍ. يختلف سكراتش عن غيره من لغات البرمجة بأنك لن تصيغ أوامرك للحاسوب على هيئة تعليماتٍ نصية تكتبها بنفسك، بل عن طريق اختيار كتلةٍ برمجيةٍ مناسبة من لوحة الكتل البرمجية. ابدأ رحلتك بالنقر على فئة المظهر Looks ضمن لوحة الكتل البرمجية وسط نافذة البرنامج، حيث ستُظهر لك هذه الفئة كتلًا باللون الأرجواني. ابحث عن الكتلة ، ثم اسحبها إلى منطقة بناء البرنامج إلى يمين النافذة، كما هو موضح في الشكل التالي. شكل 1-4 جر وإفلات كتلة برمجية ألقِ نظرةً على شكل الكتلة التي نقلتها، ستجد فتحةً في أعلاها، وجزءًا بارزًا لوصل الكتلة مع غيرها في الأسفل تمامًا مثل قطعةٍ من لعبة تركيب الأحجيات، وهذا ما يدفعك للتفكير بوجود كتلةٍ قد ترتبط بها من الأعلى وكتلةٍ من الأسفل. ويوجد أعلى الكتلة التي نقلتها للتو في برنامجنا بادئ البرنامج Trigger. انقر على فئة الأحداث Events ضمن لوحة الكتل البرمجية والتي تضم كتلًا ملونةً باللون البني الفاتح، ثم اسحب الكتلة المُسماة بكتلة القبعة إلى منطقة البناء بحيث يلتصق تقريبًا الجزء البارز منها بالفتحة أعلى كتلة ، حيث ستلاحظ ظهور إطارٍ أبيض عند تقريب كتلة القبعة، أفلتها عندئذ. ليس ضروريًا أن تكون دقيقًا في وضع كتلة فوق أخرى، فعندما تقترب كفاية من بعضها ستنزلق في مكانها تمامًا مثل قطعةٍ من قطع أحجية. كرر العملية إذا لم تنجح في المحاولة الأولى. لقد اكتمل برنامجك الآن، انقر على أيقونة الراية الخضراء لتشغيله. إذا سار كل شيءٍ على ما يرام، ستحييك شخصية القط من على المسرح بالعبارة '!Hello'، كما هو موضحٌ في الشكل التالي، وسينتهي برنامجك بنجاح. شكل 2-4 نجاح برنامجك الأول قبل المضي للأمام، اختر اسمًا لبرنامجك واحفظه من خلال النقر على قائمة ملف File، ثم حفظ Save. لدى ظهور نافذة الحفظ، اكتب اسم البرنامج ثم انقر زر حفظ Save، كما هو موضح في الشكل التالي. شكل 3-4 احفظ البرنامج باسم يمكنك تذكره. الخطوة التالية: تتابع الكتل البرمجية يمتلك برنامجك كتلتين فقط وتعليمةً واحدةً تجعل القط يقول '!Hello' في كل مرةٍ يعمل فيها البرنامج بنقر الراية الخضراء. لتطوير برنامجك أكثر، لا بدّ أن تتعلم المزيد عن سَلسَلة الكتل البرمجية Sequencing. تُعد برامج الحاسوب بأبسط وصف لها سلسلةً من التعليمات مثل وصفةٍ لتحضير طبقٍ ما، حيث تُنفَّذ كل تعليمةٍ بعد الأخرى بطريقةٍ منطقية تُدعى السلسلة الخطية. احذف الكتلة بالنقر عليها في منطقة بناء البرنامج، ثم اسحبها إلى لوحة الكتل البرمجية، وبذلك ستبقى فقط كتلة بادئ البرنامج ، كما هو موضحٌ في الشكل التالي. شكل 4-4 لحذف كتلة جرها خارج منطقة البناء ببساطة انقر على فئة الحركة Motion، التي تضم كتلًا باللون الكحلي، ثم اسحب الكتلة وضعها تحت كتلة القبعة (بادئ البرنامج) في منطقة بناء البرنامج، وكما يدل اسمها، فإنها ستُعطي أمرًا لشخصية القط بالتحرك عددًا من الخطوات بالاتجاه الذي يأخذه. سنضيف مزيدًا من التعليمات لتشكل سلسلة. انقر على فئة الصوت Sound، والتي تضم كتلًا باللون الزهري الغامق، ثم انقر على الكتلة واسحبها لتستقر تحت الكتلة ، وأعد النقر مجددًا على فئة الحركة Motion واسحب الكتلة وضعها أسفل كتلة الصوت، ثم انقر على العدد '10' واستبدله بالعدد '-10' لتصبح الكتلة على النحو التالي . انقر الآن على أيقونة الراية الخضراء لتنفيذ البرنامج. سيتحرك القط 10 خطواتٍ إلى اليمين ويصدر الصوت "مياو Meow" (تأكد من وجود مكبر صوت أو سماعة رأس)، ثم يعود إلى نقطة البداية، وسيكرّر القط ما فعله تمامًا عند النقر مجددًا على الراية. تهانينا! لقد أنجزت سلسلةً من التعليمات المتتابعة التي ينفذها سكراتش واحدةً تلو الأخرى من الأعلى إلى الأسفل. نظرًا لتنفيذ سكراتش تعليمةً واحدةً في كل مرة من السلسلة، سيكون التنفيذ سريع جدًا، لذلك حاول أن تحذف الكتلة بإبعاد الكتلة لتنفصل عنها، ثم انقلها خارج منطقة البناء واستبدلها بالكتلة الصوتية الأبسط ، ثم أعد الكتلة لتتصل بها من الأسفل. انقر الراية الخضراء لتشغيل البرنامج من جديد ولن تلاحظ حركة القط. في واقع الأمر، تحرك القط وعاد إلى مكانه دون أن تلاحظ ذلك لأن العملية حدثت بسرعةٍ كبيرة؛ والسبب في ذلك الكتلة التي لا تنتظر الصوت حتى ينتهي قبل أن ينتقل التنفيذ إلى الكتلة التالية، ونظرًا لسرعة راسبيري باي، ستُنفَّذ التعليمة التالية قبل أن تدرك ذلك. سنستخدم طريقةً أخرى للالتفاف على الموضوع دون الحاجة إلى الكتلة ، لذلك سننفِّذ التالي: انقر على فئة التحكم Control، التي تضم كتلًا باللون الذهبي، ثم اسحب الكتلة وضعها بين الكتلة والكتلة . انقر على الراية الخضراء مجددًا وسترى أن القط سينتظر بعد أن يتحرك إلى اليمين ثانيةً واحدةً ثم يعود للخلف، وتُعرف هذه التعليمة بتعليمة التأخير الزمني Delay، وهي الأساس في التحكم بالمدة الزمنية التي سيستغرقها تنفيذ سلسلة التعليمات. تنفيذ حلقة من التعليمات من المؤكد أنك لاحظت تنفيذ سلسلة التعليمات السابقة مرةً واحدةً فقط، حيث يتحرك القط بعد نقر الراية الخضراء، ثم يصدر صوتًا ويعود إلى مكانه، ثم يتوقف البرنامج حتى تنقر الراية مرةً أخرى، ولكن ليس بالضرورة أن تجري الأمور هكذا؛ حيث يقدم سكراتش نوعًا من الكتل البرمجية تُدعى الحلقة Loop. انقر على فئة التحكم Control وابحث عن الكتلة البرمجية ، ثم اسحبها إلى منطقة بناء البرنامج واحشر عارضتها العليا بين الكتلة والكتلة لتضم ضمن عارضتيها بقية الكتل. تتوسع الكتلة التي تأخذ شكل حرف C لتُحيط بمجموعة الكتل البرمجية التي ينبغي تنفيذها بصورةٍ متسلسلةٍ وباستمرار. انقر على الراية الخضراء وستدرك ما تفعله الكتلة ؛ فبدلًا من تنفيذ برنامجك مرةً واحدةً، سيعمل مرةً تلو المرة دون نهاية. تُعرف هذه الفكرة في عالم البرمجة باسم الحلقة اللامتناهية infinite loop؛ أي الحلقة التي لا تنتهي. إذا شعرت بالإزعاج من صوت القط المتكرر، انقر على المثمّن الأحمر بجانب الراية الخضراء لإيقاف البرنامج. اسحب الكتلة والكتل الواقعة أسفلها خارج الكتلة وضعهم جميعًا تحت كتلة القبعة ، ثم اسحب الكتلة خارج منطقة بناء البرنامج لحذفها. انقر واسحب الكتلة وضعها تحت الكتلة لتحيط ببقية الكتل تمامًا كما فعلت سابقتها. انقر على الراية الخضراء لتنفيذ البرنامج. ستعتقد للوهلة الأولى أنه يعيد تنفيذ البرنامج السابق بتكرير التعليمات مرارًا، لكن ما سيحدث أنه سيتوقف عن التنفيذ بعد تكرار التعليمات عشر مرات، وتُعرف هذه الفكرة باسم الحلقة المنتهية definite loop والتي يمكنك فيها تحديد نهاية الحلقة. تُعدّ الحلقات أدواتٍ قويةً برمجيًا، حيث تُستخدم بكثرة خاصةً عند برمجة الألعاب وبرامج تحسس المحيط. استخدام المتغيرات والعبارات الشرطية لا بدّ من المرور أخيرًا على مفهومين هامين قبل البدء بكتابة برامج سكراتش، هما المتغيرات والعبارات الشرطية؛ حيث تُعرّف المتغيرات Variables كما يوحي اسمها، بأنها قيمٌ قابلةٌ للتغيير مع الوقت وبتوجيهٍ من البرنامج. وللمتغيّر خاصيتين رئيسيتين، هما الاسم Name والقيمة Value التي يُخزّنها، وليس بالضرورة أن تكون قيمة المتغير أعدادًا، بل قد تكون كلماتٍ أو نصوصًا أو قيمًا منطقية (صحيح أو خاطئ)، أو أن تكون فارغةً تمامًا وتدعى عندها لا شيء null. المتغيرات أدواتٌ برمجيةٌ فعّالةٌ جدًا، فكّر مثلًا بطريقة تتبع بعض مؤشرات لعبة، مثل سلامة الشخصيات، أو سرعة حركة الأشياء، أو المستوى الحالي للعبة، أو النتيجة فجميعها قيمٌ نتعقبها من خلال متغيرات. انقر على قائمة ملف File واحفظ برنامجك الحالي بالنقر على حفظ المشروع Save Project، ثم ابدأ مشروعًا جديدًا فارغًا بالنقر على جديد New. انقر على فئة البيانات Data التي تضم كتلًا باللون البرتقالي، ثم انقر على الزر 'أنشئ متغيرًا Make a Variable' وسمِّ المتغير بالاسم'loops' كما هو موضحٌ في الشكل التالي، ثم انقر زر OK. ستكون النتيجة سلسلةً من الكتل ضمن لوحة الكتل البرمجية. شكل 5-4 تسمية المتغير انقر ثم اسحب الكتلة إلى منطقة بناء البرنامج، حيث تخبر هذه التعليمة برنامجك أن يجعل قيمة المتغّير صفرًا. انقر بعد ذلك على فئة المظهر Looks واسحب الكتلة إلى أسفل . سيقول القط ما هو مكتوب في الكتلة كما رأينا سابقًا، لكن وبدلًا من ذلك يمكنك استخدام متغيّر. انقر مجددًا على فئة البيانات Data ضمن لوحة الكتل البرمجية، ثم اختر الكتلة التي تُدعى كتلة المُخبر Reporter block، حيث ستجدها في أعلى القائمة وبجوارها مربع تحقق. ضع هذه الكتلة فوق الكلمة '!Hello' ضمن الكتلة . ستكون النتيجة كتلةً برمجيةً مركبةً هي . انقر على فئة أحداث Events ضمن لوحة الكتل البرمجية واسحب الكتلة إلى قمة سلسلة الكتل في منطقة بناء البرنامج. انقر الراية الخضراء وسترى القط يقول '0'، وهي القيمة التي أسندتها للمتغير 'loops'، كما هو موضحٌ في الشكل التالي. شكل 6-4 سيقول القط هذه المرة قيمة المتغيّر يمكن أن تتغير قيم المتغيرات. انقر على فئة البيانات Data في لوحة الكتل البرمجية، ثم انقر واسحب الكتلة إلى أسفل سلسلة الكتل، ثم انقر بعد ذلك على فئة التحكم Control، بعدها انقر واسحب الكتلة وضعها مباشرةً تحت الكتلة بحيث تحيط بقية الكتل في السلسلة. انقر على الراية الخضراء مجددًا، وسترى القط يعد من '0' إلى '9'؛ ويحدث هذا لأن برنامجك يغيّر أو يعدّل قيمة المتغير نفسه، وفي كل مرةٍ تُنفَّذ فيها الحلقة، يضيف البرنامج '1' إلى قيمة المتغيّر 'loops'، كما هو موضحٌ في الشكل التالي. الشكل 7-4 بتعديل قيمة المتغير، يعد القط تصاعديًا الآن يمكن الاستفادة من المتغيرات بما هو أكثر من مجرد تعديل قيمتها. انقر واسحب الكتلة خارج إطار الكتلة وضعها أسفلها، انقر بعد ذلك على الكتلة واسحبها نحو لوحة الكتل البرمجية لحذفها، ثم استبدلها بالكتلة وتأكد بأنها متصلةٌ بأسفل الكتلة وتحيط بالكتلتين الباقيتين. انقر على فئة العوامل Operators التي تضم كتلًا باللون الأخضر، ثم انقر واسحب الكتلة وافلتها في الفتحة المماثلة لها في كتلة . تمكّنك هذه الكتلة من موازنة قيمتين بما في ذلك المتغيّرات. انقر على فئة البيانات Data واسحب كتلة المُخبِر إلى الفراغ الأول للكتلة ثم انقر الفراغ الثاني واكتب العدد '10'. انقر على الراية الخضراء فوق منطقة المسرح وستلاحظ أن البرنامج يعمل مثل السابق، حيث يعد القط من '0' إلى '9' ثم يتوقف البرنامج. يعود السبب في ذلك إلى عمل الكتلة بنفس طريقة عمل الكتلة تمامًا، لكنها توازن قيمة المتغير 'loops' بالقيمة المقابلة في الكتلة وهي '10'، وسيتوقف البرنامج عندما تصل قيمة المتغير إلى '10'، كما هو موضح في الشكل التالي. الشكل 8-4 استخدام الكتلة 'repeat until' مع كتلة عامل الموازنة تُعرف هذه التعليمة باسم عامل الموازنة comparative operator، فهي توازن حرفيًا بين قيمتين. انقر على فئة العوامل Operators ضمن لوحة الكتل البرمجية واطلّع على الكتلتين الباقيتين أعلى وأسفل الكتلة التي تحتوي الرمز '=. لاحظ أنهما كتلتي موازنة أيضًا، حيث يوازن العامل '>' بين قيمتين ويعطي القيمة 'صحيح True' عندما تكون قيمة الطرف الأيسر أصغر تمامًا من قيمة الطرف الأيمن، بينما يحدث العكس عند استخدام العامل '<'. انقر على فئة التحكم Control ضمن لوحة الكتل البرمجية وابحث عن الكتلة ، ثم اسحبها إلى منطقة بناء البرنامج وضعها مباشرةً أسفل الكتلة . ستغلّف تلقائيًا الكتلة لذلك انقر واسحب هذه الأخيرة وضعها لتستقر أسفل الكتلة وليس ضمنها. انقر على فئة المظهر Looks، ثم انقر واسحب الكتلة إلى داخل الكتلة ، وانقر بعد ذلك على فئة العوامل Operators، ثم انقر واسحب الكتلة إلى الفتحة التي تطابقها في الكتلة . الكتلة هي كتلةٌ شرطية، أي أنّ تنفيذ الكتل التي تقع بداخلها مرتبطٌ بتحقق شرطٍ معين. انقر على فئة البيانات Data ثم اسحب الكتلة إلى المربع الفارغ الأول للكتلة ، وانقر بعدها على المربع الفارغ الثاني واكتب '5'، ثم انقر أخيرًا على الكلمة '!Hello' في الكتلة واكتب العبارة 'That’s high' مكانها. شغّل البرنامج بالنقر على الراية الخضراء وسيعمل البرنامج بدايةً كما سبق، حيث سيعُد القط تصاعديًا من الصفر، لكن عند وصول العد إلى الرقم '6' وهو الرقم الأكبر مباشرةً من '5'، ستُفعَّل الكتلة وسيعلّق comment القط على الرقم الأعلى الذي سيظهر، كما هو مبينٌ في الشكل التالي. تهانينا! ستكون قادرًا من الآن فصاعدًا على العمل مع المتغيرات والكتل الشرطية. شكل 9-4 سيُعلِّق القط عندما يصل العد إلى 6 تحدّي الأعلى والأدنى: كيف ستغيّر البرنامج حتى يعلّق القط على الأرقام الأصغر من '5' بدلًا من الأكبر؟ هل يمكنك تغيير البرنامج حتى يعلّق على القيم الأعلى والأدنى من '5'؟ لتبسيط العملية جرّب الكتلة . المشروع الأول: رائد الفضاء الخارق بعد فهم الطريقة التي يعمل بها سكراتش، حان الوقت لإنجاز مشروع تفاعلي أكثر؛ وهو مؤقتٌ يتحكم بأفعال رائد فضاء، حيث صُمم هذا المشروع لتكريم رائد وكالة الفضاء الأوروبية البريطاني "تيم بيك Tim Peake" وللوقت الذي قضاه في محطة الفضاء الدولية. احفظ مشروعك السابق إذا أردت ذلك، ثم افتح مشروعًا جديدًا بالنقر على قائمة ملف File، ثم جديد New، وسمِّ مشروعك قبل أن تبدأ العمل بالنقر على ملف File، ثم حفظ المشروع Save Project، وليكن اسمه 'Astronaut Reaction Timer'. يعتمد المشروع على صورتين، الأولى هي خلفية المسرح، والثانية هي الشخصية ولن تجدهما بالطبع ضمن موارد سكراتش المدمجة وعليك تنزيلها. انقر على أيقونة راسبيري باي، ثم انقل مؤشر الفأرة إلى إنترنت Internet وبعدها إلى متصفح الويب كروميوم Chromium Web Browser وانقر عليه، ثم اكتب العنوان 'rpf.io/astronaut-backdrop' ضمن شريط العنوان عندما يكتمل تحميل البرنامج واضغط على الزر Enter، انقر بعد ذلك بالزر اليميني للفأرة على صورة الفضاء لتظهر لك قائمةٌ اختر منها الأمر حفظ الصورة باسم 'Save image as'، ثم اختر مجلدًا لحفظها وانقر الزر حفظ Save، كما هو مبينٌ في الشكل التالي. الشكل 10-4 حفظ صورة الخلفية انقر على زر التراجع بجوار شريط العنوان ثم اكتب عنوان الصورة الأخرى 'rpf.io/astronaut-sprite' واضغط على الزر Enter. احفظ الصورة الموجودة بنفس الطريقة، وأغلق كروميوم إذا أردت أو استخدم شريط المهام للعودة إلى سكراتش 2. إذا كنت تتابع معنا هذا المقال منذ بدايته فمن المفترض أن تكون واجهة سكراتش 2 مألوفةً بالنسبة إليك. ستعتمد الشروحات في المشاريع القادمة على معرفتك بالأشياء ومواقعها ضمن الواجهة؛ فإذا نسيت أين تجد شيئًا ما، عُد إلى الخلف وانظر إلى الصورة التفصيلية للواجهة التي شرحناها بداية هذا الفصل. انقر بالزر الأيمن للفأرة على شخصية القط الموجودة على المسرح واحذفه، ثم ابحث عن أدوات التحكم بالمسرح الموجودة أسفل ويسار نافذة سكراتش 2 وانقر على أيقونة رفع الخلفيات، والتي ستكون على شكل مجلد مفتوح يخرج منه سهمٌ للأعلى. ابحث عن صورة باسم Space-background.png ضمن مجلد التنزيلات Downloads وانقر عليها، ثم اضغط الزر OK. ستتحول الآن خلفية المسرح البيضاء إلى صورةٍ للفضاء كما هو موضح في الشكل التالي، وستُستبدل منطقة بناء البرنامج بمنطقة الخلفيات التي يمكنك الرسم على الخلفية ضمنها. لإعادة منطقة بناء البرنامج، انقر على النافذة الفرعية Scripts أعلى نافذة سكراتش 2. شكل 11-4 تظهر خلفية الفضاء على المسرح حمّل الشخصية الجديدة بالنقر على أيقونة الشخصيات بجوار عبارة شخصية جديدة 'New sprite' أعلى منطقة الشخصيات. ابحث عن الصورة Astronaut-Tim.png ضمن مجلد تنزيلات Downloads، ثم انقر عليها واضغط الزر OK. ستظهر الشخصية الجديدة تلقائيًا على المسرح وقد لا تكون في المنتصف، انقر عليها واسحبها حتى تصبح أسفل منتصف المسرح بقليل ثم أفلتها، كما هو موضح في الشكل التالي. شكل 12-4 اسحب شخصية رائد الفضاء إلى أسفل منتصف المسرح ستتمكن الآن من إكمال مشروعك بعد أن وضعت الخلفية الجديدة والشخصية المطلوبة في المكان الصحيح. ابدأ الآن بإنشاء متغيرٍ جديد باسم 'time' وتأكد من تفعيل الخيار "جميع الشخصيات For all sprites" ثم اضغط الزر OK. انقر على الشخصية لاختيارها ثم أضف الكتلة إلى منطقة بناء البرنامج، وأضف بعد ذلك الكتلة ، ثم انقر على الكلمة '!Hello' واستبدلها بالعبارة '?Hello! British ESA Astronaut Tim Peake here. Are you ready'. أضِف الكتلة ثم الكتلة وغيّر هذه الأخيرة لتقول 'انقر على الفضاء !Hit Space'. اختر فئة الاستشعار Sensing والتي تضم كتلًا باللون الأزرق الفاتح، ثم أضف الكتلة التي تتحكم بمتغيرٍ خاصٍ مدمجٍ مع سكراتش لأغراض التوقيت وسنستخدمه لتقدير سرعة تفاعلك مع اللعبة. أضِف كتلة التحكم ، ثم ضع كتلة الاستشعار في الفراغ الأبيض ضمنها. سيوقف هذا الأمر البرنامج مؤقتًا حتى تضغط المفتاح SPACE في لوحة المفاتيح. إذًا، سيقيس المؤقت الآن الفترة الزمنية التي تبدأ بظهور الرسالة '!Hit Space' وحتى تضغط المفتاح SPACE. سنجعل رائد الفضاء تيم Tim يخبرك كم الزمن الذي استغرقته حتى ضغطت المفتاح SPACE وبطريقةٍ سهلة القراءة، حيث ستحتاج لتنفيذ الأمر إلى كتلة العوامل . تأخذ الكتلة قيمتين بما في ذلك المتغيرات، ثم تضمهما سويّةً الأول ثم الثاني وهذا ما يُعرف برمجيًا باسم الدمج Concatenation. ابدأ بالكتلة، ثم اسحب الكتلة وضعها فوق الكلمة '!Hello'، واكتب في الفراغ الأول لكتلة الضم العبارة ' Your reaction time was' وتأكد من ترك محرف مسافةٍ فارغةٍ في نهاية الجملة. اسحب كتلة ضمٍ أخرى وضعها في الصندوق الثاني لكتلة الضم السابقة. وأخيرًا اسحب الكتلة من فئة استشعار Sensing، وضعها في الصندوق الفارغ الأول لكتلة الضم الثانية، ثم اكتب كلمة 'seconds ' في الصندوق الفارغ الثاني لكتلة الضم الثانية، وتأكد من وجود مسافةٍ فارغةٍ قبلها. اسحب أخيرًا كتلة البيانات إلى نهاية سلسلة التعليمات، ثم استبدل الرقم '0' بكتلة الإخبار . أصبحت اللعبة الآن جاهزةً للاختبار، لذلك انقر على الراية الخضراء واستعد. اضغط المفتاح SPACE فور رؤيتك للرسالة '!Hit Space' وحاول أن تحطّم زمن اللعبة القياسي. شكل 13-4 حان وقت اللعب يمكنك توسيع المشروع أكثر بحسابك المسافة التقديرية التي ستقطعها محطة الفضاء الدولية وهي تدور حول الأرض خلال الفترة التي استغرقتها حتى تضغط المفتاح SPACE، وذلك بعد الأخذ بالحسبان أن سرعتها تصل إلى 7 كيلومترات في الثانية. أنشئ أولًا متغيرًا باسم 'distance'، ولاحظ كيف تتغير الكتل تلقائيًا في فئة البيانات Data لإظهار المتغير الجديد، وستبقى كتل المتغير السابق في البرنامج كما هي. أضف الكتلة ، ثم اسحب كتلة عامل الجداء وضعها فوق القيمة '0' للكتلة السابقة، ثم اسحب الكتلة وضعها فوق الفراغ الأول لكتلة عامل الجداء واكتب الرقم '7' في الفراغ الثاني لها. ستقرأ الكتلة المركبة التي أنشأتها أخيرًا قيمة المقدار الذي يمثِّل الزمن الذي استغرقته حتى ضغطت المفتاح SPACE مضروبًا بالرقم '7'، وهي المسافة التي ستقطعها محطة الفضاء الدولية خلال هذه الفترة. أضف الكتلة وغيّر الزمن من '1' إلى '4 ثوان'، ثم اسحب كتلةً أخرى وضعها في نهاية السلسلة، وضع ضمنها كتلتي ضم كما فعلت سابقًا. اكتب في الفراغ الأول للكتلة الأولى الجملة ' In that time the ISS travels around' واترك مسافةً فارغةً في نهايتها. ستملأ كتلة الضم الثانية الفراغ الثاني من كتلة الضم الأولى، اكتب في الفراغ الثاني لكتلة الضم الثانية الكلمة 'kilometres ' واترك مسافةً فارغةً قبلها. اسحب كتلة العوامل وضعها في الفراغ الأول لكتلة الضم الثانية، ثم اسحب الكتلة وضعها داخل الفراغ الوحيد للكتلة السابقة. تُدوِّر الكتلة العدد إلى أقرب عددٍ صحيح أكبر أو أصغر منه، وبالتالي ستظهر النتيجة عددًا صحيحًا سهل القراءة بدلًا من عددٍ فائق الدقة صعب القراءة بسبب وجود عددٍ كبيرٍ من الأرقام بعد الفاصلة. انقر على الراية الخضراء لتنفيذ البرنامج لتلاحظ المسافة التي ستقطعها محطة الفضاء الدولية خلال الوقت الذي تستغرقه في ضغط المفتاح SPACE، كما هو موضحٌ في الشكل التالي. تذكر أن تحفظ البرنامج عند الانتهاء لتتمكن من تشغيله مجددًا دون عناء كتابته من جديد. الشكل 14-4 سيخبرك تيم كم المسافة التي قطعتها محطة الفضاء الدولية المشروع الثاني: سباحة فنية متزامنة تستخدم معظم الألعاب أكثر من زر للتحكم بمجرياتها، وسنوضح هذه الفكرة في مشروعنا الحالي باستخدام زري لوحة المفاتيح ← و→. أنشئ مشروعًا جديدًا واحفظه باسم 'Synchronised Swimming'، ثم انقر على المسرح وبعدها على نافذة الخلفيات. انقر على اللون الأزرق المائي ضمن لوحة الألوان، ثم انقر على أيقونة الدلو التي تملأ الخلفية بلونٍ محدد، وانقر بعدها على الخلفية البيضاء للمسرح لتصبح باللون المطلوب، كما يبين الشكل التالي. شكل 15-4 املأ الخلفية باللون الأزرق انقر بالزر الأيمن على شخصية القط، ثم اختر الأمر "حذف delete". انقر على الأيقونة "اختر شخصية من المكتبة choose sprite from library" لتعرض لك مجموعة الشخصيات المدمجة مع سكراتش 2، وانقر على فئة الحيوانات Animals ثم اختر الشخصية 'Cat1 Flying'، وبعدها انقر الزر OK. ستفيدنا هذه الشخصية جيدًا في السباحة أيضًا! شكل 16-4 اختر شخصية من المكتبة انقر على الشخصية الجديدة، ثم اسحب كتلتي أحداث من النوع إلى منطقة بناء البرنامج. انقر في الكتلة الأولى على السهم الصغير المتجه للأسفل بجانب الكلمة 'space' واختر 'left arrow' من قائمة الخيارات، ثم اسحب كتلة الحركة وضعها أسفل كتلة الحدث when left arrow pressed (عند الضغط على السهم الأيسر)، وكرّر نفس العملية على كتلة الحدث الثانية لتغيّر زر 'space' بالزر 'right arrow'، وضع تحتها الكتلة . لاحظ أن مجموعتي الكتل السابقتين ستبقيان منفصلتين ولا تمثلان سلسلةً واحدة. اضغط الزرين ← و→ لاختبار برنامجك، حيث ستلاحظ أن القط سيدور بالجهة التي تريد وفقًا للزر الذي تضغط عليه. انتبه أنك لست بحاجةٍ إلى النقر على الراية الخضراء لتنفيذ البرنامج، لأن كتلة بادئ البرنامج التي استخدمتها سابقًا ستبقى فعّالة دومًا حتى لو لم يكن البرنامج في وضع 'التشغيل running' بالمعنى العام. كرّر الخطوات السابقة مرتين لكن اختر الزرين 'up arrow' و'down arrow' في كتلتي الأحداث وضع الكتلتين و بدلًا من كتلتي الدوران. اضغط الآن على الأسهم في لوحة المفاتيح لترى القط يدور ويسبح إلى الأمام والخلف. لجعل حركة القط أكثر واقعية، يمكنك تغيير مظهره، ويُعرف هذا الأمر في سكراتش بتغيير الزي costume. انقر على شخصية القط ثم على النافذة الفرعية المُسماة أزياء Costumes فوق لوحة الكتل البرمجية، وانقر على الزي 'cat1 flying-a'، ثم انقر على أيقونة 'X' الدائرية التي ستظهر على زاوية الزي اليسارية العليا لحذفه. اختر الزي 'cat1 flying-b' واستخدم الصندوق أعلاه لتغيير الاسم إلى 'right'، كما هو موضحٌ في الشكل التالي. شكل 17-4 إعادة تسمية الزي انقر بزر الفأرة الأيمن على الزي الجديد 'right' في النافذة الفرعية أزياء Costume، ثم انقر 'نسخة أخرى duplicate' لإنشاء نسخةٍ جديدةٍ، ثم انقر على هذه النسخة لاختيارها وسمِّها 'left'. انقر بعد ذلك على أيقونة عكس الاتجاه يمين- يسار وستجدها في شريط الأيقونات بجوار صندوق تغيير اسم الزي في النافذة الفرعية أزياء Costumes، وهكذا ستحصل على زيين، أحدهما 'right' ووجه القط فيه نحو اليمين، والآخر 'left' ووجه القط فيه نحو اليسار، كما هو موضحٌ في الشكل التالي. شكل 18-4 نسخ الزي ثم قلبه وتسميته 'left' انقر على النافذة الفرعية المُسماة منطقة بناء البرنامج Scripts، ثم اسحب إليها كتلتي مظهر من النوع و، وضَعهما أسفل كتلتي الحدث left arrow (عند الضغط على السهم الأيسر) و right arrow (عند الضغط على السهم الأيمن) بحيث يتوافق السهم اليميني مع الزي اليميني واليساري مع اليسار. جرب السهمين اليميني واليساري الآن في لوحة المفاتيح وستلاحظ أن القط يدير وجهه باتجاه الحركة. وللحصول على الطابع الأولمبي للسباحة المتزامنة، سنحتاج إلى المزيد من السباحين، كما سنحتاج إلى طريقةٍ لإعادة ضبط موقع الشخصية. أضِف الكتلة وضع تحتها كتلة الحركة ، وغيّر القيم الصفرية إذا كان ذلك ضروريًا، أضف بعد ذلك الكتلة . عند النقر على الراية الخضراء سيُوجّه القط إلى منتصف المسرح (النقطة (0,0)) وسيشير إلى اليمين. لإنشاء عددٍ أكبر من السباحين، أضف كتلة التحكم بعد تغيير قيمتها الافتراضية '10' إلى '6'، ثم ضع كتلة التحكم داخلها. ولجعل السباحين يتحركون بحيث لا يتوجهون جميعهم إلى نفس الوجهة، أضف الكتلة فوق الكتلة ضمن الكتلة . انقر على الراية الخضراء لترى أن السباحين قد ظهروا! ستحتاج إلى بعض الموسيقى لإكمال اللمسة الأولمبية، لذلك انقر على النافذة الفرعية المُسماة أصوات Sounds والموجودة فوق لوحة الكتل البرمجية، ثم انقر الأيقونة 'اختر صوتًا جديدًا من المكتبة choose new sound from library'. انقر على فئة أصوات موسيقية Music Loops واختار الصوت الذي يعجبك بعد تجريب الأصوات بالنقر على أيقونات التشغيل. اخترنا في برنامجنا الصوت 'dance around'، انقر OK لانتقاء الصوت، ثم انقر على واجهة بناء البرنامج Scripts لفتح منطقة البرنامج مجددًا. شكل 19-4 اختر الموسيقى من مكتبة الأصوات أضف كتلة أخرى إلى منطقة بناء البرنامج، ثم أضف كتلة التحكم . أضف داخل كتلة التحكم الكتلة ، وتذكر أن تضع اسم المقطوعة الموسيقية التي تختارها وليس بالضرورة 'dance around'. انقر على الراية الخضراء لاختبار البرنامج، وانقر على المثمَّن الأحمر لإيقاف البرنامج وإسكات الموسيقى إذا أردت. يمكنك محاكاة رقصةٍ كاملةٍ من خلال إضافة محرّض حدث جديد event trigger إلى برنامجك، ولهذا أضِف كتلة الأحداث ثم الكتلة وتحتها الكتلة ، وضع داخل كتلة التكرار الأخيرة الكتلتين و. انقر على الراية الخضراء، ثم انقر على المفتاح SPACE وشاهد الرقصة. لا تنس حفظ البرنامج عندما تنتهي. شكل 20-4 البرنامج الكامل للسباحة المتزامنة المشروع الثالث: لعبة القوس والسهم لقد بدأت بخط طريقك نحو احتراف سكراتش، لهذا لا بُدّ أن ترفع مستوى التحدي قليلًا بتصميم لعبة قوسٍ وسهم، والتي يُطلب فيها من اللاعب إصابة هدفٍ بقوسٍ مُتأرجحٍ وسهم. لنبدأ بتنزيل موارد اللعبة من ملف ArcheryResources.sb2 مستعينين بمتصفح كروم، حيث تستغرق العملية عدة ثوانٍ فقط. افتح بعدها برنامج سكراتش 2، ثم انقر على قائمة الملف File، ثم اختر تحميل مشروع Load Project. انقر على 'pi' في الجزء اليساري من النافذة وابحث عن المجلد 'تنزيلات Downloads'، وانقر على الملف ArcheryResources.sb2 ثم انقر الزر فتح Open. سيسألك البرنامج إذا كنت تريد استبدال محتوى البرنامج الذي تعمل عليه حاليًا، لذلك انقر إلغاء Cancel ثم احفظ برنامجك أولًا إذا أردت الاحتفاظ به، وإلا انقر الزر OK. شكل 21-4 موارد مشروع القوس والسهم يحتوي المشروع على خلفية وشخصية، ولكن دون أية شيفرات لتشغيل اللعبة، لذا عليك إضافتها بنفسك. ابدأ بإضافة الكتلة ثم الكتلة وانقر فيها على السهم المتجه للأسفل واختر 'رسالة جديدة New Message'، ثم اكتب فيها 'new arrow' قبل النقر على الزر OK. ستظهر الكتلة الآن على الشكل . رسالة البث broadcast هي رسالةٌ يصدرها جزءٌ من البرنامج ويتلقاها جزءٌ آخر؛ ولتستفيد حقيقةً من هذه الرسالة، أضف الكتلة ثم حوّلها لتصبحمن خلال النقر على السهم المتجه للأسفل واختيار 'new arrow'. أضف الكتلتين ثم أسفل الكتلة ، وغيّر القيمة الافتراضية في الكتلتين السابقتين كما يناسبك بعد سحبهما إلى منطقة بناء البرنامج. انقر على الراية الخضراء وراقب ما فعلته حتى اللحظة، حيث ستقفز شخصية السهم التي يستخدمها اللاعب للتصويب على الهدف إلى الزاوية اليمينية السفلى من المسرح ويتضاعف حجمها 4 مرات. لزيادة التحدي في اللعبة، أضف محاكاةً لحركة التأرجح عندما يُشدُّ القوس ويصوّب اللاعب. لفعل ذلك اسحب الكتلة وضع تحتها الكتلة، وغيّر القيمة '1' إلى '0.5'، ثم ضع كتلة العوامل مكان الفراغين اللذان يحملان الرقمين '150' و '-150' في الكتلة السابقة؛ وهذا يعني أنّ السهم سيغيّر اتجاهه على المسرح عشوائيًا وسيبتعد عن الهدف مسافةً عشوائيةً أيضًا، مما سيزيد صعوبة إصابة الهدف. انقر على الراية الخضراء وراقب ما يحدث؛ حيث سيتحرك السهم ضمن المسرح مغطيًا أجزاءًا مختلفةً من الهدف، لكن حتى هذه اللحظة لا توجد وسيلةٌ لإطلاق السهم؛ لذلك اسحب الكتلة وضع بعدها كتلة التحكم ، ثم انقر على السهم المتجه للأسفل في هذه الكتلة وغيّرها إلى كتلة . انقر الراية الخضراء إذا أوقفت برنامجك، ثم اضغط المفتاح SPACE وستلاحظ أن السهم سيتوقف عن الحركة. بداية جيدة، لكنك بحاجةٍ إلى تمثيل السهم متجهًا إلى الهدف؛ لذلك أضف الكتلة واتبعها بالكتلة ، ثم انقر زر الراية الخضراء لتشغيل البرنامج مجددًا، وسيبدو السهم الآن متحركًا نحو الهدف. لجعل اللعبة أكثر متعةً، لا بدّ من وسيلةٍ لإظهار النتيجة؛ لذلك أضف الكتلة وتأكد أنها تحت الكتلة وليس داخلها، ثم ضع داخل الفراغ المخصص في الكتلة كتلة الاستشعار . ولاختيار اللون الصحيح، انقر على المربع الملون داخل الكتلة، ثم انقر على البقعة الصفراء داخل الهدف الموجود على المسرح، وهكذا سيعرف اللاعب أنه حقق نتيجةً برميته. أضِف الكتلتين و داخل الكتلة ، ثم أضف أخيرًا الكتلة بعد الكتلة ، وذلك لتمنح اللاعب سهمًا جديدًا في كل مرةٍ يطلق فيها السهم. انقر على الراية الخضراء لتبدأ اللعبة، وحاول أن تصيب العين الصفراء منتصف الهدف، وإذا فعلتها ستسمع هتافًا تشجيعيًا وستُكافأ بمئتي نقطة. ستعمل اللعبة الآن، لكن حاول استخدام ما تعلمته في هذا الفصل في إضافة نقاطٍ لإصابة الأجزاء الأخرى من الهدف، كأن يحصل اللاعب على 100 نقطةٍ عند إصابة الدائرة الحمراء و50 عند إصابة اللون الأزرق. اطلع على الملحق D، الذي يحمل العنوان "اطلع أكثر" للمزيد من مشاريع سكراتش. ترجمة -وبتصرف- للفصل الرابع "Programming with scratch" من كتاب THE OFFICIAL Raspberry Pi Beginner’s Guide. اقرأ أيضًا المقال التالي: البرمجة باستخدام لغة بايثون في تطبيقات راسبيري باي المقال السابق: جولة في راسبيان: نظام تشغيل راسبيري باي إنشاء لعبة أضواء باستخدام برنامج سكراتش وحاسوب راسبيري باي لعبة تخفيف التوتر باستخدام سكراتش وحاسوب راسبيري باي
-
يُميِّز عمل مجموعة الضوء الأساسية لرسام المدرسة التقليلية Minimalism جو باير ثلاثة مربعات ذات إطاراتٍ سوداء مع عصابة ملونة، وتبدو المربعات وكأنها تطفو بفضل طريقة الإدراك النفسية للبشر. يُظهر العمل السابق أنه لا حاجة لتغطية لوحة العمل الفني بالكامل لتحصل على انطباع ما، وتنطبق القاعدة نفسها على صفحة الويب الرئيسية، فقد لا ترغب في أن تملأ كامل صفحتك بالمحتوى. وفي بعض الأحيان تسود مقولة "الأقل هو الأكثر Less is more"، فوجود المساحات البيضاء أو الفارغة سيمنح صفحتك الرئيسية منافعًا عظيمةً تصب في صالح انسيابيتها وهيكلها وتأثيرها. هل تريد معرفة الطريقة التي تبني بها صفحة رئيسية مبهرة تستخدم طاقة المساحات البيضاء؟ ستنتقل بك هذه المقالة من مفهوم المساحات البيضاء وما الذي تقدمه لتحسين صفحتك الرئيسية إلى كيفية استغلال إمكانياتها. ماهي قصة المساحات البيضاء؟ تُعرف المساحات البيضاء أو الفراغ السلبي Negative space كما تُدعى أحيانًا في مجال تصميم صفحات الويب، على أنها الفراغات بين عناصر التصميم. وعلى خلاف التسمية، لا ينبغي أن تكون المساحات البيضاء بيضاءً أو فارغةً بالضرورة، فقد يكون لهذه المساحات لون مغاير للأبيض أو حتى صورة للخلفية. للصفحات الرئيسية نوعين من المساحات البيضاء، هي المساحات الدقيقة micro والواسعة macro كما يشير تصنيفها: الفراغات البيضاء الدقيقة: هي المساحات بين العناصر الصغيرة، مثل المساحات بين الأيقونات، أو الفراغات بين أسطر النص. الفراغات البيضاء الواسعة: هي المساحات بين العناصر الكبيرة مثل الرسوميات والأعمدة التي تضم نصوصًا. لنستخدم أحد أمثلتنا عن صفحات جذب العملاء ذات معدل التحول العالي لإظهار عمل المساحات البيضاء الدقيقة والواسعة. سترى في الناحية اليمنى عددًا كبيرًا من المساحات البيضاء الواسعة، والتي توجه العين مباشرةً إلى منطقة اتخاذ القرار الموجودة على يسار الصفحة، ثم توجهها نحو التطبيق الذي يعرضه الجهاز اللوحي في الأسفل. ستجد على يسار العنوان ثلاث مناطق تحوي مساحات بيضاء دقيقة تفصل بين العنوان الرئيسي والعناوين الفرعية وحقل النموذج وزر الإرسال. إذًا كما ترى، يمكن استخدام المساحات البيضاء الدقيقة والواسعة في تصميم الصفحات الرئيسية لفصل العناصر المختلفة عن بعضها وتمييزها. تزيد المساحات البيضاء القدرة على القراءة على الرغم من أن دور المساحات البيضاء في تسهيل القراءة لن يكون واضحًا عندما تكون موجودة، إلا أنك ستدرك بالطبع عملها عندما لا تكون موجودة. هل حاولت مثلًا قراءة نص تمتد كلماته لتغطي حواف الصفحة؟ أو هل حاولت قراءة نص ضخم من الكلمات المتتالية التي لا تفصل بينها أسطر؟ لن تكون هذه تجربةً مريحةً أبدًا. تزيد أهمية القراءة السهلة أكثر على الإنترنت، حيث يريد القارئ جوابًا سريعًا لتساؤلاته. وقد خلُصت آلاف الدراسات حول مستخدمي الإنترنت أن ما يقرأه العميل لا يتعدى 18% وسطيًا من محتوى صفحة ويب. لذلك عليك مساعدتهم في العثور على 18% التي يريدونها بسرعة وإلا سيغادرون موقعك. تساعد المساحات البيضاء على تصنيف صفحتك تُعد المساحات البيضاء هي العلامة الفارقة لتصاميم صفحات الويب العصرية، حيث تبدو الصفحات التي تستخدم مساحات بيضاء أكثر، أقل تكلّفًا وأوضح وذات جودة عالية. قد تشعر أنك تهدر مساحات بلا فائدة، إلا أن أهمية المساحات البيضاء بأهمية باقي عناصر التصميم. لنلق نظرةً على مثال عن استخدام المساحات البيضاء بطريقة استراتيجية وبحدها الأدنى: إن عشت في نيويورك أو زرتها في العقدين الماضيين، فلربما تكون قد رأيت إعلانات للدكتور جوناثان تسيزمور قبل أن يتقاعد سنة 2016. لقد عُرفت هذه الإعلانات بأنها من النوع المبالغ فيه، وأحد أسباب ذلك، أنها تحشر العديد من العناصر التصميمية في صفحة واحدة. لا تمتلك هذه الإعلانات الكثير من المساحات البيضاء الدقيقة أو الواسعة، فكل بقعة منها تقريبًا تحوي نصًا أو صورة. لقد استمرت هذه الإعلانات بالعمل لصالح تسيزمور طوال 25 عامًا مثل مادة أساسية في إعلانات مترو الأنفاق. لن تعمل مقاربة تسيزمور مع الأعمال التي تريد أن تتسم بطابع الحداثة والمعاصرة. عند موازنة إعلانات تسيزمور بالصورة التي نعرضها تاليًا لصفحة رئيسية لموقع كتب إلكترونية مصدرها Optimonk، ستُظهر بوضوح تصميمًا مفعمًا بالألوان تمامًا كما في الإعلان السابق، لكنه يستخدم كمًا أكبر بكثير من المساحات البيضاء. تتماشى المساحات الفارغة الكبيرة سواءً الدقيقة أو الواسعة منها مع المعايير الحالية للمارسة الصحيحة في استخدام المساحات البيضاء، وتمنح صفحات الويب الرئيسية مظهرًا أكثر عصرية. كيف تجعل صفحتك أكثر جاذبية باستخدام المساحات البيضاء تؤدي المساحات البيضاء عدة وظائف في صفحة الويب، وستتمكن من وضع هذه الوظائف موضع التنفيذ إن فهمت هذه الوظائف المختلفة. مسارات مستخدم مباشرة تمتلك كل صفحة ويب تراتبيةً مرئيةً لعناصرها تقود المستخدم عبر العناصر التي تكوّن الصفحة. يمكنك التحكم بهذه التراتبية (التي تُدعى مسار المستخدم) بإدخال مساحات بيضاء استراتيجية، فمن المرجح أن ينظر الزائر إلى عنصر محدد عندما تحيط به مساحة بيضاء أكبر. تعود هذه الممارسة إلى مبدأ هيكلية البناء building structure، وهو أحد المبادئ الأساسية لتصميم الصفحات المرتكز على نسبة التحول. ويمكن الوصول إلى بنية تراتبية من المعلومات بإحاطة التفاصيل الأكثر أهميةً بالمزيد من المساحة البيضاء موازنةً مع الأقل أهميةً. تحقق كيف بنى مسارًا مرئيًا باستخدام المساحات البيضاء الواسعة والدقيقة في الصورة التالية: لاحظ كيف أحيط العنوان الرئيسي والعناوين الفرعية وزر اتخاذ قرار الشراء بمساحات بيضاء واسعة أكبر لتجذب عينيك أولًا. وبعد أن تمرر الصفحة للأسفل ستجد عملية من ثلاث خطوات تفصلها عن بعضها مساحات بيضاء دقيقة. وهكذا يتجه نظرك إلى العناصر الأكثر أهميةً في الصفحة بفضل الحيز الفارغ الذي يحيط بها. جزء المحتوى الذي تعرضه يمكن أن تفصل المساحات البيضاء بين النصوص في صفحات الويب وبقية الأجزاء لوضع ما يتشابه من أفكار في حيز واحد، مما يزيد سهولة القراءة. تساعد المساحات البيضاء بين أسطر النصوص والعناوين والمقاطع على فهم النص بالترتيب الصحيح. كما تجعل المساحات البيضاء الانتقال من موضوع إلى آخر في صفحة الويب أكثر سلاسة. يشير المبدأ الخامس "لفت الانتباه drawing attention" من المبادئ الأساسية لتصميم الصفحات المرتكز على نسبة التحول إلى ضرورة جذب الانتباه إلى الأجزاء الأكثر أهميةً من الصفحة. وبالتالي عندما تجزئ المحتوى باستخدام المساحات البيضاء، ستساعد الزوار بمنح كل جزء العناية التي يستحقها. ألقِ نظرةً في الصورة التالية على الطريقة التي نفّذت بها منصة Sundae صفحة ويب واضحة للغاية باستخدام الفكرة السابقة: لقد أحاطت sundae كل نقطة رئيسية في كل قسم بمساحة بيضاء لجذب الانتباه، كما دمجت بين خلفيتين باللونين الأبيض والرمادي مع الكثير من المساحات الفارغة لفصل قسمي الصفحة عن بعضهما كما يظهر في الصورة حقق تأثيرا أكبر وفقًا لمبدأ لفت الانتباه السابق، ستسمح المساحات البيضاء بمنح أثر أكبر للمساحات غير البيضاء، فالعناصر الأساسية المُحاطة بمساحات بيضاء ستبرز بوضوح عندما لا تجاورها عناصر تشتت الانتباه عنها. لاحظ كيف مزجت صفحة أمازون المساحات البيضاء مع مبدأ لفت الانتباه لأي عنصر اتخاذ قرار: تحيط الصفحة الميزات وشهادات المستخدمين بكمية مناسبة من المساحات البيضاء، ثم تعرض عنصر اتخاذ القرار ("استعد Get started") محاطًا بمساحة بيضاء، ثم تكشف التفاصيل الإضافية في قسم الأسئلة الأكثر تكرارًا FAQ بأقل كمية ممكنة من الفراغات البيضاء. تدعوك أمازون إلى الاستعداد لرحلة الشراء بعد استعراضٍ لطيف لمنافع المنتج، فإن أردت التنقيب أكثر في التفاصيل، فعليك بقسم الأسئلة الأكثر تكرارًا. عزز تجاوب صفحتك بما يرضي المستخدم لا بد أنك تريد عرض ما حققته باستخدام المساحات البيضاء على كل منصة تعرض عليها صفحتك، لذلك ابقِ على المساحات البيضاء ثابتةً في كل التنسيقات التي تُعرض بها الصفحة بما في ذلك على الهواتف المحمولة، وتأكد بالإضافة إلى ذلك أنّ المساحات البيضاء لن تدفع بقية العناصر بعيدًا عن المشهد عند عرض الصفحة على الحواسب المكتبية، أو أن تتداخل على الأجهزة الأصغر. يشير المبدأ السابع "تخفيض التلامس reducing friction" من المبادئ الأساسية لتصميم الصفحات المرتكز على نسبة التحول، إلى هذا الأمر تمامًا. ووفقًا لمنصة Statcounter، فإن 49.97% من الحركة على الإنترنت مصدرها مستخدمي الحواسب المكتبية وترتفع النسبة إلى 55.31% في الهواتف المحمولة. إذًا، لا بد من تقديم تجربة مميزة لمستخدمي الهواتف المحمولة عند زيارة صفحتك أيضًا. لاحظ كيف وازنت صفحة ويب ClaimCompass المخصصة للعرض على الهواتف المحمولة بين المساحات البيضاء وسهولة الاستخدام. تلفت الصفحة في القسم الأول الانتباه إلى الخلفية بين العنوان الرئيسي والعناوين الفرعية، ثم تفصل بين علامات التحقق التي تصف الفوائد المقدّمة بمساحات بيضاء دقيقة لتجعلها أكثر قابليةً للقراءة. تظهر جميع العناصر وكأنها في المكان المناسب حتى المساحات البيضاء المستخدمة في تنسيق الهواتف المحمولة. ابدأ باستخدام المساحات البيضاء لا تستهن بقدرة المساحات البيضاء. عندما تمنح العناصر في صفحتك مساحةً كافية، فستخدم الغرض الذي وجدت من أجله بفعالية أكبر، مما يزيد من فرصة رفع نسبة تحول الزوار إلى عملاء أيضًا. ترجمة -وبتصرف- للمقال How White Space Can Boost Your Landing Page Conversions لكاتبه Josh Gallant. اقرأ أيضًا كيفية تحسين إعدادات تطبيق بناء على تجربة المستخدم تحليل تجربة المستخدم ونصائح لتحويل الزوار إلى عملاء التحقق من سهولة الوصول لصفحات الويب الدلالات المضمنة لعناصر صفحة الويب ودورها في تعزيز سهولة الوصول النسخة العربية الكاملة من كتاب مدخل إلى تجربة المستخدم (User Experience - UX)
-
هناك الكثير من اﻷمور التي ستساعدك في العمل على أدوبي إليستريتور Adobe Illustrator. مثل محرر رسوميات شعاعية vector graphics editor، يُعد إليستريتور برنامجًا فعالًا يساعد المصممين على إنشاء وتعديل الرسوميات. لقد جاءت فكرة البرنامج من شركة آبل وصُمم ليعمل على ويندوز وماك أو إس، لكن ليس من السهل العمل على إليستريتور، لذلك يستخدم مصممي الرسوميات المتمرسين حيلًا تبسط العمل لا يعرفها الكثيرون. سيركز هذا المقال على بعض الحيل المستخدمة في إليستريتور عند العمل على رسومياتك، بحيث قد تصنع هذه الحيل الفرق وتسهل عملك، ملقيةً حملًا ثقيلًا عن كاهلك. استخدام القلم يُعَد القلم Pen أكثر أدوات إليستريتور قوةً وفعالية، حيث يساعدك في تحديد وتوزيع نقاط الارتكاز anchor points عند تعديل المنحني الذي يصل بينها. ويمكن إنجاز ذلك عن طريق التحكم بنقطتين ترتبطان بكل نقطة ارتكاز. قد يكون التعامل مع القلم مربكًا أحيانًا، لكنه فعّال جدًا عند متابعة الخطوط ورسم أشعة مخصصة. وفي الوقت ذاته، هي الأداة التي يفضلها معظم المصممين المتمرسين نظرًا للّمسة المتقنة التي تقدمها. وإن كنت مبتدئًا، فبإمكانك تعلم استخدامها عبر التمرينات؛ أما إذا كنت قد استخدمته سابقًا، فاكتسب خبرةً أكبر بالاستخدام المتكرر له. حول النص إلى صورة يمكنك في اليستريتور تحويل النصوص إلى صور أو رسومات، وذلك بتحويلها إلى كائنات Objects، وبالتالي لن تكون قادرًا بعدها على تعديل النصوص باستخدام الوظائف المخصصة لذلك، إذ لن تتمكن من تحرير النص بالكتابة فوقه، أو بالضغط على المفتاح Backspace عند تحويله إلى كائن. يريحك هذا الأمر من إرسال خطوط الكتابة Fonts إلى من يريد استخدام ملف التصميم، إذ ستواجهك مثلًا مشكلة الطباعة الخاطئة لتصميمك عندما تُرسله إلى المطبعة إن لم تكن لديها الخطوط نفسها التي استخدمتها. وهذا هو المبرر الرئيسي لتحويل النصوص إلى صور. سيكون بالإمكان معالجة النص الذي حولته إلى كائن مثل غيره من الرسوميات، وهذه حيلة ممتازة في إليستريتور. استخدم قناع الاقتطاع قناع الاقتطاع Clipping mask هو كائن يغطي العمل الفني لتظهر منه فقط المناطق التي تقع داخل القناع. ويمكن أن نقول بأنه أداة لاقتطاع أجزاء من العمل بما يتلاءم مع شكل القناع. تُعرَف المجموعة المقتطعة بأنها مجموعة من الكائنات المقنّعة (المغطاة)، بالإضافة إلى قناع الاقتطاع. لتسهيل مشاريع التصميم التي تُنفّذها، عليك التعرف على قناع الاقتطاع جيدًا وفهم طريقة عمله، إذ يمكِّنك القناع من إضافة عناصر أو إزالتها أو تعديلها ضمنه، وسيساعدك كثيرًا في إنجاز تصميمات رسومية وأعمال فنية مميزة. تعرف على لوحة التحكم بالمسارات والحدود لوحة التحكم بالمسارات Stroke panel هي لوحة تضم أدوات متنوعةً لإدارة خطوط الرسم وحدود الكائنات والمسارات التي ترسمها بعض الأدوات في تصميمك. تقع اللوحة في الناحية اليمنى من الشاشة وتتيح لك إمكانية ضبط لون وحجم المسار أو الخط المحدد. لا بدّ من الاطلاع على وظيفة كل أداة موجودة في اللوحة، وبإمكانك إظهار بقية الأدوات في اللوحة بالنقر على إظهار الخيارات show options. ركز على أدوات معينة، مثل أداة نهايات Cap وزوايا Corners ومحاذاة المسار Align stroke وتنسيق العرض Width Profile، واستغلها لصالحك. تساعدك هذه الأدوات على إضفاء لمستك الشخصية على عملك وتحسينه على أصعدة مختلفة. سيكون حجم المسار الذي ترسمه محددًا افتراضيًا، وبالطبع ستغير واجهة تصميمك إن تعلّمت كيف تتحكم وتعدل حجم هذه المسارات. استخدم اختصارات اللصق بطريقة مثالية إنّ معرفة اختصارات لوحة المفاتيح لإنجاز الوظائف في إليستريتور أمر مهم جدًا، خاصةً عندما يداهمك الوقت. فتنفيذ كل شيء باستخدام الفأرة سيستغرق وقتًا أطول. يستخدم المصممون تعليمتي النسخ واللصق في كل مشاريعهم، لذلك من الأجدى تنفيذ هاتين التعليمتين باستخدام الاختصارات لتوفير الوقت. يمكن إنجاز ذلك من خلال أشرطة القائمة Menu bars، لكننا سنقدم لك الاختصارات التي ستساعدك على تقليل الزمن اللازم لإنهاء مشروعك بالكامل. CTRL+F: ينسخ كامل الكائن الرسومي ويضعه فوق النسخة القديمة. يساعدك هذا الاختصار على إنجاز عملية محاذاة مثالية من ناحية تطابق اليكسلات مع النسخة القديمة. CTRL+B: ينسخ الكائن المحدد ويلصق النسخة خلف الأصل. الضغط على ALT أثناء سحب كائن: تؤدي هذه العملية إلى نسخ الكائن الذي تسحبه، ولا بدّ من أن تضغط باستمرار على ALT حتى ترفع إصبعك عن الفأرة، فإن رفعت يدك عن ALT قبل زر الفأرة، فستنفذ عملية سحب فقط. الضغط على SHIFT أثناء سحب كائن: ستظهر لك إرشادات إلى المكان الأنسب لنقل الكائن، وتفيدك هذه الحيلة في إيجاد التراصف المثالي للكائن عندما تنقله من مكان لآخر. فصل الأدوات التي تستعمل باستمرار عن شريط الأدوات يفضل المصممون أدوات بعينها أثناء العمل ويستخدمونها بكثرة، لذلك ننصح دائمًا بفصل هذه الأدوات عن شريط الأدوات، فبدلًا من أن تكون في مكان ثابت، فمن الأفضل أن تبقى حرةً تستطيع أن نقلها إلى المكان الذي يناسبك. انتبه إلى أنك لا تستطيع فصل أية أداة عن شريط الأدوات، فهناك أدوات لا يمكن فصلها. تسهّل هذه الإمكانية التي يقدمها شريط أدوات إليستريتور عمل المصممين، كما توفر الوقت المستغرق في البحث عن أداة محددة يستخدمونها باستمرار. وحتى لو استخدمت أداةً ما عدة مرات فقط في مشروعك، فمن الأفضل تركها تطفو حرة لتصل إليها بسهولة. أنشئ الصناديق مدورة الزوايا بطريقة أكثر مرونة يستخدم المصممون باستمرار هذه الأيام عناصرًا تُدعى بالصناديق مدورة الزوايا Rounded Boxes، وقد ترى هذه العناصر في أي مكان ضمن التصاميم التي يجري تطويرها حاليًا. يزوّدك إليستريتور بأداة خاصة لرسم هذه العناصر تُدعى المربع المدوّر Rounded-rectangle، لكنها تفتقد المرونة اللازمة في العمل، ويجد المصممون صعوبةً في استخدامها. فحالما تُنشئ هذا العنصر على الورقة، سيتوجب عليك الانتقال من نقطة ارتكاز إلى أخرى لضبط شكله وأبعاده، وقد يقودك ذلك إلى تفكيك الشكل، أو أن يغدو شكلًا غريبًا غير محدد المعالم. لتفادي هذه المشكلة، أنشئ مربعًا نمطيًا، ومن ثم انتقل بالفأرة إلى الخيار تأثيرات Effect، ثم أضف تنسيقًا Stylize، ثم زوايا مدورة Rounded Corners. يساعدك ذلك على إنشاء مربع مدوّر الزوايا انطلاقًا من مربع نمطي. ستكون النتيجة مربعًا مرن الاستخدام، بحيث يسهل تغيير حجمه بالمقدار الذي تريده. ولكي تغير قياس الزاوية، انتقل إلى لوحة المظهر appearance panel، وانقر نقرًا مزودجًا على زر تنسيق الزوايا المدورة rounded corner style. استخدم مكتبات الرموز من أهم ميزات إليستريتور هو قدرته على تخزين الكائنات الرسومية والرموز ضمن لوحة الرموز Symbols panel، ثم إعادة استخدامها. ستكون قادرًا بفضل هذه الميزة العظيمة على حفظ كائناتك الرسومية والرموز، ثم إعادة استخدامها في أي مكان تشاء ضمن مشروعك الحالي، أو أية مشاريع جديدة أخرى. توفّر لك لوحة الرموز مكتبةً جاهزةً للاستخدام تظهر فائدتها عند حاجتك إلى إعادة استخدام بعض الرموز أو الكائنات التي عملت عليها سابقًا. وتُضاف الرموز إلى اللوحة، من خلال تحديد هذه الرموز أولًا، ثم النقر على زر رمز جديد New Symbol في لوحة الرموز. بعد أن تضيف الرمز بنجاح إلى المكتبة، يمكنك استخدامه بسحبه بكل بساطة من لوحة الرموز إلى ورقة العمل. وأهم ما في الأمر، هو أنّ هذه الرموز قابلة للتعديل بالطريقة التي تشاء، لكن بمجرد أن تعدّل الرمز ستتغير جميع نسخه التي استخدمتها في ورقة العمل. ولتعديل الرمز، عليك النقر عليه ضمن ورقة العمل نقرًا مزدوجًا، وتذكر أنك حين تعدل الرمز، فكل ما تفعله سينسحب على أيقونة الرمز ضمن اللوحة، وعلى كل نسخه التي استخدمتها في ورقة العمل. أنشئ فراشيك الخاصة لا يعرف المبتدئون أنه بالإمكان إنشاء فرشاة خاصة بهم، لكن المصممين المتقدمين يدركون ذلك. فإن أدركت أن الفرشاة التي تعمل بها لا تلبي حاجاتك، فيمكن دائمًا إنشاء فرشاة جديدة خاصة بك. عليك أولًا اختيار الشكل الذي ستبني فرشاتك انطلاقًا منه، وبإمكانك إنشاء الفرشاة أيضًا من أداة المربع. انتقل بعد ذلك إلى لوحة الفراشي Brushes palette وانقر على السهم لاختيار فرشاة جديدة New Brush. عليك اختيار فرشاة فنية جديدة New Art Brush من بين الخيارات الأربعة الموجودة، ثم تخصيص، بعد ذلك اتجاه مسار الفرشاة brush stroke لكي يكون مسارها متقنًا. يُنصح دائمًا أن تنشئ عدة فراشي مخصصة لكي تختار ما يناسبك منها وتبدأ عملك. استفد من الدورات التعليمية التي تُرشدك إلى إنشاء الفراشي المخصصة إن كنت مبتدئًا. ينشئ المحترفون فراشي خاصة لتعزيز عملهم الفني، حيث تمكّنهم من العمل على مشاريع مختلفة وفقًا لما يحتاجه العملاء. وهذه أحد الحيل الرائعة التي ينبغي عليك استخدامها في إليستريتور. استخدام عدة لوحات فنية في نفس المستند يشتهر إليستريتور بكونه برنامج تصميم رسوميات قوي جدًا، وذلك نظرًا لامكانياته المتنوعة، فمن هذه الإمكانيات، نجد إنشاء عدة لوحات فنية artboards في المستند نفسه. تُعرّف اللوحة الفنية بأنها المنطقة القابلة للطباعة التي تطوّر فيها رسوماتك، ويساعدك ذلك عندما تحتاج لعمل عدة لوحات فنية في المستند التي تعمل عليه، مثل حالة تصميم عدة صفحات ويب في نفس الموقع. لكل لوحة فنية عالمها الخاص، لكنها تشترك جميعها بالطبقات layers. بإمكانك استخدام الاختصار CTRL+O لإنشاء لوحة فنية جديدة، كما يمكنك تغيير حجمها ونسخها أو حذفها كما تشاء، إذًا فاللوحات الفنية أمر مشابه لصفحات مرنة في المستند نفسه. استخدم قناع العتامة قناع العتامة opacity mask هو مزيج بين قناع الاقتطاع وقناع الطبقات، إذ يمكنك التحكم بمظهر الشكل أو الرمز باستخدام هذا القناع. يُستخدم قناع العتامة أيضًا في مزج ألوان مختلفة بانتظام لكي يبدو التصميم فريدًا وواضحًا. ولتُنشئ قناعًا ما، أنشئ أولًا شكلًا ثم انتقل إلى لوحة الشفافية transparency panel، حيث يمكنك ملاحظة صورة مصغرة لعرض الشكل. انقر نقرًا مزدوجًا على البقعة الموجودة على يمين هذه الصورة لتمكين القناع. يعمل قناع العتامة في الستريتور بالطريق ذاتها التي يعمل فيها في برنامج أدوبي فوتوشوب، حيث يمكنك استخدام اللون الأسود لإخفاء العناصر والأبيض لإظهارها. ولكي تجرب الأمر قليلًا، حاول أن تستخدم التدرجات اللونية gradients وأن تغير العتامة. سيعطيك ذلك تأثيرات رائعة على الرسوميات المختارة، وهكذا سيكون عمل قناع العتامة. ستكون قادرًا على تصميم أعمال فنية مميزة باستخدام قناع العتامة إن تعلّمت كيف تستخدمه بذكاء وفعالية. يُنصح المبتدئون بتعلم المزيد من خلال الدورات التعليمية على الإنترنت، ويمكن للمتمرسين تطبيق ذلك مباشرةً في مشاريعهم. فكلما استخدمت أداة أكثر، خطرت في بالك أفكار جديدة في استخدامها. خلاصة ركّزنا كثيرًا على استخدام عدة حيل تزيد من فعالية عملك على مشاريع تصميم الرسوميات، كما أشرنا إلى إعادة استخدام الرموز والعناصر التي ترغب في استخدامها مرارًا في مشاريعك. ستوفر الكثير من الوقت عند اتقانك لهذه الحيل، وستساعدك على تصميم أعمال فنية مذهلة بكل سهولة. يمكن للمبتدئين العودة إلى هذه النصائح والحيل بعد تعلم أساسيات العمل على الستريتور ليحثوا الخطى في تطوير أعمالهم. ترجمة -وبتصرف- للمقال 11 Amazing Illustrator Hacks to Boost Your Work لكاتبه Harsh Raval. اقرأ أيضًا رسم خلفية شاطئ صيفي ببرنامج الإليستريتور كيفية رسم طائر النحام الوردي باستخدام برنامج أدوبي إليستريتور كيفية إنشاء ميزان مطبخ واقعي باستخدام إليستريتور كيفية إنشاء عربة يدوية مليئة بالرمال باستخدام إليستريتور
-
تعرفنا في المقال السابق مكونات الويب: عناصر HTML المخصصة وقوالبها على عناصر HTML المخصصة وكيفية إنشائها وسنكمل التعرف على موضوع مهم في هذا الصدد وهو شجرة DOM الخفية Shadow DOM -والتي تترجم أيضًا إلى ظل شجرة DOM- تستخدَم للتغليف encapsulation، والتي تسمح لكل مكوِّن ويب مخصص أن يمتلك شجرةً خفيةً لا يمكن للمستند الرئيسي الوصول إليها صدفةً، وقد تمتلك قواعد تنسيق خاصة وغير ذلك من الخواص. شجرة DOM الخفية هل لديك فكرة عن مدى تعقيد إنشاء وتنسيق أدوات تحكم المتصفح؟ لنفترض وجود العنصر <"input type="range> مثلًا: input[type="range" i] { appearance: auto; cursor: default; color: rgb(157, 150, 142); padding: initial; border: initial; margin: 2px; } يستخدم المتصفح ضمنيًا DOM/CSS لرسم أدوات التحكم، ولا تظهر بنية شجرة DOM لنا عادةً، لكن يمكننا رؤيتها باستخدام أدوات التطوير بتفعيل الخيار Show user agent shadow DOM من إعدادات أدوات المطور Dev Tools تحديدًا قسم Elements في المتصفح Chrome مثلًا. ستبدو الشجرة DOM للعنصر السابق كالتالي: ما نراه تحت العنوان shadow-root# يسمَّى شجرة DOM المخفية، ولا يمكن الحصول على عناصر شجرة DOM المخفية المضمنة باستخدام استدعاءات JavaScript النظامية أو المحددات selectors، فهذه العناصر ليست أبناءً نظاميةً، بل هي تقنية تغليف متقدمة. سنرى في المثال السابق السمة المفيدة pseudo، وهي سمة غير قياسية وموجودة لأسباب تاريخية، حيث يمكننا استخدامها لتنسيق العناصر الفرعية باستخدام CSS كالتالي: <style> /* make the slider track red */ input::-webkit-slider-runnable-track { background: red; } </style> <input type="range"> See the Pen JS-p3-06-Shadow-DOM -ex1 by Hsoub (@Hsoub) on CodePen. بدأت المتصفحات باختبار الشيفرة بالاستفادة من بنى DOM الداخلية لتجهيز أدوات التحكم، وبعد فترة من الزمن طوّرت شجرة DOM الخفية وأصبحت معياريةً لتسمح لنا وللمطورين بالقيام بأشياء مماثلة. سنستخدم لاحقًا المعيار الحديث لشجرة DOM الخفية الذي تغطيه مواصفات DOM وغيرها من المواصفات ذات الصلة. الشجرة الخفية Shadow tree يمكن لكائن DOM أن يمتلك نوعين من الأشجار الفرعية: شجرة ظاهرة Light tree: وتمثل شجرةً فرعيةً نظاميةً مكوّنةً من عناصر HTML أبناء، وجميع الأشجار التي رأيناها في المقال السابق من هذا النوع. شجرة خفية Shadow tree: شجرة DOM فرعية لا تظهر على صفحة HTML، بل تكون مخفيةً من الظهور والرؤية. إذا امتلك العنصر الشجرتين السابقتين فسيصيّر المتصفح الشجرة الخفية، لكن يمكن إعداد توليفة بين الشجرتين، وسنرى تفاصيل أكثر في مقالات لاحقة. يمكن استخدام الشجرة الخفية في العناصر المخصصة لإخفاء المكوّنات الداخلية وتطبيق تنسيق خاص بالمكوِّن. يخفي العنصر <show-hello> على سبيل المثال شجرة DOM الداخلية ضمن شجرة خفية: <script> customElements.define('show-hello', class extends HTMLElement { connectedCallback() { const shadow = this.attachShadow({mode: 'open'}); shadow.innerHTML = `<p> Hello, ${this.getAttribute('name')} </p>`; } }); </script> <show-hello name="John"></show-hello> See the Pen JS-p3-06-Shadow-DOM -ex2 by Hsoub (@Hsoub) on CodePen. ستظهر النتيجة في أدوات تطوير متصفح بالشكل التالي، حيث تكون جميع المحتويات تحت "shadow-root#": يُنشئ الاستدعاء ({...:elem.attachShadow({mode شجرةً مخفيةً أولًا، لكن توجد عقبتان: يمكن إنشاء جذر مخفي واحد لكل عنصر. يجب أن يكون العنصر عنصرًا مخصصًا، أو أحد العناصر التالية: "article". "blockquote". "body". "div". "footer". "h1…h6". "header". "main". "nav". "p". "section". "span". ولا يمكن لغير هذه العناصر استضافة شجرة خفية. يضبط الخيار mode مستوى التغليف، ويمكن أن يحمل إحدى القيمتين: "open": يمكن أن نحصل على الجذر الخفي من خلال elem.shadowRoot، ويمكن لأي شيفرة الوصول إلى الشجرة الخفية لــ elem. "closed": ويعيد الأمر elem.shadowRoot القيمة null دائمًا. يمكن الوصول إلى شجرة DOM الخفية عن طريق المرجع الذي يعيده attachShadow فقط، ومن الممكن أن يكون مخفيًا ضمن صنف، وتكون الأشجار الخفية الأصلية المتعلقة بالمتصفح -مثل <input type="range">- من النوع المغلق، ولا يمكن الوصول إليها، ويشابه الجذر الخفي shadow root الذي يعيده attachShadow العناصر، ويمكن إظهاره باستخدام innerHTML أو توابع DOM مثل append. يُدعى العنصر الذي يمتلك جذرًا خفيًا بمضيف الشجرة الخفية shadow tree host، ويتاح للاستخدام ضمن الخاصية host للجذر الخفي: // لن يعيد شيئًا elem.shadowRoot وإلا {mode: "open"} بافتراض أن alert(elem.shadowRoot.host === elem); // محقق التغليف Encapsulation تفصَل شجرة DOM الخفية عن المستند الرئيسي: لا تكون عناصر شجرة DOM الخفية مرئيةً للتابع querySelector من شجرة DOM الظاهرة، وقد تمتلك عناصر شجرة DOM الخفية معرفات id تتعارض مع تلك الموجودة في شجرة DOM الظاهرة، فهي فريدة ضمن الشجرة الخفية فقط. لشجرة DOM الخفية أوراق تنسيق خاصة بها، إذ لا تُطبق قواعد التنسيق الخارجية عليها. إليك مثالًا: <style> /*(1) #elem لن يطبق التنسيق على الشجرة الخفية للعنصر*/ p { color: red; } </style> <div id="elem"></div> <script> elem.attachShadow({mode: 'open'}); // (للشجرة الخفية تنسيقها الخاص (2 elem.shadowRoot.innerHTML = ` <style> p { font-weight: bold; } </style> <p>Hello, John!</p> `; // مرئية فقط للاستعلامات التي تجري داخل الشجرة <p> (3) alert(document.querySelectorAll('p').length); // 0 alert(elem.shadowRoot.querySelectorAll('p').length); // 1 </script> تشير (1) و(2) و(3) أعلاه إلى الآتي: (1) لا يؤثر تنسيق الصفحة على تنسيق الشجرة الخفية. (2) يؤدي التنسيق داخل الشجرة عمله بطريقة صحيحة. (3) للحصول على العناصر من الشجرة الخفية، لا بدّ من الاستعلام عنها من داخل الشجرة. تقليم شجرة DOM الخفية وعملية التركيب تحتاج العديد من المكوّنات إلى محتوىً لتصييره، مثل النوافذ ومعارض الصور والقوائم وغيرها، ويتوقع العنصر المخصص <custom-tabs> محتوى نافذة ليمرَّر إليه، على غرار العناصر الأصلية المضمنة في المتصفح مثل <select> -عدا عناصر <option>-، ويحتاج العنصر <custom-menu> كذلك إلى محتوى على شكل عناصر قائمة. قد تبدو الشيفرة التي تستعمل العنصر <custom-menu> كالتالي: <custom-menu> <title>Candy menu</title> <item>Lollipop</item> <item>Fruit Toast</item> <item>Cup Cake</item> </custom-menu> سيتمكن بعدها مكوّننا من تصيير المحتوى بصورة صحيحة، على شكل قائمة جميلة لها عنوان وعناصر ومعالجات لأحداث القائمة وغيرها، ولكن كيف سننجز ذلك؟ يمكننا أن نحاول تحليل محتوى العناصر وأن ننسخ أو نعيد ترتيب عقد DOM ديناميكيًا، لكن إذا كنا سننقل عناصر إلى شجرة DOM الخفية shadow DOM، فلن يُطبَّق تنسيق CSS الموجود ضمن المستند عليها، وبالتالي سنفقد التنسيق المرئي، وسيحتاج ذلك إلى كتابة بعض الشيفرة، ولحسن الحظ ليس علينا فعل ذلك، إذ تدعم شجرة DOM الخفية عناصر <slot> التي تُملأ تلقائيًا بالمحتوى الموجود في شجرة DOM الظاهرة وشبهنا هذه العملية بعملية "التقليم" التي تجرى على الأشجار من قص وإنشاء فتحة في الشجرة ووضع فرع في الفتحة من شجرة أو نوع آخر لذلك أطلقنا عليها هذا الاسم وسنستعمل اسم "فتحة" المقابل للكلمة الأجنبية slot (اسم العنصر) للسهولة. الفتحات المسماة Named slots لنلق نظرةً على طريقة عمل التقليمات في هذا المثال البسيط، حيث توفر شجرة DOM الخفية للعنصر المخصص <user-card> فتحتين تُملآن من شجرة DOM الظاهرة: <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <div>Name: <slot name="username"></slot> </div> <div>Birthday: <slot name="birthday"></slot> </div> `; } }); </script> <user-card> <span slot="username">John Smith</span> <span slot="birthday">01.01.2001</span> </user-card> See the Pen JS-p3-06-Shadow-DOM -ex3 by Hsoub (@Hsoub) on CodePen. يعرّف العنصر <"slot name="X> في شجرة DOM الخفية نقطة إدراج، وهو المكان الذي تُصيَّر فيه العناصر التي تعرف فيها الخاصية "slot="X، ثم ينفِّذ المتصفح عملية تركيب composition، إذ يأخذ عناصر من شجرة DOM الظاهرة، ويصيّرها ضمن الفتحات الملائمة ضمن شجرة DOM الخفية، وهكذا سنحصل في نهاية الأمر على مكوِّن يمكن ملؤه بالبيانات، وهذا ما نريده. إليك بنية الشجرة DOM بعد تنفيذ السكربت مهملين عملية التركيب: <user-card> #shadow-root <div>Name: <slot name="username"></slot> </div> <div>Birthday: <slot name="birthday"></slot> </div> <span slot="username">John Smith</span> <span slot="birthday">01.01.2001</span> </user-card> لقد أنشأنا شجرة DOM خفيةً، وسنجدها تحت #shadow-root. سيمتلك العنصر الآن شجرتي DOM الظاهرة والخفية. سيبحث المتصفح -لأغراض التصيير- عن السمة "..."=slot لكل <"..."=slot name> التي لها نفس الاسم في شجرة DOM الظاهرة، وستُصيَّر هذه العناصر ضمن الفتحات. وعندها ستسمى النتيجة شجرة DOM المسطّحة flattened DOM. <user-card> #shadow-root <div>Name: <slot name="username"> <!-- slotted element is inserted into the slot --> <span slot="username">John Smith</span> </slot> </div> <div>Birthday: <slot name="birthday"> <span slot="birthday">01.01.2001</span> </slot> </div> </user-card> لقد أوُجِدت شجرة DOM المسطحة لأغراض التصيير ومعالجة الأحداث، قد تبدو الطريقة التي تُعرض بها الأمور افتراضيةً نوعًا ما، لكن لن تُنقَل العقد في المستند فعليًا، فمن السهل التحقق من أنّ العقد لا تزال في مكانها بتنفيذ الأمر querySelectorAll. alert( document.querySelectorAll('user-card span').length ); // 2 تُشتقّ شجرة DOM المسطحة من شجرة DOM الخفية بإدراج الفتحات، حيث يصيّرها المتصفح ويستخدمها في وراثة التنسيق وانتقال الأحداث -وهو ما سنوضّحه لاحقًا-، لكن نظرتها لن تغير JavaScript إلى المستند قبل الوصول إلى الشجرة المسطحة. ملاحظة: يمكن للأبناء من المستوى الأعلى فقط امتلاك السمة "…"=slot، إذ يمتلك الأبناء المباشرون للمضيف الخفي -وهو العنصر <user-card> في مثالنا- السمة "..."=slot فقط، بينما ستُهمل هذه السمة للعناصر الداخلية. وسيتجاهل المتصفح في المثال التالي العنصر <span> الثاني، فهو ليس ابنًا مباشرًا للعنصر <user-card>: <user-card> <span slot="username">John Smith</span> <div> <!-- invalid slot, must be direct child of user-card --> <span slot="birthday">01.01.2001</span> </div> </user-card> إذا وجدت عناصر لها فتحات بنفس الاسم في شجرة DOM الظاهرة، فستوضع في الفتحة واحدةً تلو الأخرى، إليك المثال التالي: <user-card> <span slot="username">John</span> <span slot="username">Smith</span> </user-card> إليك مثالًا عن شجرة DOM مسطحة تمتلك عنصرين ضمن الفتحة <"slot name="username>: <user-card> #shadow-root <div>Name: <slot name="username"> <span slot="username">John</span> <span slot="username">Smith</span> </slot> </div> <div>Birthday: <slot name="birthday"></slot> </div> </user-card> المحتوى الابتدائي للعنصر slot إذا وُضع محتوًى ما ضمن الفتحة <slot>، فسيصبح هو المحتوى الافتراضي حيث سيعرضه المتصفح إذا لم يوجد محتوىً مناسب ضمن شجرة DOM الظاهرة لملء الفتحة. عندها ستكون نتيجة تصيير هذا الجزء مثلًا من شجرة DOM الخفية هي Anonymous إذا لم توجد فتحة باسم "slot="username في شجرة DOM الظاهرة. <div>Name: <slot name="username">Anonymous</slot> </div> الفتحة الافتراضية هي أول فتحة بلا اسم ستكون أول فتحة <slot> في شجرة DOM الخفية غير مالكة لاسم هي الفتحة الافتراضية، وستحصل على كل العقد في شجرة DOM الظاهرة التي لم تُدرج في أي فتحة أخرى. لنضف في المثال التالي فتحةً افتراضيةً للعنصر المخصص <user-card> الذي سيخفي كل معلومات المستخدم غير المدرجة في فتحات: <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <div>Name: <slot name="username"></slot> </div> <div>Birthday: <slot name="birthday"></slot> </div> <fieldset> <legend>Other information</legend> <slot></slot> </fieldset> `; } }); </script> <user-card> <div>I like to swim.</div> <span slot="username">John Smith</span> <span slot="birthday">01.01.2001</span> <div>...And play volleyball too!</div> </user-card> See the Pen JS-p3-06-Shadow-DOM -ex4 by Hsoub (@Hsoub) on CodePen. سيوضع محتوى شجرة DOM الظاهرة غير المُدرج في المثال السابق ضمن العنصر <fieldset> الذي يحمل تسمية Other information، حيث ستُدرج العناصر ضمن الفتحة واحدًا تلو الآخر، ولذا ستُدرج قطعتا المعلومات معًا ضمن الفتحة الافتراضية. وستبدو شجرة DOM المسطحة كالتالي: <user-card> #shadow-root <div>Name: <slot name="username"> <span slot="username">John Smith</span> </slot> </div> <div>Birthday: <slot name="birthday"> <span slot="birthday">01.01.2001</span> </slot> </div> <fieldset> <legend>Other information</legend> <slot> <div>I like to swim.</div> <div>...And play volleyball too!</div> </slot> </fieldset> </user-card> مثال القائمة لنعد الآن إلى العنصر المخصص الذي أشرنا إليه سابقًا في هذا المقال، حيث يمكن استخدام الفتحات لتوزيع العناصر، إليك ترميز HTML للعنصر المخصص <custom-menu>: <custom-menu> <span slot="title">Candy menu</span> <li slot="item">Lollipop</li> <li slot="item">Fruit Toast</li> <li slot="item">Cup Cake</li> </custom-menu> تمثل الشيفرة التالية قالب شجرة DOM الخفية مزوّدةً بالفتحات الملائمة: <template id="tmpl"> <style> /* menu styles */ </style> <div class="menu"> <slot name="title"></slot> <ul><slot name="item"></slot></ul> </div> </template> يُدرج <"span slot="title> ضمن <"slot name="title>. هناك العديد من عناصر القائمة <li slot="item"> في القالب، لكن توجد فتحة مناسبة <slot name="item"> واحدة ضمنه، لذلك ستُدرج كل عناصر القائمة -مثل <li slot="item">- ضمن الفتحة الواحد تلو الآخر، وهذا ما يُشكِّل القائمة. ستصبح شجرة DOM المسطّحة كالتالي: <custom-menu> #shadow-root <style> /* menu styles */ </style> <div class="menu"> <slot name="title"> <span slot="title">Candy menu</span> </slot> <ul> <slot name="item"> <li slot="item">Lollipop</li> <li slot="item">Fruit Toast</li> <li slot="item">Cup Cake</li> </slot> </ul> </div> </custom-menu> قد نُلاحظ تواجد العنصر <li> ضمن القائمة <ul> في شجرة DOM صالحة، لكن الشجرة في الشيفرة السابقة مسطحة تصف آلية تصيير المكوِّن، وهذا الأمر طبيعي، ويبقى علينا إضافة معالج للحدث click لفتح وإغلاق القائمة <custom-menu> لتصبح جاهزةً: customElements.define('custom-menu', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); //هو قالب الشجرة الخفية في الأعلى this.shadowRoot.append( tmpl.content.cloneNode(true) ); // لا يمكن اختيار عقد من الشجرة الظاهرة، لذلك تُعالج النقرات ضمن الفتحة this.shadowRoot.querySelector('slot[name="title"]').onclick = () => { // فتح وإغلاق القائمة this.shadowRoot.querySelector('.menu').classList.toggle('closed'); }; } }); وستكون النتيجة: See the Pen JS-p3-06-Shadow-DOM -ex5 by Hsoub (@Hsoub) on CodePen. يمكن بالطبع تعزيز وظائف القائمة من خلال الأحداث والتوابع وغيرها. تحديث الفتحات ماذا لو أرادت الشيفرة الخارجية إضافة أو إزالة العناصر ديناميكيًا؟ يراقب المتصفح الفتحات، ثم تُحدَّث العناصر المصيّرة إذا تغيرت أو أزيلت العناصر المدرجة، وطالما ستُصيَّر العناصر الظاهرة فقط في الفتحات -ولن تُنسَخ- فستظهر أي تغييرات عليها مباشرةً، فلا حاجة لفعل أي شيء لتحديث نتيجة التصيير، لكن يمكن استخدام الحدث slotchange إذا أرادت شيفرة المكوّن مراقبة التغيرات في الفتحات. يُدرَج في المثال التالي عنصر قائمة ديناميكيًا بعد ثانية، ويتغير العنوان بعد ثانيتين: <custom-menu id="menu"> <span slot="title">Candy menu</span> </custom-menu> <script> customElements.define('custom-menu', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = `<div class="menu"> <slot name="title"></slot> <ul><slot name="item"></slot></ul> </div>`; //لا يمتلك الجذر الخفي معالجات أحداث لذا سنستخدم الابن الأول this.shadowRoot.firstElementChild.addEventListener('slotchange', e => alert("slotchange: " + e.target.name) ); } }); setTimeout(() => { menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Lollipop</li>') }, 1000); setTimeout(() => { menu.querySelector('[slot="title"]').innerHTML = "New menu"; }, 2000); </script> See the Pen JS-p3-06-Shadow-DOM -ex6 by Hsoub (@Hsoub) on CodePen. يُحدَّث تصيير القائمة كل مرة دون تدخلنا، وسنلاحظ في الشيفرة السابقة وجود حدثي slotchange: عند التهيئة: حيث يقع الحدث slotchange: title مباشرةً عندما يُدرج "slot="title من شجرة DOM الظاهرة ضمن الفتحة الموافقة له. بعد ثانية: يقع الحدث slotchange: item عندما يُضاف عنصر قائمة جديد <"li slot="item>. لاحظ أنه لا وجود للحدث slotchange بعد ثانيتين عندما يُعدَّل محتوى "slot="title، والسبب هو عدم وجود أي تغييرات في الفتحة، حيث عدّلنا محتوى عنصر مُدرج في الفتحة، وهذا أمر مختلف. يمكن تتبع التغيرات الداخلية ضمن شجرة DOM الظاهرة باستخدام شيفرة JavaScript من خلال الآلية المعمّمة المعروفة بـ MutationObserver. الواجهة البرمجية للفتحات لنلق نظرةً على توابع JavaScript المتعلقة بالفتحات، إذ تنظر JavaScript كما رأينا سابقًا إلى شجرة DOM الحقيقية دون تسطيح flattening، لكن إذا كانت قيمة الخيار mode في الشجرة الخفية هي "open"، فسنتمكن من معرفة أي العناصر أدرِجت في فتحات، كما يمكننا معرفة الفتحة من العنصر الذي أدرج ضمنه: node.assignedSlot: تعيد الفتحة <slot> التي أسندت إليها عقدة من الشجرة node. ({slot.assignedNodes({flatten: true/false: يعيد عقد DOM التي أسندت إلى فتحة، وستكون قيمة الخيار flatten هي "false" افتراضيًا، لكن إذا ضبطت على القيمة "true" صراحةً، فسيُنظر إلى DOM المسطحة بعمق، كما سيعيد التابع الفتحات المتداخلة nested في حال وجود مكوّنات متداخلة، ويعيد "محتوى التراجع" إذا لم يجد أي عقد أسندت إلى فتحات. ({slot.assignedElements({flatten: true/false: يعيد عناصر DOM التي أسندت إلى فتحة، وهو يشابه التابع السابق إلا أنه يعيد عقد العنصر. تُستخدَم هذه التوابع عندما نريد إظهار محتوى الفتحات وتتبعها باستخدام JavaScript. وإذا أراد المكوّن <custom-menu> مثلًا معرفة ما الذي يعرضه، فيمكنه تعقّب الحدث slotchange والحصول على العناصر من خلال slot.assignedElements: <custom-menu id="menu"> <span slot="title">Candy menu</span> <li slot="item">Lollipop</li> <li slot="item">Fruit Toast</li> </custom-menu> <script> customElements.define('custom-menu', class extends HTMLElement { items = [] connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = `<div class="menu"> <slot name="title"></slot> <ul><slot name="item"></slot></ul> </div>`; // يقع عندما يتغير محتوى الفتحة this.shadowRoot.firstElementChild.addEventListener('slotchange', e => { let slot = e.target; if (slot.name == 'item') { this.items = slot.assignedElements().map(elem => elem.textContent); alert("Items: " + this.items); } }); } }); // items update after 1 second setTimeout(() => { menu.insertAdjacentHTML('beforeEnd', '<li slot="item">Cup Cake</li>')}, 1000); </script> See the Pen Untitled by Hsoub (@Hsoub) on CodePen. تنسيق شجرة DOM الخفية يمكن أن تحتوي شجرة DOM الخفية على الوسمين <style> و<link rel="stylesheet" href="…">، وتُخزّن أوراق التنسيق في الوسم الأخير في ذاكرة HTTP المؤقتة، لذلك لن يُعاد تحميلها للمكونات المختلفة التي تستخدم نفس القالب، ويمكن أن نعد ما لي قاعدةً عامةً: يعمل التنسيق المحلي داخل الشجرة الخفية، بينما تعمل تنسيقات المستند ككل خارجها، مع وجود بعض الاستثناءات. المحدد host: يسمح هذا المحدد باختيار المضيف الخفي، أي العنصر الذي يحتوي الشجرة الخفية، لنفترض مثلًا أننا سننشئ عنصرًا مخصصًا <custom-dialog> يجب أن يتوسط الصفحة، لذلك لا بدّ من تنسيق المكوّن <custom-dialog> ذاته، وهذا ما يفعله host: تمامًا: <template id="tmpl"> <style> /* the style will be applied from inside to the custom-dialog element */ :host { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); display: inline-block; border: 1px solid red; padding: 10px; } </style> <slot></slot> </template> <script> customElements.define('custom-dialog', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}).append(tmpl.content.cloneNode(true)); } }); </script> <custom-dialog> Hello! </custom-dialog> See the Pen JS-p3-06-Shadow-DOM-styling -ex1 by Hsoub (@Hsoub) on CodePen. توريث التنسيقات Cascading يبقى المضيف الخفي -<custom-dialog> ذاته- ضمن شجرة DOM الظاهرة، لذلك سيتأثر بقواعد CSS التي يخضع لها المستند كاملاً، فإذا وجدت خاصية جرى تنسيقها محليًا ضمن host: وفي المستند بنفس الوقت، فسيُطبق حينها تنسيق المستند. فإن كان المستند مثلًا: <style> custom-dialog { padding: 0; } </style> فسيُعرض العنصر <custom-dialog> دون حاشية padding. من الملائم أن نعّرف تنسيقًا افتراضيًا للمكوّن ضمن القاعدة host:، ثم نعيد التنسيق -إذا أردنا- باستخدام تنسيق المستند، ويبقى الاستثناء في الخاصية التي نعنونها important!،عندها يُطبق التنسيق المحلي. المحدد (host(selector: هو نفسه host: لكنه يُطبّق فقط إذا تطابق اسم المضيف الخفي مع الوسيط selector، فعلى سبيل المثال: نريد توسيط المكوّن <custom-dialog> في الصفحة فقط إذا امتلك السمة centered: <template id="tmpl"> <style> :host([centered]) { position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); border-color: blue; } :host { display: inline-block; border: 1px solid red; padding: 10px; } </style> <slot></slot> </template> <script> customElements.define('custom-dialog', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}).append(tmpl.content.cloneNode(true)); } }); </script> <custom-dialog centered> Centered! </custom-dialog> <custom-dialog> Not centered. </custom-dialog> See the Pen JS-p3-06-Shadow-DOM-styling -ex2 by Hsoub (@Hsoub) on CodePen. والآن سيُطبّق التوسيط فقط على المكوّن الأول <custom-dialog centered>. باختصار، يمكن استخدام عائلة المحددات host: لتنسيق العنصر الرئيسي للمكوِّن بناءً على السياق، ويمكن تغيير هذه التنسيقات -ما لم تستخدم السمة important!- من قبل تنسيق المستند. تنسيق محتوى الفتحات لننظر إلى حالة وجود فتحات slots. تأتي المكوّنات المقلمة slotted components من شجرة DOM الظاهرة، لذا ستستخدم تنسيقات المستند، ولن يؤثر التنسيق المحلي على المحتوى المركب. سنرى في المثال التالي أنّ العنصر المركب <span> سيظهر بخط سميك وفق تنسيق المستند، لكنه لن يتأثر بقيمة الخاصية background العائدة للتنسيق المحلي للعنصر: <style> span { font-weight: bold } </style> <user-card> <div slot="username"><span>John Smith</span></div> </user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <style> span { background: red; } </style> Name: <slot name="username"></slot> `; } }); </script> See the Pen JS-p3-06-Shadow-DOM-styling -ex3 by Hsoub (@Hsoub) on CodePen. سيكون الخط سميكًا لكنه ليس باللون الأحمر. وإذا أردنا تنسيق العناصر المركبة في مكوننا فأمامنا خياران هما: الأول هو تنسيق الفتحة <slot> نفسه اعتمادًا على وراثة تنسيق CSS: <user-card> <div slot="username"><span>John Smith</span></div> </user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <style> slot[name="username"] { font-weight: bold; } </style> Name: <slot name="username"></slot> `; } }); </script> See the Pen JS-p3-06-Shadow-DOM-styling -ex4 by Hsoub (@Hsoub) on CodePen. سيكون الخط سميكًا في <p>John Smith</p>، وذلك لأن وراثة CSS ستنقل التنسيق من الفتحة <slot> إلى المحتويات، لكن ليست كل خصائص CSS قابلة للوراثة. الخيار الثاني هو استخدام محدد الصنف الوهمي (slotted(selector::، والتي تطابق العناصر بناءً على شرطين: أن يكون العنصر مركبًا وآتيًا من الشجرة الظاهرة، ولا يهم اسم الفتحة هنا، ويطبق على العنصر المركب ذاته وليس على أبنائه. أن يطابق العنصر قيم الوسيط selector. يختار المحدد (slotted(div:: في مثالنا التالي العنصر <"div slot="username> تمامًا وليس أبناءه: <user-card> <div slot="username"> <div>John Smith</div> </div> </user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = ` <style> ::slotted(div) { border: 1px solid red; } </style> Name: <slot name="username"></slot> `; } }); </script> See the Pen JS-p3-06-Shadow-DOM-styling -ex5 by Hsoub (@Hsoub) on CodePen. لاحظ أنه يمكن للمحدد slotted:: التقدم داخل عناصر الفتحة، لذا تكون المحددات التالية غير صحيحة: ::slotted(div span) { /*مع هذا المحدد div لايتطابق العنصر */ } ::slotted(div) p { /* DOM لا يمكنه التقدم داخل */ } يمكن استخدام slotted:: أيضًا في querySelector. خطافات CSS مع خاصيات مخصصة كيف ننسق العناصر الداخلية للمكوِّن من المستند الرئيسي؟ تطبق المحددات -مثل host:- القواعد على عناصر مخصصة، مثل <custom-dialog> أو <user-card>، لكن كيف ننسق عناصر شجرة DOM الخفية ضمنها؟ لا يوجد محدد قادر على التأثير المباشر على تنسيق شجرة DOM الخفية من المستند الرئيسي، لكن -وكما عرضنا توابع للتفاعل مع مكوننا- يمكن عرض متغيرات CSS -خصائص CSS مخصصة- إلى التنسيق. تتواجد الخصائص المخصصة على كل المستويات وفي كلتا الشجرتين الخفية والظاهرة، حيث يمكن مثلًا استخدام المتغير --user-card-field-color في شجرة DOM الخفية لتنسيق الحقول، ويمكن أن يضبط المستند الخارجي قيمتها: <style> .field { color: var(--user-card-field-color, black); /* معرفًا استخدم اللون الأسود --user-card-field-color */ } </style> <div class="field">Name: <slot name="username"></slot></div> <div class="field">Birthday: <slot name="birthday"></slot></div> عندها يمكن التصريح عن هذه الخاصية في المستند الخارجي للمكوّن <user-card>: user-card { --user-card-field-color: green; } تخترق خاصيات CSS المخصصة شجرة DOM الخفية، وهي مرئية في كل مكان، لذلك يمكن للقاعدة الداخلية field. استخدامها، وإليك مثالًا كاملًا: <style> user-card { --user-card-field-color: green; } </style> <template id="tmpl"> <style> .field { color: var(--user-card-field-color, black); } </style> <div class="field">Name: <slot name="username"></slot></div> <div class="field">Birthday: <slot name="birthday"></slot></div> </template> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.append(document.getElementById('tmpl').content.cloneNode(true)); } }); </script> <user-card> <span slot="username">John Smith</span> <span slot="birthday">01.01.2001</span> </user-card> See the Pen JS-p3-06-Shadow-DOM-styling -ex6 by Hsoub (@Hsoub) on CodePen. شجرة DOM الخفية والأحداث إنّ الفكرة الأساسية من الشجرة الخفية هي تغليف تفاصيل التنفيذ الداخلي للمكوّن. لنفترض وقوع حدث نقرٍ داخل شجرة DOM الخفية للمكوّن <user-card>، لكن لا تملك السكربتات في المستند الرئيسي أي فكرة عن ما يجري داخل الشجرة، وخاصةً إذا أتى المكون من طرف ثالث، لذا يعيد المتصفح استهداف الحدث للمحافظة على تغليف التفاصيل. تستهدف الأحداث التي تحدث في شجرة DOM الخفية العنصر المضيف عندما تُلتقط خارج إطار المكوِّن. وإليك مثالًا بسيطًا: <user-card></user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = `<p> <button>Click me</button> </p>`; this.shadowRoot.firstElementChild.onclick = e => alert("Inner target: " + e.target.tagName); } }); document.onclick = e => alert("Outer target: " + e.target.tagName); </script> See the Pen JS-p3-06-Shadow-DOM-and-events -ex1 by Hsoub (@Hsoub) on CodePen. إذا نقرت على الزر فستظهر الرسائل التالية: الهدف الداخلي BUTTON: معالج حدث داخلي يحصل على الهدف الصحيح، وهو عنصر داخل الشجرة الخفية. الهدف الخارجي USER-CARD: معالج حدث المستند، والذي يحصل على المضيف الخفي مثل هدف. إنّ إعادة استهداف retargeting الأحداث أمر مفيد جدًا، إذ يُفترض أن لا يعرف المستند الخارجي ما يوجد داخل المكوّن، فالحدث قد وقع من وجهة نظره على المكوّن <user-card>. لا تحدث إعادة الاستهداف إذا وقع الحدث في عنصر مُركّب، وذلك لأنه موجود فيزيائيًا في شجرة DOM الظاهرة، فلو نقر مستخدم مثلًا على <span slot="username"> في المثال التالي، فسيستهدف الحدث العنصر span هذا بالتحديد، وفي كلا معالجي الحدث الخاصين بالشجرتين الظاهرة والخفية: <user-card id="userCard"> <span slot="username">John Smith</span> </user-card> <script> customElements.define('user-card', class extends HTMLElement { connectedCallback() { this.attachShadow({mode: 'open'}); this.shadowRoot.innerHTML = `<div> <b>Name:</b> <slot name="username"></slot> </div>`; this.shadowRoot.firstElementChild.onclick = e => alert("Inner target: " + e.target.tagName); } }); userCard.onclick = e => alert(`Outer target: ${e.target.tagName}`); </script> See the Pen JS-p3-06-Shadow-DOM-and-events -ex2 by Hsoub (@Hsoub) on CodePen. فإذا نُقر الاسم "John Smith"، فسيكون الهدف بالنسبة لمعالجي الحدث الداخلي والخارجي هو <"span slot="username>، وهنا لن يُعاد الاستهداف لأنه عنصر من DOM الظاهرة. من ناحية أخرى، إذا نُقر عنصر يعود إلى شجرة DOM الخفية مثل العنصر <b>Name</b>، فسُيعاد ضبط هدف الحدث event.target أثناء انسياب الحدث لخارج شجرة DOM الخفية ليصبح <user-card>. التابعان Bubbling و()event.composedPath تُستخدم DOM المسطحة لدفع الأحداث إلى الخارج bubbling، لذا إذا وُجد عنصر مركب slotted في فتحة ووقع حدث في مكان ما بداخله، فسيندفع الحدث إلى خارج العنصر باتجاه الفتحة <slot> وإلى الأعلى، ويمكن الحصول على المسار الكامل للهدف الأصلي للحدث مع كل عناصر شجرته الخفية باستخدام التابع ()event.composedPath، ويؤخذ هذا المسار -كما يدل اسم التابع- بعد عملية التركيب، وإليك شجرة DOM المسطحة للمثال السابق: <user-card id="userCard"> #shadow-root <div> <b>Name:</b> <slot name="username"> <span slot="username">John Smith</span> </slot> </div> </user-card> عند النقر على <span slot="username">، سيعيد استدعاء التابع ()event.composedPath المصفوفة التالية: [span, slot, div, shadow-root, user-card, body, html, document, window]، وهي شجرة العناصر كاملةً ابتداءًا من العنصر المُستهدف في شجرة DOM المسطحة بعد التركيب. التابع event.composed تنساب معظم الأحداث إلى خارج حدود شجرة DOM الخفية، لكن هذا لا ينطبق على بعضها، وذلك بحسب قيمة كائن الحدث composed، فإذا كانت "true" فستعبر الأحداث حدود الشجرة، وإلا فستلتقَط داخل حدود الشجرة فقط. إذا ألقينا نظرةً على توصيفات أحداث واجهة المستخدم UI Events specification، فسنجد أن قيمة composed هي "true" للأحداث: blur وfocus وfocusin وfocusout. click وdblclick. mousedown وmouseup وmousemove وmouseout وmouseover. wheel. beforeinput وinput وkeydown وkeyup. جميع أحداث اللمس والتأشير. أما الأحداث التي يكون فيها composed: false فهي: mouseenter وmouseleave: التي لا تنساب للخارج إطلاقًا. load وunload وabort وerror. select. slotchange. يمكن التقاط هذه الأحداث من قبل العناصر داخل شجرة DOM نفسها، حيث يقيم العنصر المستهدف. الأحداث المخصصة لا بدّ من ضبط الخاصيتين bubbles وcomposed على القيمة "true" إذا أردنا إيفاد أحداث مخصصة تنساب إلى مستوى أعلى وإلى خارج المكوّن، وسننشئ في المثال التالي div#inner في شجرة DOM الخفية ضمن div#outer ونطلق حدثين فيها، وسيصل الحدث الذي يمتلك الخاصية composed: true فقط إلى الخارج، وصولًا إلى المستند: <div id="outer"></div> <script> outer.attachShadow({mode: 'open'}); let inner = document.createElement('div'); outer.shadowRoot.append(inner); /* div(id=outer) #shadow-dom div(id=inner) */ document.addEventListener('test', event => alert(event.detail)); inner.dispatchEvent(new CustomEvent('test', { bubbles: true, composed: true, detail: "composed" })); inner.dispatchEvent(new CustomEvent('test', { bubbles: true, composed: false, detail: "not composed" })); </script> خلاصة شجرة DOM الخفية هي طريقة لإنشاء شجرة محلية خاصة بالمكوِّن. ({shadowRoot = elem.attachShadow({mode: open|closed: ينشئ شجرة DOM خفيةً للعنصر elem، ويمكن الوصول إليها باستخدام الخاصية elem.shadowRoot إذا كانت الخاصية "mode="open. يمكن تعميم الكائن shadowRoot باستخدام innerHTML أو توابع DOM أخرى. عناصر شجرة DOM الخفية: لها فضاء خاص لقيم المعرّفات id. غير مرئية لمحددات JavaScript الموجودة في المستند الرئيسي، مثل querySelector. تستخدم التنسيق الموجود ضمن الشجرة الخفية فقط، وليس تنسيق المستند الرئيسي. تُصيّر شجرة DOM الخفية -إن وجدت- ضمن ما يُسمى شجرة DOM الظاهرة light DOM، مثل عناصر أبناء نظاميين. إذا امتلك عنصر شجرة DOM خفيةً، فلن تُعرض شجرة DOM الظاهرة عادةً، لذلك تسمح الفتحات slots -الناتجة عن عملية تقليم الشجرة- بعرض عناصر من شجرة DOM الظاهرة في أماكن محددة من شجرة DOM الخفية، ويوجد نوعان من الفتحات: الفتحات المسماة <slot name="X">...</slot>: الحصول على أبناء شجرة DOM الظاهرة فتحتها slot="X". الفتحة الافتراضية: وهي أول فتحة <slot> لا تحمل اسمًا، وتهمَل الفتحات غير المسماة التي تأتي لاحقًا، كما يحصل على أبناء شجرة DOM الظاهرة الذين لم يُدرجوا في فتحات. إذا وجدَت عدة عناصر في الفتحة نفسها، فستُدرج واحدًا بعد الآخر. يُستخدَم محتوى الفتحة <slot> محتوىً للتراجع، لذا يظهر هذا المحتوى عند عدم وجود أبناء لشجرة DOM الظاهرة في الفتحة. تُدعى عملية تصيير العناصر المدرجة في فتحات ضمن فتحاتها بالتركيب composition، وتسمى النتيجة شجرة DOM المسطّحة. لا تنقل عملية التركيب العقد في الواقع، فمن وجهة نظر JavaScript ستبقى شجرة DOM نفسها، ويمكن الوصول إلى الفتحات من خلال توابع JavaScript التالية: ()slot.assignedNodes/Elements: يعيد العقد أو العناصر داخل فتحة. node.assignedSlot: وهي الخاصية المعاكسة، وتعيد الفتحة من العقدة التي تشغلها. إذا أردنا معرفة ما نُظهره، فيمكننا تعقب محتوى الفتحة باستخدام: الحدث slotchange: ويقع في المرة الأولى التي تُملأ بها الفتحة، أو أثناء عمليات الإضافة أو الإزالة أو الاستبدال لعنصر مُدرج في الفتحة وليس لأبنائه، وستكون الفتحة event.target. MutationObserver للغوص عميقًا في محتوى الفتحة ومعرفة التغيرات التي حدثت ضمنها. بعد أن تعرفنا على كيفية إظهار عناصر من شجرة DOM الظاهرة، سنتعرف على كيفية تنسيقها بالشكل الصحيح، وتبقى القاعدة الأساسية هي أن عناصر شجرة DOM الخفية تُنسّق من الداخل، والظاهرة من الخارج -مع بعض الاستثناءات-، وهذا ما سنتعرف عليه في المقال القادم. يمكن أن تحوي شجرة DOM الخفية تنسيقات مثل <style> أو <"link rel="stylesheet>، وتؤثر التنسيقات المحلية في: الشجرة الخفية. المضيف الخفي عندما يستعمل عائلة المحددات host:. العناصر المركبة القادمة من شجرة DOM الظاهرة، حيث يسمح المحدد (slotted(selector:: باختيار العناصر المركبة ذاتها وليس أبنائها. بينما تؤثر تنسيقات المستند الرئيسي في: المضيف الخفي، إذا كان حيًّا في المستند الخارجي. العناصر المركبة ومحتوياتها، إذا كانت أيضًا في المستند الخارجي. سيُطبق تنسيق المستند الخارجي عندما يحدث تضارب في تنسيق العنصر، إلا عند استخدام الخاصية مع عنوان important!، حيث يُطبق التنسيق المحلي. تخترق خصائص CSS المخصصة شجرة DOM الخفية، وتستخدم مثل خطافات hooks لتنسيق المكوّن: يستخدم المكوِّن خصائص CSS المخصصة لتنسيق العناصر المفتاحية، مثل (<var(--component-name-title, <default value. ينشر مؤلف المكون هذه الخصائص للمطورين، وهي بأهمية توابع المكوّنات العامة. عندما لا يرغب المطور في قضاء وقت طويل على التنسيق، فسيسند الخاصية --component-name-title إلى المضيف الخفي أو الخاصية التي أشرنا إليها في الأعلى. الربح طبعًا! تتجاوز الأحداث حدود شجرة DOM الخفية إذا كانت قيمة الراية composedهي true، والتي تكون كذلك لمعظم الأحداث المضمنة Built-in، كما هو محدد في التوصيفات: أحداث واجهة المستخدم. أحداث اللمس. أحداث التأشير. من الأحداث المضمنة التي يكون فيها composed: false: mouseenter وmouseleave: لا تنساب للخارج إطلاقًا. load وunload وabort وerror. select. slotchange. يمكن التقاط هذه الأحداث من قِبل العناصر داخل شجرة DOM نفسها. إذا أردنا إيفاد أحداث مخصصة CustomEvent فلا بد من ضبط الخاصية composed على القيمة "true" بشكل صريح. المراجع مراجع DOM. مراجع التوافقية Compatibility ذكرت الشجرة الخفية في الكثير من المواصفات مثل DOM Parsing. ترجمة -وبتصرف- للفصول Shadow DOM and events و Shadow DOM slots, Composition وShadow DOM وShadow DOM Style من سلسلة The Modern JavaScript Tutorial. اقرأ أيضًا إنشاء الحركات عبر جافاسكربت التنقل في شجرة DOM عبر جافاسكربت تعديل الخاصيات والأصناف والأنماط في شجرة DOM إجراء تعديلات على DOM طريقة الوصول للعناصر في DOM
- 1 تعليق
-
- dom
- shadow dom
-
(و 1 أكثر)
موسوم في:
-
من لا يحب البشرة النضرة؟ صحيح أن قلةً من الناس فقط من يتمتع ببشرة طبيعية متألقة وصافية، لكن ما يريده الجميع هو بشرة خالية من العيوب، فالكثير منا ليس محظوظًا كفايةً للحصول على بشرة كهذه. يحاول البعض استخدام مساحيق التجميل إلا أن آخرين يفضلون استخدام الفوتوشوب لتنقية صورهم والحصول على بشرة خالية من العيوب. يُعَد برنامج الفوتوشوب من أدوبي أو Adobe Photoshop، أداةً قويةً لمساعدتك في تصفية بشرتك وإضافة لمسات ختامية لتبدو طبيعية المظهر، إلى درجة أنك قد لا تدرك أنّ الصور قد عُولجت باستخدام الفوتوشوب. تركز هذه المقالة على مجموعة من الحيل والنصائح لتنعيم البشرة وتنقيتها من خلال برنامج أدوبي فوتوشوب. ولنلق نظرةً إذًا على طريقة إنجاز الأمر. 1. أنشئ نسخة عن الصورة الأصلية سترى الصورة عندما تدرجها في البرنامج على طبقة الخلفية الموجودة ضمن لوحة الطبقات Layers panel. أزل بدايةً العيوب التي تراها قبل أن تنتقل إلى مرحلة تنعيم البشرة. انسخ طبقة الخلفية، وذلك بالضغط على المفتاحين ALT+WIN في لوحة المفاتيح، ثم النقر على طبقة الخلفية وسحبها إلى رمز الطبقة الجديدة New Layer الموجود في الناحية السفلية من لوحة الطبقات. سمِّ النسخة الجديدة Spot Healing ضمن مربع النص الذي يُدعى "مضاعفة الطبقة Duplicate Layer". ستظهر نسخة جديدة من الصورة باسم Spot Healing أعلى الصورة الأصلية، وسننجز عملية التنظيف الضرورية للبشرة ضمن طبقة منفصلة. 2. استخدم فرشاة معالجة البقع انتقل إلى شريط الأدوات وانقر على فرشاة معالجة البقع Spot Healing brush لنبدأ العمل. تُستخدم هذه الأداة لإزالة اللطخات والعيوب الموجودة على البشرة، وتشبه في عملها فرشاة المعالجة healing brush، حيث تلوّن المنطقة المختارة من الوجه بعينة منتقاة من البكسلات Pixels، بحيث تتلاءم مع البشرة المحيطة، وتمنح الأداة إمكانية تفتيح البشرة وجعلها شفافة، إلى جانب إمكانية إضافة الظلال التي تتمتع بها بكسلات العينة إلى بكسلات البقعة التي تحتاج إلى معالجة، لكن هناك فرق بين فرشاة المعالجة وفرشاة معالجة البقع، إذ لا تحتاج الأخيرة إلى نقطة لأخذ عينة من البكسلات، فهي تنتقي البكسلات تلقائيًا من المنطقة النقية على بشرة الوجه. 3. استخدم فرشاة معالجة البقع مع خيار الانتباه للمحتوى Content-Aware تتوفر ثلاثة خيارات مع فرشاة معالجة البقع: التطابق التقريبي Proximity match: تأخذ الأداة وفق هذا الخيار البكسلات حول محيط المنطقة المحددة لانتقاء البكسلات التي ستستخدم للترميم. إنشاء نسيج Create texture: حيث تأخذ الأداة بكسلات من المنطقة المحددة لتنشئ نسيجًا ترميميًا، فإن لم تنجح المحاولة، جرِّب أن تختار نفس المنطقة مجددًا. الانتباه إلى المحتوى Content-Aware: يوازن الخيار محتوى المنطقة المجاورة ليملأ المنطقة المحددة بما يطابق للمحتوى المدروس، ويبقي على التفاصيل الرئيسية مثل الظلال وحواف الكائنات. عليك هنا اختيار Content-Aware من صندوق الخيارات، وهو الخيار الافتراضي، لكن عليك التأكد من اختياره. 4. أزل اللطخات عن البشرة بالنقر عليها بعد ضبط جميع معاملات parameters فرشاة معالجة البقع، ابدأ بالنقر على اللطخات غير المرغوبة لإزالتها عن البشرة. يستبدل فوتوشوب المنطق المعيبة من البشرة بنسيج مناسب مستفيدًا من نسيج البشرة في المنطقة المحيطة. لتسهيل الأمر عليك، تأكد من زيادة حجم الفرشاة قليلًا عما هو عليه عادة، لكي تزداد تغطية الفرشاة للبقعة، وبالتالي ستزداد فعاليتها. استخدم مفتاح القوس المربع اليميني "[" لتكبير الفرشاة، والقوس اليساري"]" لتصغير حجمها. إن لاحظت بقاء البقعة جزئيًا، تراجع عما فعلته (تراجع عن النقر)، وذلك بالضغط على المفتاحين Ctrl+Z في لوحة المفاتيح، ثم أعد ضبط حجم الفرشاة لاستهداف ما تبقى من البقعة. الصورة المعروضة هي صورة لامرأة تعاني عيوبًا محددةً في بشرتها. لاحظ البثور الكثيرة في بشرة الجبين، هنا يمكنك وضع فرشاة معالجة البقع فوق الاندفاع الجلدي، ثم تكبيرها وإزالة العيوب بالنقر عليها. عند النقر، سيميز فوتوشوب المشكلة وسيجد النسيج الأفضل لترميم الجلد في هذه البقعة اعتمادًا على المناطق المجاورة لها، حيث يخلط بكسلات البقعة ببكسلات النسيج الذي شكّله وستختفي البقعة تمامًا. سنتابع العمل بنفس الطريقة لإزالة بقية العيوب (الاندفاعات الجلدية) على الجبين محافظين على حجم فرشاة معالجة البقع لاستهداف العيوب بدقة. ستختفي كل البقع والعيوب بالنقر هنا وهناك، وستكون النتيجة بشرةً أكثر نعومةً ولمعانًا دون أية عيوب. ألقي نظرةً على النتيجة في هذه الصورة: انتبه عندما تعمل على تنقية البشرة ألا تقترب من الشامات أو أية ندبات دائمة. لاتحاول إزالة الشامات أو الندبات، فهي تمثل المعالم الأساسية لوجه الإنسان وبالطبع من غير المنطقي أن تجعل شخصًا يبدو كشخص آخر عندما تعالج مشاكل البشرة في صورته. تابع العمل وفق التوجيهات السابقة على بقية مناطق الوجه التي تعاني من بقع ومشاكل في البشرة حتى تزيلها كلها، وألقي نظرةً على صورة الوجه قبل وبعد إزالة البقع: تُظهر الصورة اليمينة بشرة الوجه الأصلية مع جميع عيوبها، بينما تظهر اليسارية البشرة بعد إزالة العيوب. لا تستغرق غالبًا تنقية البشرة في الصور الكثير من الوقت. 5. أنشئ نسخة جديدة عن الطبقة التي عنونتها بـ Spot Healing سنحاول الآن تنعيم البشرة بعد إزالة جميع البقع من الصورة، لذلك علينا إنشاء طبقة جديدة مختلفة. سننسخ الطبقة Spot Healing داخل لوحة الطبقات بالضغط على المفتاح ALT في لوحة المفاتيح، ثم النقر على الطبقة Spot Healing وسحبها إلى أيقونة "طبقة جديدة New Layer" أسفل لوحة الطبقات. امنح الطبقة الاسم "Smoothen skin"، ثم انقر على الزر "OK". لدينا الآن الصورة الأصلية في الخلفية تعلوها طبقة إزالة العيوب spot healing، ومن ثم تأتي طبقة Smoothen skin في الأعلى. 6. استخدم مرشح التمرير العالي على الصورة يمكنك استخدام مرشح التمرير العالي High Pass filter لتنعيم مناطق البشرة التي أزلنا عيوبها، وذلك لكي تبدو صحيةً وأكثر إشراقًا. انقر على قائمة "مرشح Filter"، ثم انتقل إلى الخيار "High Pass": يُستخدم مرشح التمرير العالي عادةً في فوتوشوب لتوضيح الصور sharpening إلى حد معين، لكننا سنستخدم المرشح هنا لتنعيم الصورة وليس لتوضيحها أكثر، وستبقى خطوات العمل نفسها في الحالتين. يميّز المرشح الحواف بين المناطق التي تعاني تبدلًا فجائيًا في الإضاءة، أو في مستوى ألوان البكسلات المتجاورة. وفي حالة صور البورتريه (صور الوجه)، سيكون الوجه قريبًا، كما ستكون الحواف عادةً حول الشعر والعينين والفم. لا يحتوي نسيج البشرة عمومًا على الكثير من التفاصيل، ويعطي تدرجًا لونيًا ناعمًا بين مناطقه المختلفة التي لا تُعَد في هذه الحالة حوافًا. لن يميّز المرشح هذه المناطق بل سيملؤها فقط ببكسلات تحمل اللون الرمادي الحيادي neutral grey. لو كنا قد استخدمنا المرشح لتوضيح الصورة، لكان مرشح التمرير العالي قادرًا على توضيح الحواف دون التأثير على نسيج البشرة، لذلك سنستخدم المرشح بطريقة عكسية لتنعيم الصورة بتطبيقه على كامل المناطق اللونية ما عدا الحواف. لاحظ كيف ننجز ذلك: قيمة نصف القطر يُعدّ مرشح التمرير العالي الأداة هو الأفضل لتمييز حواف الكائن وتصحيحها. ستجد تحت مربع الحوار "تمرير عال High Pass" الخيار "نصف القطر Radius"، الذي يتحكم بعرض تمييز الحواف. فعندما يحدد فوتوشوب الحافة، ستعطينا قيمة نصف القطر عدد البكسلات الموزعة على طرفي منطقة الحافة. وإن كانت قيمة نصف القطر صغيرة، فسيميز المرشح تفاصيلًا أدق حول الحافة. وطالما أننا لن نحتاج إلى تنعيم هذه الحواف التي تمثل الأجزاء الأساسية في الصورة بل المناطق المجاورة لها، فلا بدّ من زيادة قيمة نصف القطر. وبالنسبة لصور الوجه، ستجد أن قيمة 24 بيكسل هي الأفضل. إن كان الجزء المعالج من الصورة بعيدًا للخلف أو كانت دقة الصورة منخفضة، فينبغي تقليل نصف القطر إلى حوالي 15 بيكسل. من المفترض أن تقبل قيمة نصف القطر القسمة على 3 مثل 24 أو 15 وسنرى فائدة ذلك لاحقًا. انقر الآن على الزر "Ok" لإغلاق مربع حوار "تمرير عال High Pass"، وسترى أنّ الصورة قد تحولت إلى اللون الرمادي. تبدو المناطق الواسعة التي لا تمتلك حوافًا باللون الرمادي، وهي مناطق قليلة التفاصيل، لكن المناطق التي تمتلك هالات لونية عالية التباين هي من تساهم في تمييز الحواف. 7. استفد من مرشح التغشية منتظم التوزع لا بدّ الآن من تغشية أثر فلتر الترشيح العالي، فعلى غير المتوقع، ستعطي تغشية المناطق التي نًعّمت باستخدام فلتر الترشيح العالي مظهرًا رائعًا للبشرة، لذلك افتح القائمة "ترشيح Filter"، وانتقل إلى "تغشية Blur"، ثم اخترGaussian Blur. اضبط قيمة نصف القطر Radius value في مربع الحوار الذي سيظهر لتكون ثلث القيمة السابقة في مرشح التمرير العالي. وطالما أننا ضبطنا تلك القيمة على 24 بيكسل، فستكون الأخيرة 8 بيكسل. انقر بعد ذلك على "Ok". سيبدو أثر مرشح التمرير العالي على النحو التالي بعد التغشية: 8. استخدم نمط المزج الخطي على طبقة التنعيم غيِّر قيمة الخيار "نمط المزج Blend Mode" للطبقة Smooth Skin في لوحة الطبقات من عادي normal إلى خطي Linear. سيدمج هذا الأمر نتيجة الترشيح العالي مع الصورة مكوّنًا بصورة عالية التباين مع تأثيرات أعلى وضوحًا. 9. لابد الآن من تغيير ألوان الصورة من قائمة "صورة Image"، انتقل إلى ضبط Adjustments، ثم اختر "تغيير Invert". ستبدو الصورة في حالة فوضى عارمة بعد تطبيق هذا الأمر وأكثر غشاوةً عما كانت عليه سابقًا. 10. انتقل إلى خيارات المزج Blending Options لتخفيف أثر الهالة، انتقل إلى خيار تنسيق الطبقات Layer styles الموجود أسفل لوحة الطبقات، ثم اختر "خيارات المزج Blending Options". 11. عدل إعدادات خيار المزج Blend If افتح مربع الحوار "تنسيق الطبقات Layer styles"، وابحث عن خيار المزج Blend If في النصف السفلي من الصندوق. ستجد مجموعتين من الخيارات هي: "هذه الطبقة This Layer"، و"الطبقة الأدنى Underlying Layer". استعمل الخيار العلوي. انظر إلى خيار المزج الواقع تحت نهايات كل أشرطة التدرج gradient bars. سنستخدم هذا الخيار في التحكم في مقدار التمازج بين الطبقة Smooth Skin والصورة التي تحتها. تجري العملية اعتمادًا على مستويات سطوع الطبقة brightness. إذ يمزج الخيار اليساري المناطق الأكثر قتامةً، بينما يمزج الخيار اليميني المناطق الفاتحة. العمل على الهالات الفاتحة ابدأ العمل على الهالات الفاتحة أكثر. اضغط الزر Alt في لوحة المفاتيح، ثم انقر على خيار المزلقة اليمينية واسحبها نحو اليسار. لاحظ أن المزلقة مقسومة إلى نصفين، وسيتحرك فقط الجزء اليساري ولن يؤثر اطلاقًا على الجزء اليميني من المزلقة. ستلاحظ اختفاء الهالات عن الصورة. اسحب المزلقة إلى أقصى يسار النصف الذي تتحرك عليه لتخفيف الهالات قدر الإمكان. تخفيف الهالات الأكثر قتامة اضغط الزر Alt في لوحة المفاتيح، ثم انقر المزلقة اليسارية واسحبها نحو اليمين قدر الإمكان لإزالة الهالات الأكثر قتامةً. انقر على الزر "OK" عندما تنتهي ولاحظ نتيجة عملك. 12. إضافة قناع على الطبقة للحد من التأثيرات التي تظهر في الصورة السابقة، لا بدّ من إضافة طبقة قناع layer mask جديدة. افتح لوحة الطبقات واضغط باستمرار على الزر Alt، ثم انقر على الأيقونة "إضافة طبقة قناع Add Layer Mask". ستظهر عند ذلك صورة مصغّرة عن طبقة القناع مملوءة بالأسود فوق الطبقة Smooth Skin. 13. اختر أداة الفرشاة اختر الفرشاة من شريط الأدوات. 14. اختر اللون الأبيض للفرشاة تأكد من أن لون الفرشاة أبيض. يمكنك البحث في عينات الألوان الخاصة بك للتحقق من اللون الأمامي ولون الخلفية. 15. لون فوق البشرة تحقق من خيارات الفرشاة المتاحة ضمن شريط الخيارات Options bar قبل أن تبدأ التلوين، ثم تحقق من أنّ النمط قد ضبط على الخيار عادي Normal، وأنّ العتامة Opacity قد ضبطت على القيمة 100%؛ أما الخيار انسياب Flow، فعلى 100%. بعد ذلك استخدم الفرشاة للتلوين على البشرة، وذلك لكي يظهر أثر التنعيم. انتق الفرشاة ناعمة الحواف soft-edged لتبدو تأثيرات التدرج أكثر نعومةً. انظر إلى نتيجة عملك بالكامل عند انتهاء العمل. خلاصة تقود هذه المقالة المبتدئين لتنعيم وتنقية البشرة باستخدام برنامج أدوبي فوتوشوب، ويمكنك بالطبع الاستفادة من جميع الملاحظات والحيل التي نفذناها في المقال لتنقية صورة بنجاح. ترجمة -وبتصرف- للمقالة 15 Tips to Get Skin Smoothing and Retouching Right in Photoshop لكاتبها Harsh Raval. اقرأ أيضًا كيفية تصميم تأثير الجلد على النصوص في الفوتوشوب كيفية قص الشعر من الخلفيات المعقدة في الفوتوشوب كيفية تطبيق تأثير الإضاءات المختلفة على الصور الشخصية في الفوتوشوب
-
ستتعلم في هذا المقل كيفية استثمار نظام التشغيل راسبيان Raspbian، حيث يمتلك راسبيري باي القدرة على تشغيل مجالٍ واسعٍ من البرمجيات متضمنةً عددًا من أنظمة التشغيل المختلفة، التي يعتمد عليه الجهاز أساسًا ليعمل. يُعدّ راسبيان نظام التشغيل الرسمي لمؤسسة راسبيري باي، وأكثر أنظمة التشغيل المستخدمة مع باي شعبية، حيث بُني راسبيان اعتمادًا على نظام التشغيل ديبيان لينكس Debian Linux، وقد صُمم خصيصًا ليناسب جهاز راسبيري باي، ويأتي مع حزمةٍ من البرمجيات والإضافات المثبتة مسبقًا والجاهزة للعمل. إذا استخدمت سابقًا أنظمة تشغيل مثل مايكروسوفت ويندوز، أو آبل ماك، أو إس؛ فالأمر مشابهٌ على راسبيان، حيث يعتمد على مبادئ النافذة والأيقونة والقائمة ومؤشر الفأرة نفسها، وستألف بيئة العمل بسرعة. سيحضِّرك هذا المقال للانطلاق في العمل ويعرّفك على بعض البرمجيات المجمّعة خصيصًا. معالج الإعداد الترحيبي Welcome Wizard سترى هذا المعالج عند ظهور سطح مكتب راسبيان للمرة الأولى، وهو أداةٌ مفيدةٌ تساعدك على تغيير بعض إعدادات النظام من أجل تهيئته بما يناسب طبيعة ومكان استخدامك الراسبيري باي. شكل 1-3 معالج الإعداد الترحيبي انقر على زر التالي Next، ثم اختر بلدك ولغتك ونطاقك الزمني Time zone بالنقر على القائمة المنسدلة الموافقة لكل خيار، كما هو موضحٌ في الشكل التالي. إذا كنت تستخدم لوحة مفاتيح عربية، فانقر على صندوق التحقق المجاور حتى تتأكد من أن راسبيان سيستخدم مخطط لوحة المفاتيح العربية الصحيح؛ وإذا أردت استخدام اللغة الإنكليزية أيًا كانت لغتك الأم، فانقر على صندوق التحقق Use English language لتفعيله، ثم انقر زر التالي Next عندما تنتهي. شكل 2-3 اختيار اللغة ستطلب منك الشاشة الثانية للمعالج تغيير كلمة السر الخاصة بالمستخدم الافتراضي pi، حيث تكون الكلمة الافتراضية raspberry، ومن المهم جدًا أن تختار كلمة سر جديدة لأسبابٍ أمنية. أدخل الكلمة في كلا الصندوقين (كلمة السر وتأكيدها) كما هو موضحٌ في الشكل التالي، وبالنقر على مربع التحقق بجوار إخفاء المحارف Hide characters ستكون قادرًا على رؤية الكلمة التي تكتبها، كما يجب أن تكون كلمة المرور متطابقةً في الصندوقين. شكل 3-3 إعداد كلمة سر جديدة تسمح لك الشاشة التالية المُوضحة بالشكل التالي باختيار الشبكة اللاسلكية التي ستتصل بها من خلال قائمةٍ من الشبكات المتوفرة، حيث يمكنك التَنقُّل عبر عناصر القائمة باستخدام الفأرة أو لوحة المفاتيح حتى تجد اسم شبكتك، بعد ذلك انقر عليها وانقر على زر التالي Next. سيُطلب منك كتابة كلمة مرور الشبكة اللاسلكية إذا كانت محميّة Secure، حيث من الأفضل أن تكون محمية، وتُعرف هذه الكلمة أيضًا بـ pre-shared key، وستجد هذا المفتاح عادةً على علبة الموجّه أو أسفل الموجِّه نفسه. انقر على زر التالي Next للاتصال بالشبكة، وإذا كنت لا تريد الاتصال بشبكة لاسلكية، فانقر زر تخطي Skip. شكل 4-3 اختيار الشبكة اللاسلكية تتحقق الشاشة التالية المُوضحة بالشكل التالي من وجود تحديثات updates لراسبيان وبقية البرمجيات على باي، وتتيح لك تثبيتها، حيث يُحدّث راسبيان بانتظامٍ لإصلاح الثغرات وإضافة ميزاتٍ جديدة وتحسين الأداء. انقر زر التالي Next لتثبيت التحديثات المتوفرة أو تخطي Skip لتجاوز العملية. قد يستغرق تنزيل التحديثات بعض الوقت، وستظهر نافذةٌ تخبرك أن النظام مُحدّث عند انتهاء عملية التثبيت، انقر بعدها على الزر OK. شكل 5-3 التحقق من وجود تحديثات ستسألك الشاشة الأخيرة من المعالج والموضحة في الشكل التالي عما إذا كنت تريد إعادة إقلاع النظام rebooting، حيث لن تُفعّل بعض التغييرات دون إعادة تشغيل راسبيري باي. وإذا طُلب منك ذلك، فانقر على زر إعادة الإقلاع Reboot، وسيعيد باي تشغيل نفسه، لكن معالج الإعداد الترحيبي سيختفي هذه المرة ويصبح باي جاهزًا للعمل. شكل 6-3 إعادة إقلاع راسبيان التعرف على سطح المكتب تُعرف نسخة راسبيان المُثبَّتة على معظم أجهزة راسبيري باي على أنها راسبيان مع سطح مكتب راسبيري باي، وذلك للدلالة على واجهة المستخدم الرسومية الرئيسية. تحتل مساحة سطح المكتب صورةً تُدعى خلفية سطح المكتب Wallpaper؛ حيث تدل العلامة A في الشكل التالي عليها، وستظهر فوقها البرامج التي تشغلها. ستشاهد في أعلى سطح المكتب شريط المهام Taskbar عند العلامة B، والذي يمكِّنك من تحميل أي برنامجٍ من البرامج التي وضعتها ضمنه، ويُشار إلى البرامج التي جرى تشغيلها بالمهمَّات tasks من مكان العلامة C في هذا الشريط. سطح مكتب راسبيان سنوضّح الآن أقسام سطح المكتب التي تشير إليها العلامات في الشكل السابق، وهي: A: خلفية سطح المكتب. B: شريط المهام. C: مهمّة. D: لوحة النظام system tray. E: إزالة الوسائط. F: أيقونة بلوتوث. G: أيقونة شبكة الاتصال. H: أيقونة حجم الصوت. I: الساعة. J: شريط تشغيل البرامج Launcher. K: أيقونة القائمة أو أيقونة راسبيري. L: أيقونة سلة المحذوفات. M: أيقونة السوّاقات Drivers القابلة للإزالة. N: شريط عنوان النافذة. O: تصغير النافذة. P: تكبير النافذة. Q: إغلاق النافذة. تقع لوحة النظام D على يسار شريط القائمة menu bar ويظهر فيها رمزٌ لإزالة أي وسيط تخزينٍ قابلٍ للإزالة، مثل سواقة تخزين USB إذا وصلتها بالجهاز. سيسمح لك النقر على هذا الرمز بإزالة وإخراج الوسيط بأمان، وستجد في أقصى يسار الشريط الساعة I، وستشاهد بالنقر عليها تقويمًا رقميًا موضحًا في الشكل التالي. شكل 8-3 تقويم رقمي تجد إلى جانب الساعة أيقونةً على شكل مكبر صوت H، يمكنك النقر عليها بزر الفأرة الأيسر لضبط حجم الصوت، وبالزر الأيمن لاختيار المخرج الصوتي الذي يستخدمه باي. كما ستجد أيقونة شبكة الاتصال G إلى جانب مكبر الصوت، حيث ستظهر قوة الإشارة على شكل أشرطةٍ متتابعةٍ إذا كنت متصلًا بشبكةٍ لاسلكية، وسترى فقط سهمين عموديين متعاكسين إذا كنت متصلًا بشبكةٍ سلكيةٍ محلية. انقر على أيقونة شبكة الاتصال لعرض قائمة الشبكات اللاسلكية المتاحة في الجوار، وعلى أيقونة البلوتوث F للاتصال بأجهزة البلوتوث المجاورة لراسبيري، كما هو موضح في الشكل التالي. شكل 9-3 قائمة الشبكات اللاسلكية المتاحة يتوضع شريط تشغيل البرامج J على يمين شريط القائمة، ويضم أيقونات البرامج التي ثبتَّها عند تثبيت راسبيان، وقد لا ترى جميع أيقونات البرامج على شريط التشغيل، حيث يتوضع بعضها ضمن قائمة راسبيري الموجودة أقصى يسار شريط القائمة، ويمكنك فتح القائمة بمجرد النقر على أيقونتها. شكل 10-3 قائمة راسبيان تُقسم البرامج الموجودة في قائمة راسبيري إلى فئاتٍ تدل أسماؤها على طبيعة البرامج ضمنها، حيث تضم فئة البرمجة Programming مثلًا البرامج المصممة لمساعدتك في كتابة برامجك الخاصة كما سترى في مقال لاحق حول البرمجة باستخدام سكراتش Scratch وما يليه؛ بينما ستسلّيك الألعاب Games لبعض الوقت. لن نستعرض بالطبع جميع البرامج في هذا الدليل، لذلك جرّب أن تتعلم بنفسك ما تريده منها. متصفح الويب كروميوم Chromium سنتمرن الآن على استخدام باي بتشغيل متصفح كروميوم على النحو التالي: انقر على أيقونة راسبيري في الزاوية اليسارية العليا لسطح المكتب لتظهر لك قائمة البرامج، ثم انتقل بعدها إلى فئة الإنترنت وانقر على أيقونة متصفح كروميوم، كما هو مبين في الشكل التالي. شكل 11-3 متصفح كروميوم سيبدو لك كروميوم مألوفًا إذا استخدمت سابقًا متصفح الويب جوجل كروم Google Chrome على حاسوبٍ آخر، حيث يتيح لك كروميوم مثل أي متصفح ويب زيارة المواقع المختلفة ومشاهدة مقاطع الفيديو والتمتع بالألعاب والتواصل مع أشخاص آخرين حول العالم من خلال المنتديات المختلفة أو مواقع المحادثة المباشرة. ابدأ باستخدام كروميوم من خلال تكبير نافذته إلى الحد الأقصى لتملأ نافذة سطح المكتب؛ حيث ستجد ثلاث أيقوناتٍ متجاورةٍ في أقصى يسار شريط عنوان نافذة كروميوم N. انقر على الأيقونة الموجودة في الوسط والتي تحمل رمز سهمٍ متّجِهٍ للأعلى P، وهي أيقونة تكبير النافذة لتشغل مساحة سطح المكتب؛ أما الأيقونة التي تأتي على يسارها فهي تصغير النافذة O، والتي تُخفي النافذة حتى تنقر عليها في شريط المهام أعلى سطح المكتب. وتحمل بالطبع أيقونة الإغلاق Q رمز الضرب إلى يمين أيقونة التكبير. إذا أردت تصفح الإنترنت فما عليك سوى النقر على شريط العناوين Address bar أعلى المتصفح؛ وهو الشريط الأبيض الطويل الذي ينتهي برمز عدسة البحث، ومن ثم كتابة العنوان الذي تريد التوجه إليه، مثل www.raspberrypi.org، ثم اضغط على زر Enter وسيحمّل كروميوم الموقع المطلوب كما هو موضح في الشكل التالي. تستطيع البحث عما تريده أيضًا بدلًا من كتابة عنوان محدد، كأن تكتب راسبيري باي، أو راسبيان، أو أية عباراتٍ أخرى، وسيبحث كروميوم عن محتوىً يضم هذه العبارات. شكل 12-3 تحميل موقع ويب راسبيري باي عبر كروميوم قد تظهر لك عدة نوافذٍ فرعية tabs ضمن نافذة كروميوم عند تشغيله للمرة الأولى، حيث يمكنك التنقل بينها بمجرد النقر على النافذة، كما يمكنك إغلاق نافذة فرعية محددة بالنقر على رمز الضرب الموجود على الطرف الأيمن للنافذة. لفتح نافذة فرعية جديدة، انقر على زر النافذة الفرعية الموجود إلى يمين آخر نافذة فرعية مفتوحة، أو اضغط معًا زري لوحة المفاتيح CTRL وT. تُعَد النوافذ الفرعية وسيلةً جيدةً لتصفح عدة مواقع ويب معًا دون ازدحام نوافذ كروميوم على الشاشة. انقر على زر الإغلاق عند الإنتهاء من العمل على كروميوم. مدير الملفات File Manager تُخزّن جميع الملفات في المجلد الرئيسي Home، سواءٌ كانت قصيدةً كتبتها، أو برنامجًا أو مقطع فيديو أعددته، أو صورةً نزّلتها من موقع ويب. وللوصول إلى هذا المجلد، انقر على زر القائمة، ثم انتقل إلى الملحقات Accessories، وبعدها انقر على مدير الملفات File Manager. شكل 13-3 برنامج مدير الملفات يتيح لك مدير الملفات إمكانية تصفّح الملفات والمجلدات الموجودة على بطاقة الذاكرة الأساسية microSD، أو أية وسائط تخزينٍ قابلةٍ للإزالة، مثل سواقات التخزين المحمولة بواجهة USB، حيث ينقلك البرنامج عند تشغيله إلى المجلد الرئيسي مباشرةً، ويضم هذا المجلد بدوره مجلداتٍ فرعيةٍ subdirectories أخرى مرتبةٍ في فئات كما هو حال برامج قائمة راسبيري، وأهمها: سطح المكتب Desktop الذي يضم ما تراه من ملفات على سطح المكتب، فإذا خزّنت ملفًا ضمن هذا المجلد سيظهر على سطح المكتب، وبالتالي يسهّل عليك إيجاده وفتحه. المستندات Documents: وهو المجلد الرئيسي لمعظم الملفات التي تُنشئها. التنزيلات Downloads: تُخزَّن ضمنه الملفات التي نزّلتها من الإنترنت عبر المتصفح كروميوم تلقائيًا. MagPi: يحتوي على نسخةٍ إلكترونية من مجلة MagPi؛ وهي المجلة الرسمية لمؤسسة راسبيري باي. الموسيقى Music: يضم ملفات موسيقى كنت قد أنشأتها أو خزنتها على راسبيري باي. الصور Pictures: وهو مجلدٌ فرعيٌ مخصصٌ لتخزين الصور. عام Public: طالما أن معظم الملفات والمجلدات خاصة، فما تضعه في هذا المجلد يصبح عامًا؛ أي يمكن لأي مستخدمٍ الوصول إليه حتى لو كان له معلومات استيثاق خاصة، مثل اسم المستخدم وكلمة المرور. الفيديوهات Videos: وهو مجلدٌ فرعيٌ مخصصٌ لتخزين مقاطع الفيديو، ويمثّل وجهة البحث الأولى لبرامج تشغيل الفيديو. تُقسم نافذة مدير الملفات إلى قسمين: القسم الأيسر الذي يعرض المجلدات الموجودة على راسبيري باي، والقسم الأيمن الذي يعرض الملفات والمجلدات الفرعية الموجودة ضمن المجلد الذي اخترته من مجلدات القسم الأيسر. إذا وصلت أحد وسائط التخزين القابلة للإزالة بأحد منافذ USB، فستظهر لك نافذةً تسألك عما إذا كنت تريد تصفح محتويات وسيط التخزين باستخدام مدير الملفات File Manager، كما هو مبين في الشكل التالي، انقر زر OK لتتمكن من ذلك. شكل 14-3 توصيل وسيط تخزين قابل للإزالة بإمكانك نسخ الملفات من الأجهزة القابلة للإزالة إلى بطاقة الذاكرة microSD أو العكس، وذلك بفتح مجلد بطاقة الذاكرة الخاصة براسبيري باي ومجلد الجهاز القابل للإزالة في نافذتين مختلفتين لمدير الملفات، ثم الضغط المستمر على الملف الذي تريد نسخه بزر الفأرة اليساري وسحبه إلى النافذة الأخرى، ثم إفلاته ضمنها، وتُعرف هذه العملية باسم السحب والإفلات Drag and Drop. هناك طريقةٌ أخرى لنسخ الملفات من خلال النقر مرةً واحدةً على الملف، ثم النقر على قائمة التحرير Edit واختيار نسخ Copy، والانتقال بعد ذلك إلى النافذة الأخرى والنقر على قائمة التحرير Edit، ثم اختيار لصق Paste. يمكنك أيضًا اختيار أمر النقل Move من قائمة التحرير Edit بدلًا من النسخ Copy، لكن الفرق بينهما أن عملية النقل ستحذف الملف من المجلد الأصلي بعد نقله إلى المجلد الجديد. يتيح راسبيان إمكانية تنفيذ العمليتين السابقتين عبر اختصارات لوحة المفاتيح، حيث يمكنك تنفيذ عملية النسخ بالضغط على زري CTRL وC معًا ؛ وتنفيذ النقل أو القص بالضغط على زري CTRL وX معًا؛ أما النسخ، فمن خلال الضغط على زري CTRL وV معًا. الشكل 15-3 سحب وإفلات ملف أغلق نافذة مدير الملفات حين تنتهي من تجربته، وأغلق أية نوافذٍ أخرى قد فتحتها بالنقر على زر الإغلاق في أعلى يمين النافذة، بعد ذلك أخرج وسائط التخزين القابلة للإزالة إذا وصلت أحدها براسبيري باي من خلال النقر على زر الإخراج Eject أعلى يمين الشاشة، ثم سحب الجهاز من منفذ USB. مجموعة البرامج المكتبية ليبر أوفيس LibreOffice وهي وسيلةٌ أخرى لاختبار إمكانيات راسبيري باي. انقر على أيقونة راسبيري، ثم انقل الفأرة إلى أدوات المكتب Office وانقر على محرر النصوص ليبر أوفيس رايتر LibreOffice Writer؛ وهو البرنامج الذي يعالج النصوص ضمن مجموعة ليبر أوفيس المكتبية ذات الشعبية الواسعة والمماثلة لغيرها من المجموعات المكتبية، مثل مايكروسوفت أوفيس Microsoft Office، وجوجل دوكس Google Docs. من الممكن ألا تجد ليبر أوفيس مُثبّتًا افتراضيًا مع بعض نسخ راسبيان، لذلك استخدم أداة برمجيات مفيدة Recommended Software لتثبيته، والتي سنتحدث عنها لاحقًا في هذا المقال. شكل 16-3 محرر نصوص ليبر أوفيس يتيح لك محرر نصوص ليبر أوفيس رايتر إمكانية كتابة المستندات وتنسيقها بطرقٍ مُلفتة، حيث يمكنك تغيير نمط الخط ولونه وقياسه، وإضافة التأثيرات والصور والمخططات البيانية والرسومية والجداول وغيرها من المحتويات، كما يساعدك المحرر على تتبع الأخطاء، حيث يّظلل مباشرةً أخطاء الكتابة باللون الأحمر وأخطاء القواعد بالأخضر أثناء الكتابة. ابدأ تجربتك بكتابة مقطعٍ عما تعلّمته حتى الآن عن راسبيري باي وبرمجياته، واختبر مختلف الأيقونات الموجودة أعلى نافذة المحرر لتكتشف عملها، وحاول أن تزيد حجم الكلمات وأن تغيّر لون النص. إذا لم تكن واثقًا من عمل أية أيقونة، فمرّر مؤشر الفأرة فوقها وسيظهر لك مربع ملاحظات tooltip يخبرك عن عمل الأيقونة. احفظ عملك إذا كنت مقتنعًا به بالنقر على قائمة ملف File، ثم خيار الحفظ Save، واختر اسمًا مناسبًا لملفك، ثم انقر زر حفظ Save. شكل 17-3 حفظ ملف يُعدُّ محرر النصوص ليبر أوفيس رايتر جزءًا من مجموعة ليبر أوفيس المكتبية المتكاملة، وستجد بقية برامج المجموعة في فئة أدوات المكتب Office ضمن قائمة راسبيري إلى جانب ليبر أوفيس رايتر. هذه البرامج هي: ليبر أوفيس بيس LibreOffice Base: قاعدة بياناتٍ لتخزين المعلومات والبحث عنها بسرعة وتحليلها. ليبر أوفيس كالك LibreOffice Calc: جداول Spreadsheet لمعالجة الأرقام وإنشاء المخططات والرسومات. ليبر أوفيس درو LibreOffice Draw: برنامج رسوميات لإنشاء الصور والمخططات. ليبر أوفيس إمبرس LibreOffice Impress: برنامج عروض تقديمية لتصميم شرائح عرض وتقديمها. ليبر أوفيس ماث LibreOffice Math: محرّر صيغٍ رياضية لكتابة معادلات وعبارات رياضية بالشكل المناسب، ثم استخدامها في برامجٍ أخرى. إذا أعجبك العمل على ليبر أوفيس، فيمكنك استخدامه على أجهزة حاسوبٍ أخرى. نزّل البرنامج من موقع ليبر أوفيس libreoffice.org بما يتناسب مع نظام التشغيل لديك سواءٌ كان مايكروسوفت ويندوز، أو توزيعات لينوكس، أو آبل ماك، أو إس وثبته بعد ذلك؛ وإذا أردت التعرف أكثر على ما يقدمه لك ليبر أوفيس، فانظر قسم ليبر أوفيس في أكاديمية حسوب. أداة البرمجيات المفيدة Recommended Software على الرغم من تنوع حزمة البرامج التي تأتي مع راسبيان، فهو قادرٌ على تشغيل الكثير غيرها، حيث ستجد مجموعةً من أفضل البرامج ضمن الأداة Recommended Software، ولا بدّ أن يتصل جهازك بالإنترنت لاستخدامها. انقر على أيقونة راسبيري وانتقل بمؤشر الفأرة إلى تفضيلات Preferences، ومن ثم إلى أداة البرمجيات مفيدة Recommended Software، حيث ستبدأ الأداة بمجرد تشغيلها بالبحث عبر الإنترنت عن البرمجيات المتوفرة، وستعرض بعد ثوانٍ معدودة قائمةً بحزم البرمجيات المتوافقة مع راسبيان كما هو موضحٌ في الشكل التالي، وتكون مرتبةً ضمن فئات تمامًا مثل البرامج في قائمة راسبيري. انقر على أي فئةٍ في القسم اليساري من النافذة لاستعراض البرامج التي تنتمي إليها، أو انقر على كل البرامج All Programs لعرض كل شيء. شكل 18-3 أداة Recommended Software إذا وجدت مربع التحقق بجانب البرنامج مُعلّمًا، فالبرنامج مثبتٌ بالفعل على جهازك؛ وإذا لم يكن كذلك، فيمكنك تعليمه بالنقر عليه حتى يُثبَّت، حيث يمكنك اختيار القدر الذي تشاء من البرمجيات لتثبيتها سويةً، لكن قد لا تتسع بطاقة الذاكرة لها إذا كان حجم البطاقة أقل من المطلوب. وبنفس الطريقة، يمكنك إلغاء تثبيت البرامج من خلال البحث عن البرنامج المثبت مسبقًا، حيث سترى مربع التحقق إلى جواره معلّمًا. انقر عليه لإزالة العلامة، وبالطبع يمكنك عكس العملية إن أخطأت لسبب ما. عندما ترى أنك اخترت ما تريد تثبيته أو إزالته، فانقر على زر OK لتبدأ العملية، كما هو موضحٌ في الشكل التالي، وبعد انتهاء تنزيل البرامج المطلوبة وتثبيتها سيظهر لك صندوق حوار، انقر الزر OK لإغلاق الأداة Recommended Software. شكل 19-3 إلغاء تثبيت برنامج يمكنك استخدام أداة إضافة وإزالة البرامج Add/Remove Software لتثبيت أو إزالة برنامج، وستجدها أيضًا ضمن نفس فئة التفضيلات Preferences في قائمة راسبيان؛ حيث تعرض هذه الأداة مجالًا أوسع من البرامج، لكن معظمها لم يُختبر من قبل مؤسسة راسبيري باي. أداة ضبط راسبيري باي Configuration Tool آخر ما سنتعرف عليه في هذا المقال هي أداة الضبط Raspberry Pi configuration tool التي تشابه معالج الإعداد الترحيبي، حيث تساعدك الأداة في تغيير مجموعةٍ متنوعةٍ من إعدادات راسبيان. انقر على أيقونة راسبيري، وانتقل بمؤشر الفأرة إلى تفضيلات Preferences، ومن ثم إلى أداة ضبط راسبيري باي Raspberry Pi Configuration المُبينة في الشكل الآتي. تنقسم نافذة الأداة إلى أربع نوافذٍ فرعية تتحكم كلٌ منها بمزايا محددة لراسبيان؛ أولى هذه النوافذ هي النظام System، وتظهر عند فتح البرنامج مباشرةً، حيث تمكّنك هذه النافذة من تغيير كلمة سر حسابك على راسبيان وتعيين اسمٍ لجهازك، وهو الاسم الذي يميِّز جهاز راسبيري باي عند الاتصال بشبكاتٍ محلية سلكيةٍ أو لاسلكية، وغيرها من الإعدادات. مع ذلك، لا تحتاج معظم هذه الإعدادات لأي تغيير. تضم النافذة الفرعية الثانية المُسماة واجهات Interfaces فئةً أخرى من الإعدادات التي يظهر معظمها غير مفعّلٍ disabled، حيث لا ينبغي تغيير هذه الإعدادات إلا عند إضافة طرفيةٍ جديدة لراسبيري، مثل تجهيزة الكاميرا وبالطريقة التي يطلبها صانعوا الطرفية فقط، ويُستنثنى من هذه القاعدة الخيارات التالية: الخيار SSH: الذي يفعّل ميزة Secure Shell، حيث تسمح هذه الميزة بالولوج إلى راسبيري باي من حاسوبٍ آخر على الشبكة باستخدام عميل SSH. الخيار VCN: الذي يفعّل ميزة Virtual Network Computer، حيث تسمح هذه الميزة بالتحكم في جهاز راسبيري باي عبر جهاز حاسوبٍ آخر باستخدام عميل VCN. الخيار Remote GPIO: الذي يسمح لك باستخدام أرجل المنصة GPIO لجهاز راسبيري باي من خلال حاسوبٍ آخر على الشبكة، وسنتعرف على ذلك لاحقًا عند تطرقنا للحوسبة الفيزيائية باستخدام بايثون وسكراتش في مقال لاحق. شكل 20-3 أداة ضبط راسبيري باي تساعدك النافذة الفرعية الثالثة "الأداء Performance" على ضبط حجم الذاكرة المخصصة لوحدة معالجة الرسوميات GPU، بالإضافة إلى زيادة أداء بعض نماذج راسبيري باي من خلال العملية المعروفة باسم رفع تردد العمل Overclocking. وكما أشرنا سابقًا، يُفضَّل عدم تغيير شيءٍ من إعدادات هذه الفئة. أما النافذة الأخيرة "إعدادت محلية Localization"؛ فهي مخصصةٌ لإعداداتٍ مثل تغيير الموقع، والذي يتحكم بدوره باللغة التي يستخدمها راسبيان وكيفية عرض الأرقام، بالإضافة إلى تغيير المنطقة الزمنية وتنسيق لوحة المفاتيح وتغيير اسم الدولة لأغراضٍ تتعلق بالشبكات اللاسلكية WiFi. يفضل حاليًا نقر زر Cancel لإغلاق الأداة دون تغيير أي شيء. إيقاف التشغيل بعد أن استُكشف سطح مكتب راسبيان، فقد حان الوقت لتعلّم إيقاف تشغيل راسبيري باي بأمان، حيث يُبقي راسبيري باي مثل غيره من الحواسيب جميع الملفات التي تعمل عليها في الذاكرة المتطايرة من النوع RAM، التي تفقد محتواها عند انقطاع التغذية الكهربائية. وبطبيعة الحال، تنتقل المستندات التي أنشأتها عند حفظها من الذاكرة المتطايرة إلى الذاكرة الدائمة المتمثلة ببطاقة الذاكرة microSD، وهذا كافٍ لتطمئن أنّ كل شيء على ما يرام. لكن ما يحدث أن راسبيان يفتح مجموعةً من الملفات التي يستخدمها أثناء العمل، وإذا فُصلت التغذية الكهربائية لسببٍ أو لآخر، فستبقى هذه الملفات مفتوحةً وقد ينتج عن ذلك خللٌ في نظام التشغيل، كما قد تضطر أيضًا إلى إعادة تثبيته. لهذا لابدّ من وسيلةٍ لإبلاغ راسبيان بإغلاق كل هذه الملفات استعدادًا لفصل التغذية الكهربائية، وهذا ما يُعرف بإيقاف التشغيل. انقر على أيقونة راسبيري في الزاوية اليسارية العليا لسطح المكتب، وانقر على إيقاف التشغيل Shut Down. ستظهر لك شاشةٌ تضم ثلاثة خيارات، هي إيقاف تشغيل Shut Down وإعادة إقلاع Reboot وتسجيل خروج Logout، كما هو موضحٌ بالشكل التالي. لدى اختيارك أمر إيقاف التشغيل Shut Down (وهو الأكثر استخدامًا)، فإنك تخبر راسبيان أن يغلق كافة البرمجيات والملفات المفتوحة ومن ثم يطفئ راسبيري باي. بعد أن تصبح شاشة العرض سوداء، انتظر عدة ثوانٍ حتى يختفي الضوء الأخضر على باي ومن ثم يمكنك فصل التغذية الكهربائية؛ ولإعادة تشغيل باي صل كابل الطاقة الكهربائية مجددًا. شكل 21-3 إيقاف تشغيل راسبيري باي تمر عملية إعادة الإقلاع Reboot بمراحل إيقاف التشغيل نفسها وذلك بإغلاق كل شيء، لكنها ستعيد تشغيل باي بدلًا من قطع التغذية الكهربائية عنه، أي كأنك أوقفت تشغيل الجهاز ثم فصلت كابل الطاقة الكهربائية وأعدت وصله. ستضطر إلى إعادة إقلاع باي عندما تجري تعديلات معينة على نظام التشغيل مثل تثبيت تحديثاتٍ محددة لبرمجياته البنيوية، أو أن يعمل أحد البرامج بصورةٍ خاطئة وتُعرف هذه الحالة بالانهيار crashing، فيؤثر على أداء راسبيان. أخيرًا، ستلمس فائدة تسجيل الخروج Logout إذا كان هناك أكثر من مستخدمٍ لجهاز راسبيري باي، حيث تغلق هذه العملية كل البرامج المفتوحة، وتنتقل بك إلى شاشة تسجيل الدخول Login التي تطلب منك إدخال اسم المستخدم وكلمة المرور؛ وإذا نقرت عن طريق الخطأ على تسجيل الخروج، فيمكنك العودة بسهولةٍ إلى العمل بكتابة pi اسمًا للمستخدم وأية كلمة سر كنت قد اخترتها في معالج الإعداد الترحيبي الذي شرحناه بداية هذا المقال. ترجمة -وبتصرف- للفصل الثالث Using your Raspberry Pi من كتاب The official Raspberry Pi Beginners Guide اقرأ أيضًا المقال السابق: تجميع راسبيري باي والتحضير لاستعماله تعرف على جهاز راسبيري باي Raspberry Pi المدخل الشامل لتعلم علوم الحاسوب أنظمة التشغيل للمبرمجين
-
من غير الممكن لسوء الحظ، إقناع كل زوار موقعك أن يصبحوا عملاءً. لهذا لا بدّ في عالم الأعمال أن تحلل في مرحلة ما سلوك الزوار واكتشاف الأسباب التي تمنعهم من التحوّل إلى عملاء. تحتاج الأعمال بأسلوبها التقليدي أو من خلال الإنترنت، إلى تجربة مستخدم مثالية كي تضمن جذب زوار أكثر ليصبحوا عملاءً، سواءً من خلال شاشات العرض داخل المتاجر، أو من خلال نماذج ويب لكسب العملاء، أو استخدام أسلوب الجذب الذي يُعرف بمخروط المبيعات متعدد المراحل multi-stage sales funnel. وبما أنه من غير غير الممكن إقناع كل الزوار بأن يصبحوا عملاءً، فلا بدّ لأصحاب الأعمال أو خبراء تصميم تجارب العملاء UX، أن يتابعوا البيانات التحليلية في مرحلة ما، وأن يتساءلوا عن الأسباب التي تمنع الزوار من التحوّل إلى عملاء. يُعَد جوجل أنالايتكس Google Analytics أكثر إطارات العمل شعبيةً في مجال تحليل البيانات، إلا أنه قد يُغرق المصممين ببيانات كمية عن سلوك المستخدم وعدد مرات استعرض الصفحات، ومعدل مغادرة الصفحات، ومصادر الحركة نحو الصفحات وغيرها. وعلى الرغم من كم البيانات التي سنحصل عليها من أداة تحليل البيانات هذه، إلا أنك لن تجد تفسيرًا لسلوك الزوار أثناء تصفحهم المواقع. فتسليط الضوء على مشكلة مثل معدل مغادرة الزوار المرتفع لموقع، هو أمر؛ أما اكتشاف الوسيلة لحل هذه المشكلة، فهو أمر آخر. ووفقًا لهذه الرؤية، نستطيع القول أن جوجل أنالايتكس يفتقر إلى أداة جوهرية يحتاجها مصممو تجربة المستخدم، وهي جمع الآراء من مستخدمين حقيقيين. الصفحة الرئيسية لـ Hotjar استخدام أداة Hotjar لفهم سلوك المستخدمين وجمع آرائهم يتيح هوتجار Hotjar للشركات إمكانية فهم ما يراه ويفعله الناس على مواقعهم الإلكترونية، ويساعدهم على فهم سبب استخدام الناس لمواقعهم تحديدًا وكيف يستخدمونها. وبفضل Hotjar ليس من الضروري وضع توقعات حول سلوك الزوار، وذلك لامتلاكه عددًا كبيرًا من الميزات التي ترشد المسوقين ومصممي تجربة المستخدم إلى تفاصيل التجربة التي يخوضها زوار مواقعهم الإلكترونية. الخرائط الحرارية للمواقع الخريطة الحرارية هي لقطة شاشة لصفحة ويب عليها مخطط من طبقات، تتدرج ألوانه من الدافئة إلى الباردة، لتظهر النقاط التي نقرها الزوار أكثر وإلى أي حد وصلوا في تصفحهم، والأماكن التي نقلوا إليها مؤشر الفأرة. وتمامًا مثل خريطة في نشرة جوية، سيكون اللون الأحمر هو الأكثر حرارةً أو الأكثر جذبًا للانتباه، أما الأزرق فيدل على المناطق الباردة، أو التي لا تسترعي انتباه الزوار. تُعَد الخرائط الحرارية أكثر أدوات Hotjar شعبيةً، ومن الممكن استخدامها على أية صفحة وأي جهاز. وتُلخّص سلوكيات المستخدمين كالتالي: خرائط النقر click maps: تُظهر الأماكن التي ينقر عليها الزوار (نقر أو لمس). خرائط التمرير scroll maps: وتُظهر كم المسافة التي تصفحها الزوار نزولًا في صفحة الويب. خرائط التنقل move maps: وتظهر مسارات حركة الفأرة. الخرائط الحرارية التي تولدها Hotjar. المنطقة التي تميل إلى الأحمر هي الأكثر نقرًا وبعكسها تلك التي تميل إلى الأزرق. تساعد الخرائط الحرارية المصممين بما تقدمه من إرشادات مرئية مختصة على إنشاء صفحات ذات تخطيط أفضل تلفت انتباه المستخدم إلى أهم أجزاء الصفحة رافعة نسبة تحول الزوار إلى عملاء. وأكثر التحسينات على التصميم شيوعًا، هو تحسين أو توجيه معدل اتخاذ القرار نحو المنطقة الدافئة، مما يرفع نسبة تحول الزوار إلى عملاء. سجلات جلسات العمل تلخص الخرائط الحرارية في ملف واحد السلوك الإجمالي للزوار ضمن صفحة محددة، بينما تسمح سجلات جلسات العمل لأصحاب الأعمال بالاطلاع على التجارب الفردية، وفهم طريقة تفاعل الأشخاص مع الموقع. أين يتجهون؟ متى يرتبك الزوار في الموقع ومتى يشعرون بالإحباط؟ لماذا لم يجذبهم الموقع ليصبحوا عملاء؟ تُعَد سجلات Hotjar بديلًا ممتازًا لاختبارات فعالية الاستخدام usability tests، مثل أبحاث تجارب المستخدم UX researchers، وتساعد المصممين على التجاوب مع عشرات التجارب التي يختبرها مستخدمون فعليون، وتمنحهم رؤيةً دقيقةً لأسلوب تصفح الزوار لمحتويات صفحات الويب، كما يزوّدهم كل سجل بمسار زمني تفاعلي يعرض نقاط التفاعل الأساسية مع الصفحات. لن تحتاج أكثر من عدة سجلات عادةً للتعرف على أنماط السلوك واكتشاف ما ينفع وما لا ينفع. فلو أخذنا على سبيل المثال حالة النقر المتكرر على عنصر، فسيوحي ذلك بأن الزائر قد انتقل إلى مرحلة اتخاذ قرار الشراء (سواءً فعل ذلك أم لا)، أو أن الرابط لا يعمل كما هو مطلوب. وفي كلتا الحالتين يمكن تسوية الحالة لجعل تجربة المستخدم ككل أكثر فعاليةً ومتعة. من الأفضل دائمًا في حالة السجلات أن تستفيد من خبرات مصممي تجارب المستخدم. وبما أنّ متابعة تجاوب الزوار أمر يستهلك الوقت، فلا بدّ من تحديد أهداف واضحة قبل متابعة السجلات لزيادة فعالية العملية وفائدتها. فقد ترغب مثلًا شركات SaaS في تحسين نموذج الاشتراك المجاني على مواقعها، بينما يرغب مالكو eCommerce في التركيز على عمليات التحقق. إذًا فتحديد هذه الأهداف مسبقًا سيسمح للمصممين بالتركيز على الأجزاء الأكثر إلحاحًا في السجلات دون هدر المزيد من الوقت. مخروط التحول يسعى أصحاب الأعمال إلى تحويل المزيد من الزوار إلى عملاء، فالهدف بالنسبة لهم إذًا تحويل الزوار إلى عملاء من خلال وسيلة الاصطياد المخروطية funnel سواءً للحصول على مشتركين جدد ضمن قوائم الرسائل الإخبارية، أو لشراء المزيد من المنتجات. يتألف المخروط النمطي في منصة eCommerce مثلًا، من خمس مراحل مختلفة على الأقل هي: الصفحة الرئيسية Homepage، ثم صفحة المنتجات Product page، ثم سلة التسوق Cart، ثم التحقق Checkout، بعدها صفحة تأكيد الطلب Confirmation page. وكما هو حال المخروط الفعلي، يضيق مخروط الاصطياد عبر الإنترنت عند النهاية، بمعنى أن عدد العملاء في النهاية أقل من عددهم في البداية، لكن من الصعب التكهن بما يجري في المراحل الضمنية، أو لماذا يُغادر الزوار. مراحل مخروط انتقال الزائر إلى عميل كما يظهره مخروط Hotjar. لاحظ نسبة من يبقى في كل مرحلة. يُعَد مخروط هوتجار ميزةً متقدمةً تسمح لأصحاب الأعمال بمعرفة عدد الزوار الذين اجتازوا مرحلةً معينةً مقابل الذين غادروها. وبفهم متى وكيف يغادر العملاء المحتملين، سيتمكن المصممون من إيجاد وضع مثالي للمخروط وتحسين المسار الكلي للمستخدم ضمن الصفحة. إنّ غياب عملية مطلوبة لاتخاذ قرار (مثل اختيار الحجم قبل إضافة منتج إلى سلة المشتريات) أو وضعها في مكان خاطئ أو نماذج طويلة يجب ملؤها، هي أمثلة عن عوائق لتحول الزائر إلى عميل، وستُحدث ثغرات في مخروط الجذب. ستجد هذه الثغرات في الصفحات التي تعاني من معدل مرتفع لمغادرة الزوار، لذلك سيحسن مصممو تجارب المستخدم من تصاميمهم لسد تلك الثغرات بمجرد تحديدها، مما سيدفع المزيد من الزوار نحو نهاية المخروط وبالتالي زيادة عدد التحولات المحتملة. النماذج ينتهي مخروط التحوّل Conversion funnel غالبًا بنموذج يُملئ ويُرسل، سواءً كان نموذج تسجيل، أو نموذج تحقق، أو نموذج إرسال بيانات. يُعَد تجاهل النماذج أمرًا شائعًا ولكنه لا يُعد أمرًا مهمًا دائمًا في تجربة المستخدم، غير أنه في الوقت ذاته مهم لفهم السبب الذي يدفع مستخدم يبدو وكأنه جاهز للتحول إلى تجاهل النموذج فجأة، فما يغير نيته عادة هي التجارب السلبية. تقرير Hotjar عن سبب مغادرة الزائر قبل إكمال نموذج الاشتراك أو الشراء. لاحظ عدد المغادرين عند كل مرحلة (كتابة الاسم أو البريد الإلكتروني أو اسم الشركة وهكذا..). يغطي تقرير النماذج في Hotjar مجالًا واسعًا من التحليلات، مثل عدد جلسات العمل، وزمن التفاعل الوسطي، ومعدل التحول، وحقول النموذج التي تُركت فارغة. تكون هذه الإضاءات مصحوبةً بممارسات أفضل، مثل إبقاء عدد الحقول في حده الأدنى، وتزويد النموذج بعناوين ومدخلات واضحة، ستساعد أولًا على التخلص من العوامل التي تدفع العملاء إلى المغادرة، وتقود إلى نماذجًا ذات معدلات تحوّل مرتفعة. استطلاعات الرأي من السهل الحصول على تصوّرات خاطئة عما يهتم با الناس أو لماذا يتصرفون على نحوٍ معين. فوضع افتراضات عن سلوك الزوّار هو العدو الأكبر لمالكي الأعمال ومصممي تجربة المستخدم، لأنها تقود إلى أخطاء قاتلة في التصميم والتسويق. ولا تمثّل الآراء الصادقة للعملاء الطريق الأفضل لتلافي تلك الأخطاء وحسب بل هي الطريق الأقصر لاكتشاف كيف وصلوا إلى مكان ما وما الذي يبحثون عنه، ماذا يحتاجون وما الذي يخشونه. تُعدُّ استطلاعات رأي هوتجار Hotjar’s poll أداةً غير مزعجة، تظهر أسفل الصفحة وتعرض أسئلةً للحصول على معلومات متعلقة بالموضوع المطروح. يمكن طرح هذه الأسئلة ضمن صفحة خاصة لتعرف مثلًا نوع المحتوى الذي يبحث عنه الزوار، أو وفقًا لسلوك الزائر، مثل الحالة التي يغادر فيها الصفحة. تظهر فائدة أداة استطلاع الرأي للأعمال المرتبطة بمنصة eCommerce في فهم سبب مغادرة العملاء قبل النقر على تأكيد الطلب Confirm Order في نهاية مخروط المبيعات، فأسئلة مثل "ما هي أكبر مخاوفك من شراء هذا المنتج من موقعنا؟"، أو "ما المعلومات التي تحتاجها لتسهل عليك اتخاذ قرار الشراء؟"، قد تساعدك على استخلاص آراءٍ تتعلق بالحالة المدروسة تمامًا، بما في ذلك نقص المعلومات التي توضح عوامل الأمان عند تنفيذ عملية الدفع وتكاليف الشحن، ووقت التسليم، وتفاصيل الكفالة أو سياسة إعادة المنتج الذي اشتريته. يتيح هوتجار أيضًا أداةً قابلةً للتحكم لرصد أراء العملاء السريعة، تُدعى الآراء المستقبلة Incoming feedback. تسمح هذه الأداة القابلة للتوسع للأشخاص بتقييم صفحة أو عنصر محدد والتعليق عليه باستخدام الوجوه التعبيرية emojis، والتي قد تمثل بديلًا أكثر لطفًا عن استطلاعات الرأي، وذلك اعتمادًا على الجمهور المستهدف ومدى جدية العلامة التجارية المطروحة. الاستبيانات لا أحد يعرف كيف يفكر العملاء سوى العملاء أنفسهم، لذلك تُعَد الاستبيانات طريقةً ممتازةً لجمع آراء نوعية من العملاء أنفسهم، وذلك أثناء زيارتهم لصفحة أو بعد زيارتها، إذ تستهدف صلب المواضيع المطروحة، فقد تهتم مواقع eCommerce على سبيل المثال باستبيانات ما قبل وما بعد الشراء لتحديد السبب الرئيسي الذي يدفع المستخدم إلى إتمام عملية الشراء أو تركها. أحد النماذج المثالية لتصميم استبيان يتيح هوتجار لمالكي المواقع الإلكترونية إمكانية إنشاء استبيانات مخصصة يمكن مشاركتها عبر البريد الإلكتروني أو الروابط، أو من خلال النوافذ المنبثقة في صفحات الويب، وقد تكون الاستبيانات على شكل أجوبة مفتوحة، أو خيارات متعددة أو أسئلة متدرجة. توفر لك الأجوبة المفتوحة في معظم الأوقات أكثر الإرشادات فائدةً، وذلك لأنها تعبير عن تجربة الأشخاص بكلماتهم الخاصة وما الذي يريدون تحسينه بالفعل. وبغض النظر عن طبيعة الأسئلة المطروحة، فلا بدّ أن تركّز على ما يحتاجه العملاء وما يسعون إليه، كما يمكن أن تركز على مسائل متنوعة مثل المحتوى (هل هذه المقالة مفيدة؟)، أو أن تحول الزوّار إلى عملاء (ما الذي تعتقد بأنه منعك من الشراء اليوم؟). يميل معظم الأشخاص إلى المشاركة في الاستبيانات إن كان هنالك شيء بالمقابل، لهذا فكر جديًا بالمحفّزات. استعمال Hotjar تثبيت Hotjar تتيح Hotjar اشتراكًا مجانيًا بميزات أساسية وحدود لتبادل البيانات. يُعَد هذا الخيار مثاليًا لمواقع الويب الصغيرة التي تريد أن تلقي نظرةً واسعةً على سلوك العملاء واحتياجاتهم، بينما قد يهتم أصحاب الأعمال الأكبر بالاشتراك المميز Premium الذي يمكّنهم من جمع بيانات أكثر والحصول على استطلاعات رأي مخصصة وواجهات لإجراء الاستبيانات. من السهل إعداد Hotjar، فما عليك سوى تثبيت مجموعة من شيفرات التتبع إما يدويًا، أو من خلال إضافة برمجية Plugin. يتوافق Hotjar مع مئات المنصات بما في ذلك ووردبرس من خلال الإضافة الرسمية الخاصة بالمنصة، ومنصات التجارة الإلكترونية eCommerce، مثل شوبفاي وماغينتو وبريستا شوب. بمجرد اشتراك الصفحة في Hotjar وتثبيت الشيفرة اللازمة، سيسهُل إنشاء الخرائط الحرارية ويبدأ في تسجيل جلسات العمل، أو إنشاء استطلاعات الرأي والاستبيانات. تُعَد لوحة التحكم في Hotjar واضحةً وتعرض النتائج بأسلوب مرئي، كما تُلخص البيانات في جداول بسيطة يسهل البحث فيها واستخلاص النتائج. الموازنة بين Hotjar وجوجل أنالايتكس يُعَد أنالايتكس أداةً معقدةً كما أشرنا سابقًا، وتركز فقط على كم البيانات التي يمكن جمعها، بينما يقدم Hotjar إضاءات كمية وكيفية بطريقة مفهومة. لا يُعَد Hotjar قويًا أو مفصّلًا مثل جوجل أنالايتكس من ناحية البيانات الكميةـ مثل التقارير الديموغرافية أو تقارير مخاريط التحوّل. وصف مارك زوكربيرغ في أحد المرات وصفة فيسبوك للنجاح، فقال:"لقد أنصتنا فعليًا لما يريده المستخدمون، إنصاتًا كيفيًا للكلمات التي يقولونها، وكميًا بتمعّن السلوك الذي يمارسونه". وبدلًا من النظر إلى أنالايتكس وHotjar على أنهما ضدان (وهما بالفعل ليسا كذلك)، من الأفضل عدّهما متكاملان. فاستخدام الحلول التي يتيحانها ستزوّد مصممي تجارب المستخدم والمسوقين بإضاءات لتحسين عملهم. التعامل مع مصممي تجارب المستخدم وأبحاث تجارب المستخدم يمكن للمسوقين ومحللي البيانات المحترفين تقديم العون للشركات في فهم البيانات الكمية، لكن جمع الآراء وتحليلها يتطلب معرفةً بعلم النفس والتعاطف. وعلى الرغم من أن واجهة Hotjar أكثر وضوحًا من جوجل أنالايتكس، فقد تكون مفيدةً لمالكي الأعمال كي يعملوا يدًا بيد مع الباحثين في مجال تجارب المستخدم ومصمميها لتفسير النتائج. يمكن لخبراء تجارب المستخدمين فعل الآتي: الإضاءة على جزئيات محددة. اكتشاف تحسينات محتملة. إعداد تقارير عن سجلات جلسات العمل. التفكير بأسئلة في صلب الموضوع عند تصميم استطلاعات الرأي والاستبيانات. التوصية بمحتوى معين أو تغييرات محددة في التصميم. تتبع اختبارات "الاختلاف الوحيد A/B test". سيساعد ذلك الأعمال بالمشاركة مع ميزات ومنافع Hotjar في تحسين تجربة عملائها وبالتالي نمو هذه الأعمال، فهذا استثمار يرفع قيمة عائدات الاستثمار ROI على المدى الطويل. ترجمة -وبتصرف- للمقال Hotjar UX and Conversion Tips لصاحبته Laura Geley. اقرأ أيضًا كيفية إجراء اختبار سريع لتجربة المستخدم كيفية استخدام اختبارات تجربة المستخدم لتحسين قيمة استمرار التعامل مع العميل CLV طرق مثبتة لتخصيص تجربة المستخدم كيفية تحسين إعدادات تطبيق بناء على تجربة المستخدم كيفية إجراء بحث في تجربة المستخدم لتحسين مشاريعك
-
يصف هذا القسم مجموعةً من المعايير الحديثة لمكونات الويب، وهذه المعايير بصدد التطوير حاليًا. تُدعم بعض الميزات وتتكامل ضمن معايير HTML/DOM، بينما يبقى البعض الآخر في مرحلة الإعداد. ويمكنك تجربة الأمثلة في أي متصفح، لكن يبقى المتصفح كروم Chrome الأكثر حداثةً فيما يتعلق بدعم هذه الميزات، والسبب طبعًا هو أنّ أشخاصًا من Google هم من وضعوا العديد من هذه المواصفات. ما هو الشيء المشترك بين محطة الفضاء ومكونات الويب إن فكرة المكونات ليست جديدة، فقد استُعملت في العديد من إطارات العمل وفي منصات مختلفة، وذلك قبل الانتقال إلى تفاصيل إنجاز تلك الميزات. لنلقي نظرةً على الصورة التالية، والتي تمثل إنجازًا عظيمًا للإنسانية: إنها محطة الفضاء الدولية، وتمثل الصورة التالية المكوِّنات التي تتألف منها -تقريبيًا طبعًا-: نلاحظ الآتي: تتألف المحطة من مكوِّنات عديدة. يمتلك كل مكوِّن بدوره أجزاءً أصغر في داخله. المكوّنات معقدّة جدًا، وهي بالطبع أعقد بكثير من معظم مواقع الويب. طوّرت مكونات المحطة على صعيد عالمي، ومن قِبل فرق من دول مختلفة تتحدث بلغات مختلفة. تحلق المحطة في مدارها حول الأرض وتبقي من في داخلها على قيد الحياة، فكيف بني هذا الجهاز المعقد؟ وما هي المبادئ التي يمكن أن نستعيرها لنطوّر مكوّنات ويب لها نفس مستوى الوثوقية وقابلة للنمو، أو قريبة من ذلك على الأقل؟ معمارية المكون تنص القاعدة الشهيرة في تطوير البرمجيات المعقدة على أن لا تطوّر برمجيات معقّدة، فإذا وصل بك الأمر إلى أمر معقد، فافصله إلى أجزاء أبسط واربطها ببعضها بأكثر الطرق وضوحًا، فالمعماري الأمهر هو من يستطيع تحويل المعقد إلى بسيط. يمكن تقسيم واجهة المستخدم إلى مكوّنات مرئية لكلٍّ منها موقعه في الصفحة، وتُنفِّذ مهمةً محددةً بعناية وتكون مفصولةً عن بعضها البعض، لنلق نظرةً على موقع ويب، وليكن تويتر مثلًا، يُقسم الموقع إلى مكوّنات: مكوّن للتنقل. معلومات المستخدم. اقتراحات المتابعة. نموذج إرسال. رسائل. غيرها من المكوِّنات. قد يكون للمكوّنات مكوّنات فرعيةً أيضًا، فقد تكون الرسائل مكوّنات فرعيةً من مكوّن أعلى مستوىً مثل قائمة الرسائل، كما قد تكون صورة المستخدم التي تقبل النقر مكوّنًا.. وهكذا. كيف نحدد ما هو المكوِّن إذًا؟ يأتي ذلك من حسن البديهة والخبرة والمنطق، حيث تشكًل المكوّنات عادةً كيانات مرئيةً نميزها وفقًا لوظيفتها وكيفية تتفاعلها مع الصفحة، ففي حالة الصفحة السابقة، سنجد أنها تحوي وحدات يلعب كل منها دوره الخاص، ومن المنطق إذًا أن نجعلها مكوّنات. للمكوِّن: صنف JavaScript خاص به. بنية DOM تُدار منفردةً من قبل الصنف الخاص بها، ولا يمكن الوصول إليها من قبل شيفرة خارجية (مبدأ التغليف). تنسيق CSS يُطبَّق على المكوّن. واجهة برمجية API، أحداث وتوابع يستخدمها الصنف وغيره من الكائنات التي تتفاعل مع المكوّنات الأخرى. تذكر أنّ فكرة الكائن ككل لا تفرض وجود أي شيء خاص. تتواجد الكثير من المنصات وإطارات العمل التي تقدم وسائل لإنشاء المكوّنات، وتستخدم أصناف CSS بالإضافة إلى ترتيبات أخرى لتعزيز المكوّنات وتحديد مجال تطبيق CSS وتغليف DOM. تزوّدنا مكوِّنات الويب بإمكانيات تصفّح مدمجةً معها ولا حاجة لمحاولة تقليدها بعد الآن. عناصر مخصصة Custom elements: لتعريف عناصر HTML مخصصة. شجرة DOM مخفية Shadow DOM: لإنشاء شجرة داخلية للمكوّن مخفية عن بقية المكوّنات. مجال لتطبيق أوراق التنسيق CSS Scoping: لتعريف قواعد تنسيق يمكن تطبيقها على الشجرة الداخلية لمكوّن. إعادة توجيه الحدث Event retargeting وغيره من الأمور الثانوية: لتحسين قدرة المكوّنات على التلاؤم مع عملية التطوير. كل الميزات المدعومة جيدًا لمكوّنات الويب جيدة في الأمور التي تقدر على تنفيذها. عناصر HTML المخصصة يمكننا إنشاء عناصر HTML مخصصة يصفها الصنف الذي ننشئه لهذا الغرض مزوّدًا بتوابعه وخصائصه وأحداثه الخاصة، وسنتمكن من استخدام العنصر ضمن عناصر HTML الأصلية بعد أن نعرّفه، وهذا أمر جيد، فعلى الرغم من غنى HTML بالعناصر، إلا أنها لا تحوي عناصرًا مثل علامة جداول سهلة التعامل <easy-tabs> أو سير دائري منزلق <sliding-carousel> أو الرفع بطريقة جذابة <beautiful-upload> أو أي عناصر أخرى قد نحتاجها. يمكن تعريف العناصر المخصصة ضمن أصناف خاصة، واستخدامها كما لو أنها جزء من HTML، ويوجد نوعان من هذه العناصر المخصصة، وهما: عناصر مخصصة ذاتية التصرف Autonomous: وهي عناصر جديدة توّسع عمل الصنف المجرّد HTMLElement. عناصر أصلية مخصصة customized: توسّع العناصر الأصلية، لإنجاز أزرار مخصصة مبنية على أساس الصنف HTMLButtonElement مثلًا. سنغطي أولًا العناصر ذاتية التصرف، ثم سننتقل إلى العناصر المخصصة، ولإنشاء عنصر مخصص لا بدّ من إخبار المتصفح عن جملة من التفاصيل حوله، مثل كيف سيُظهره؟ وماذا سيفعل عند إضافته أو إزالته من الصفحة؟ يجري ذلك بإنشاء صنف له توابعه الخاصة، وسيكون الأمر بسيطًا، فهي عدة توابع فقط وجميعها اختيارية. إليك تصورًا مع قائمة كاملة من التوابع: class MyElement extends HTMLElement { constructor() { super(); // إنشاء العنصر } connectedCallback() { //يستدعيه المتصفح عند إضافة عنصر ويمكن استدعاؤه عدة مرات إن أضيف أو حذف العنصر } disconnectedCallback() { //يستدعيه المتصفح عند حذف عنصر ويمكن استدعاؤه عدة مرات إن أضيف أو حذف العنصر } static get observedAttributes() { return [/* مصفوفة من أسماء السمات التي تراقب التغيرات */]; } attributeChangedCallback(name, oldValue, newValue) { // يُستدعى عندما تتغير صفة من السمات المذكوره أعلاه } adoptedCallback() { // يُستدعى عندما يُنقل العنصر إلى صفحة أخرى } // إن كان هناك عناصر أو سمات أخرى } نحتاج بعد ذلك إلى تسجيل العنصر: // سيُخدّم من قبل الصنف الجديد <my-element> أبلغ المتصفح أن customElements.define("my-element", MyElement); ستتولد الآن نسخة عن الصنف MyElement لكل عنصر HTML له الوسم <my-element>، وستستدعى التوابع المذكورة. كما يمكن تنفيذ ذلك باستخدام شيفرة JavaScript كالتالي: document.createElement('my-element') مثال عنصر لتنسيق الوقت يُستخدم العنصر الأصلي <time> لإظهار الوقت والتاريخ، لكنه لا يقدم أي تنسيق للبيانات التي يعطيها، لننشئ إذًا العنصر <time-formatted> الذي يعرض الوقت بتنسيق جميل يأخذ اللغة بالحسبان: <script> class TimeFormatted extends HTMLElement { // (1) connectedCallback() { let date = new Date(this.getAttribute('datetime') || Date.now()); this.innerHTML = new Intl.DateTimeFormat("default", { year: this.getAttribute('year') || undefined, month: this.getAttribute('month') || undefined, day: this.getAttribute('day') || undefined, hour: this.getAttribute('hour') || undefined, minute: this.getAttribute('minute') || undefined, second: this.getAttribute('second') || undefined, timeZoneName: this.getAttribute('time-zone-name') || undefined, }).format(date); } } customElements.define("time-formatted", TimeFormatted); // (2) </script> <!-- (3) --> <time-formatted datetime="2019-12-01" year="numeric" month="long" day="numeric" hour="numeric" minute="numeric" second="numeric" time-zone-name="short" ></time-formatted> See the Pen JS-p3-06-Custom-elements -ex1 by Hsoub (@Hsoub) on CodePen. تشير (1) إلى الصنف تابع واحد فقط هو ()connectedCallback، حيث يستدعيه المتصفح عندما يُضاف العنصر إلى الصفحة، أو عندما يكتشف مفسّر HTML وجوده، ويستخدم العنصر تابع تنسيق البيانات المدمج Intl.DateTimeFormat الذي يلقى دعمًا جيدًا من معظم المتصفحات لعرض الوقت بتنسيق جميل. تعني (2) أنه لا بدّ من تسجيل العنصر الجديد باستخدام الأمر (customElements.define(tag, class). تعني (3) أنه يمكن الآن استخدام العنصر في أي مكان. تحديث العنصر المخصص إذا صادف المتصفح العنصر <time-formatted> قبل عبارة التعريف customElements.define فلن يعدّ ذلك خطأً، بل سيعدّه مجرد عنصر غير معروف مثل أي وسم غير معياري، ويمكن تنسيق هذه العناصر غير المعروفة باستخدام المحدد (not(:defined:، وهو أحد محددات CSS. ستُحدّث هذه العناصر عندما يُستدعى التابع customElement.define، وستتولد نسخة من الصنف TimeFormatted لكلٍّ منها، ثم يُستدعى التابع connectedCallback وتتحول إلى عناصر معرّفة defined:. توجد توابع عدة للحصول على معلومات عن عناصر مخصصة: (customElements.get(name: يعيد الصنف الخاص بالعنصر الذي يحمل الاسم المحدد name. (customElements.whenDefined(name: يعيد وعدًا promise يحَلَّ -دون قيمة- عندما يُعرَّف العنصر المخصص الذي له الاسم name المحدد. التصيير داخل connectedCallback لا في constructor رأينا في المثال السابق كيف جرى تصيير محتوى العنصر ضمن التابع connectedCallback، لكن لماذا لم يُصيّر ضمن الدالة البانية constructor؟ إنّ السبب بسيط، لأنه من المبكر جدًا تصيير المكوّن عند استدعاء constructor، فعندما يُنشأ العنصر في هذه المرحلة، فلن يُعالجه المتصفح أو يسند إليه السمات الخاصة به، فاستدعاء getAttribute سيعيد القيمة "لاشيء" null، وبالتالي لن نتمكن من تصيير شيء، ولو فكرنا بالأمر قليلًا فسنجد أنّ هذا الأداء أفضل لتأخير العمل حتى يكون كل شيء جاهزًا. يقع الحدث connectedCallback عندما يُضاف العنصر إلى الصفحة، ولن يُضاف إلى عنصر آخر مثل ابن فقط، بل سيغدو عمليًا جزءًا من الصفحة، وهكذا سنتمكن من بناء شجرة DOM منفصلة، وإنشاء عناصر وتحضيرها للعمل لاحقًا، وستصيّر هذه العناصر فعليًا عندما تُضاف إلى الصفحة. مراقبة السمات ليس لتغير السمات attribute في الطريقة الحالية لتنفيذ العنصر <time-formatted> أي تأثير بعد تصييره، وهذا غريب بالنسبة إلى عنصر HTML، فعندما نغيّر في صفة مثل a.href، فسنتوقع عادةً أن نرى التغير مباشرةً. نصلح هذا الأمر إذًا. يمكن مراقبة السمات بتزويد دالة الإحضار الساكنة ()observedAttributes بقائمة تضم أسماءهم، وهكذا ستُستدعى الدالة attributeChangedCallback عندما نجري أي تعديلات على تلك السمات. لن يقع هذا الحدث بالطبع عندما تتغير سمات غير مدرجة في القائمة، لأسباب تتعلق بالأداء. إليك شيفرة العنصر <time-formatted> الذي يُحدَّث تلقائيًا عندما تتغير سماته: <script> class TimeFormatted extends HTMLElement { render() { // (1) let date = new Date(this.getAttribute('datetime') || Date.now()); this.innerHTML = new Intl.DateTimeFormat("default", { year: this.getAttribute('year') || undefined, month: this.getAttribute('month') || undefined, day: this.getAttribute('day') || undefined, hour: this.getAttribute('hour') || undefined, minute: this.getAttribute('minute') || undefined, second: this.getAttribute('second') || undefined, timeZoneName: this.getAttribute('time-zone-name') || undefined, }).format(date); } connectedCallback() { // (2) if (!this.rendered) { this.render(); this.rendered = true; } } static get observedAttributes() { // (3) return ['datetime', 'year', 'month', 'day', 'hour', 'minute', 'second', 'time-zone-name']; } attributeChangedCallback(name, oldValue, newValue) { // (4) this.render(); } } customElements.define("time-formatted", TimeFormatted); </script> <time-formatted id="elem" hour="numeric" minute="numeric" second="numeric"></time-formatted> <script> setInterval(() => elem.setAttribute('datetime', new Date()), 1000); // (5) </script> See the Pen JS-p3-06-Custom-elements -ex2 by Hsoub (@Hsoub) on CodePen. يُقصَد بـ (1) أنه قد نُقل منطق التصيير إلى التابع المساعد <time-formatted>. تعني (2) أن هذا التابع يُستدعى مباشرةً في اللحظة التي يضاف فيها العنصر إلى الصفحة. تعني (3) أنه يقع الحدث attributeChangedCallback عند حدوث أي تغير في السمات التي مررت على شكل قائمة إلى ()observedAttributes تعني (4) أنه يُعاد تصيير العنصر بعد ذلك. تعني (5) أنه في النهاية يمكننا إنشاء مؤقت مباشر بكل سهولة. تسلسل التصيير عندما يبني مفسِّر HTML الشجرة DOM، تعالَج العناصر الواحد تلو الآخر والأب قبل الابن، فلو كان لدينا العنصر: <outer><inner></inner></outer> فسيتولد العنصر <outer> أولًا ويضاف إلى DOM، ثم العنصر <inner>، وسيقود ذلك إلى تبعات بالنسبة للعناصر المخصصة، فإذا أراد العنصر المخصص الوصول إلى innerHTML ضمن connectedCallback مثلًا، فلن يحصل على شيء. <script> customElements.define('user-info', class extends HTMLElement { connectedCallback() { alert(this.innerHTML); // empty (*) } }); </script> <user-info>محمد</user-info> إذا نفّذت الشيفرة السابقة فستظهر الرسالة alert فارغة، والسبب في ذلك هو عدم وجود عناصر أبناء في هذه المرحلة، وستكون الشجرة DOM غير مكتملة، يربط مفسِّر HTML العنصر المخصص <user-info> وسيستأنف العمل بعدها لربط أبنائه لكنه لم يفعلها بعد. إذا أردنا تمرير معلومات إلى عنصر مخصص، فيمكننا استخدام السمات فهي متاحة مباشرةً، وإذا كنا بحاجة فعلًا إلى الأبناء، فيمكن تأجيل الوصول إليهم باستخدام الحدث setTimeoutدون تأخير زمني Zero-delay. <script> customElements.define('user-info', class extends HTMLElement { connectedCallback() { setTimeout(() => alert(this.innerHTML)); // محمد (*) } }); </script> <user-info>John</user-info> وهكذا ستُظهر الرسالة alert في السطر (*) الكلمة "محمد" طالما أننا ننفذ الأمر دون تزامن، وذلك بعد اكتمال تفسير شيفرة HTML، ويمكن معالجة الأبناء إذا اقتضى الأمر ثم إنهاء مرحلة التهيئة، لكن هذا الحل ليس مثاليًا. فإذا استخدمت العناصر المخصصة الحدث لتهيئة نفسها، فإنها ستصطف بحيث يقع الحدث setTimeout للعنصر الخارجي ثم الداخلي، وبالتالي ستنهي العناصر الخارجية تهيئة نفسها قبل الداخلية، لنشرح ذلك من خلال المثال التالي: <script> customElements.define('user-info', class extends HTMLElement { connectedCallback() { alert(`${this.id} connected.`); setTimeout(() => alert(`${this.id} initialized.`)); } }); </script> <user-info id="outer"> <user-info id="inner"></user-info> </user-info> تسلسل الخرج: يُربط العنصر الخارجي بالشجرة. يُربط العنصر الداخلي. يُهيأ العنصر الخارجي. يُهيأ العنصر الداخلي. يمكننا أن نرى بوضوح أن العنصر الخارجي سينهي تهيئة نفسه (الخطوة 3) قبل الداخلي (الخطوة 4)، ولا يوجد استدعاء مدمج يمكن أن يقع بعد أن يصبح العنصر ضمن الشجرة، كما يمكن تنفيذ استدعاء مثل هذا عند الحاجة بأنفسنا، حيث يمكن للعناصر الداخلية إرسال أحداث مثل initialized تستمع إليها العناصر الخارجية وتتفاعل معها. العناصر الأصلية المخصصة ليس للعناصر المخصصة -مثل <time-formatted>- أي دلالات تتعلق بها، فهي غير معروفة لمحركات البحث مثلًا، ولا تستطيع الأجهزة التي تدعم مبدأ الوصول السهل accessibility التعامل معها. قد تكون هذه الأشياء مهمةً، فقد يرغب محرّك البحث بمعرفة أن هذا العنصر سيظهر الوقت، فإذا أنشأنا نوعًا خاصًا من الأزرار، فلماذا لا يمكننا إعادة استخدام وظيفة الزر الأصلي <button>؟ يمكننا توسيع وتخصيص العناصر الأصلية بالوراثة من أصنافها، فالأزرار مثلًا هي نسخ عن الصنف HTMLButtonElement، ولإنشاء عنصر يرث منه ويوسّعه عليك باتباع الآتي: وسِّع الصنف HTMLButtonElement عن طريق الصنف الذي سننشئه: class HelloButton extends HTMLButtonElement { /* custom element methods */ } أعط قيمةً للوسيط الثالث للدالة customElements.define والذي يمثّل الوسم tag المستهدف: customElements.define('hello-button', HelloButton, {extends: 'button'}); يمكن أن تشترك وسوم مختلفة بصنف DOM، لذلك سنحتاج إلى استخدام التعليمة extends. لكي نستخدم أخيرًا العنصر المخصص، استخدم الوسم الأصلي النظامي <button>، لكن أضف إليه الصفة "is="hello-button. <button is="hello-button">...</button> إليك المثال كاملًا: <script> // عند ضغطه "hello" الزر الذي يظهر class HelloButton extends HTMLButtonElement { constructor() { super(); this.addEventListener('click', () => alert("Hello!")); } } customElements.define('hello-button', HelloButton, {extends: 'button'}); </script> <button is="hello-button">Click me</button> <button is="hello-button" disabled>Disabled</button> See the Pen JS-p3-06-Custom-elements -ex3 by Hsoub (@Hsoub) on CodePen. يوسِّع الزر الجديد الزر الأصلي، وبالتالي سيحتفظ بتنسيق وميزات الزر الأصلي مثل الصفة disabled. قوالب HTML يقدم العنصر الأصلي <template> قوالبًا لتخزين شيفرة HTML ويتجاهل محتوياتها، ويتحقق من صحة التركيب اللغوي للشيفرة ضمنها حصرًا، لكن يمكن الوصول إليها واستخدامها في إنشاء عناصر أخرى. يمكن نظريًا إنشاء أي عناصر مخفية ضمن ملف HTML لتخزين شيفرة HTML، فما الغاية من <template>؟ أولًا: قد يحتوي على أي شيفرة HTM صالحة، حتى تلك التي تتطلب عادةً وسم إغلاق Closing tag، فيمكن أن نضع ضمنها أسطر جدول <tr>: <template> <tr> <td>Contents</td> </tr> </template> عندما نحاول عادةً وضع <tr> ضمن <div> مثلًا، فسيكتشف المتصفح الخطأ في بنية DOM ويصلحها بإضافة الوسم <table> وجعل السطر ضمنه تلقائيًا. من الناحية الأخرى سيُبقي <template> ما وضعناه في المكان الذي وضعناه فيه دون تعديل. يمكننا وضع تنسيقات وسكربتات ضمن <template> أيضًا: <template> <style> p { font-weight: bold; } </style> <script> alert("Hello"); </script> </template> يَعُد المتصفح محتويات <template> "خارج المستند"، أي لن تُطبق قواعد التنسيق الموجودة ضمنه ولن يعمل <video autoplay>، كما سيُصبح المحتوى حيًا -أي يُطبَّق التنسيق وتنفَّذ السكربتات- عندما ندرجه ضمن المستند. إدراج القالب يُتاح محتوى قالب ضمن الخاصية content العائدة له على شكل عقدة من النوع DocumentFragment، وهي نوع خاص من عقد DOM، ويمكن التعامل معها مثلما نتعامل مع أي عقدة DOM عدا خاصية واحدة مميزة، وهي إدراج الأبناء بدلًا من العقدة عندما نحاول إدراجها، فعلى سبيل المثال: <template id="tmpl"> <script> alert("Hello"); </script> <div class="message">Hello, world!</div> </template> <script> let elem = document.createElement('div'); // انسخ محتوى القالب لاستخدامه مرات عدة elem.append(tmpl.content.cloneNode(true)); document.body.append(elem); // سيعمل الآن السكربت الموجود ضمن القالب </script> دعونا نعيد كتابة مثال شجرة DOM الخفية في الفصل السابق باستخدام القالب <template>: <template id="tmpl"> <style> p { font-weight: bold; } </style> <p id="message"></p> </template> <div id="elem">Click me</div> <script> elem.onclick = function() { elem.attachShadow({mode: 'open'}); elem.shadowRoot.append(tmpl.content.cloneNode(true)); // (*) elem.shadowRoot.getElementById('message').innerHTML = "Hello from the shadows!"; }; </script> See the Pen JS-p3-06-Template-element -ex1 by Hsoub (@Hsoub) on CodePen. عندما ننسخ وندرج tmpl.content في السطر (*) مثل عقدة من النوع DocumentFragment، فسيُدرج أبناؤها (<style> و<p>) بدلًا منها، وسيشكلان شجرة DOM الخفية: <div id="elem"> #shadow-root <style> p { font-weight: bold; } </style> <p id="message"></p> </div> خلاصة للعناصر المخصصة نوعان: الأول عناصر مخصصة ذاتية التصرف Autonomous، لها وسم خاص جديد وتوسع الصنف HTMLElement، وإليك تخطيط التعريف لهذه العناصر: class MyElement extends HTMLElement { constructor() { super(); /* ... */ } connectedCallback() { /* ... */ } disconnectedCallback() { /* ... */ } static get observedAttributes() { return [/* ... */]; } attributeChangedCallback(name, oldValue, newValue) { /* ... */ } adoptedCallback() { /* ... */ } } customElements.define('my-element', MyElement); /* <my-element> */ أما النوع الثاني، فهو عناصر أصلية معدَّلة customized: توّسع عناصرًا أصليةً موجودة، وتتطلب وسيطًا ثالثًا للدالة define.، وإلى الصفة "..."=is ضمن وسمها. class MyButton extends HTMLButtonElement { /*...*/ } customElements.define('my-button', MyElement, {extends: 'button'}); /* <button is="my-button"> */ تدعم معظم المتصفحات العناصر المخصصة جيدًا، كما يوجد موائم polyfill أي تعويض نقص الدعم للمتصفحات غير المدعومة. قد يكون محتوى <template> أي عناصر HTML صالحة قواعديًا. يعُد المتصفح محتويات <template> خارج المستند، إذ لن يكون لها أي تأثير. يمكن الوصول إلى محتوى القالب template.content من JavaScript، ونسخها لإعادة استخدامها في مكوَّن جديد. الوسم فريد لأن: محتويات القالب <template> ستتعرض إلى تدقيق قواعدي من قِبل المتصفح، وهذا مخالف لاستخدام قالب نصي ضمن سكربت. يُسمح باستخدام وسوم HTML عالية المستوى حتى تلك التي لا يجب استخدامها دون عنصر يغلّفها، مثل <tr> التي ينبغي تغليفها بالوسم <table>. تصبح المحتويات حيّةً -أي تُطبَّق وتُنفَّذ- عندما تُدرج ضمن المستند. لا يدعم العنصر <template> المُكررات iterator، ولا آليات الربط بقواعد البيانات أو تبديل المتغيرات، لكن يمكن إدراجها في أعلى القالب. مهام لإنجازها عنصر توقيت مباشر أنشئ العنصر <live-timer> الذي يعرض الوقت الحالي: ينبغي أن يستخدم عنصر التوقيت العنصر <time-formatted> الذي أنشأناه سابقًا، بصورة ضمنية وليس بنسخ وظيفته. يُحدَّث المؤقت كل ثانية. مع كل تحديث للمؤقت سيتولد حدث خاص يُدعى tick يحمل التاريخ الحالي ضمن الخاصية event.detail، راجع فصل "إنشاء أحداث مخصصة في المتصفح عبر جافاسكربت". طريقة الاستخدام: <live-timer id="elem"></live-timer> <script> elem.addEventListener('tick', event => console.log(event.detail)); </script> إليك المثال النموذجي: يمكنك فتح المثال في بيئة تجريبية لاحظ أن: صفرنا المؤقت setInterval عند إزالة العنصر من الصفحة وهذا أمر مهم إذ بدونه سيستمر الحدث بالعمل كل ثانية في الوقت الذي لا يُستعمَل فيه، ولا يمكن للمتصفح أيضًا أن يمسح الذاكرة من تلقاء نفسه. يمكن الوصول إلى القيمة الحالية عبر الخاصية elem.date، إذ كل توابع الصنف وخاصياته هي توابع للعنصر وخاصياته. ويمكنك فتح الحل في بيئة تجريبية. المراجع HTML Living Standard Compatiblity ترجمة -وبتصرف- للفصول Custom elements وFrom Orbital Height وTemplate element من سلسلة The Modern JavaScript Tutorial. اقرأ أيضًا المقال السابق: إنشاء الحركات عبر جافاسكربت المقال التالي: مكونات الويب: التعامل مع شجرة DOM الخفية iframe { border: 1px solid #e7e5e3 !important; width: 100%; } iframe div#path { margin: auto; }
-
يتضمن هذا المقال تعريفًا بالتجهيزات الأساسية التي تحتاجها للعمل على راسبيري باي وكيفية توصيلها وإعدادها للعمل، حيث صُمم جهاز راسبيري باي ليكون سهل الإعداد وسريعًا قدر الإمكان، لكنه سيتأثر بالمكونّات الخارجية المُتصلة به، والتي تُدعى طرفيات peripherals مثل بقية الحواسيب. قد يبدو لك الأمر مربكًا للوهلة الأولى عندما تتأمل اللوحة المجردة لجهاز راسبيري باي، والتي تبدو مختلفةً تمامًا عن الحواسب الموضوعة في حواضن مغلقة، وستظن أنّ الوضع سيصبح أكثر تعقيدًا. في الواقع لن يحدث ذلك، حيث ستتعامل جيدًا مع راسبيري باي خلال عشر دقائق إن اتبعت ببساطةٍ خطوات هذا المقال. إذا كنت تمتلك عدة مبتدئي راسبيري باي Starter Kit والتي تباع عادةً مثل مجموعة متكاملة، فسيكون لديك كل ما يلزم لتبدأ العمل، وما عليك سوى تأمين شاشة حاسوب أو تلفازٍ قادرٍ على الاتصال بمنفذ HDMI لترى ما ينفّذه جهاز راسبيري باي؛ أما إذا لم تقتنِ بعد عدة مبتدئي راسبيري باي، فستحتاج إلى الأتي: جهاز راسبيري باي 4 نموذج B. مصدر تغذية كهربائي بمخرج USB-C: جهده 5 فولت وتياره 3 أمبير. ويُفضّل شراء مصدر التغذية الرسمي الخاص براسبيري باي كونه يتلاءم مع التبدلات السريعة لكمية الطاقة الكهربائية التي يتطلبها الجهاز. بطاقة ذاكرة من نوع microSD ثُبِّت عليها برنامج NOOBS، حيث تعمل بطاقة الذاكرة مثل مخزنٍ دائمٍ لراسبيري باي، وتحتوي على كل الملفات التي تخزنها والبرامج التي تثبتها بما في ذلك نظام التشغيل. قد تكفي ذاكرة بحجم 8GB لتبدأ العمل، لكن ستساعدك ذاكرة بحجم 16GB على توسيع عملك لاحقًا، ومن الأفضل والأوفر من ناحية الوقت أن يأتي برنامج NOOBS مثبتًا مسبقًا على بطاقة الذاكرة. برنامج NOOBS هو اختصارٌ لبرنامج لجهاز جديد New Out-Of Box Software، حيث تُوضع نسخةٌ منه على بطاقة ذاكرة مهيأة حديثًا ليتولى شؤون تنصيب نظام التشغيل المناسب بمجرد وضعها في راسبيري باي وتشغيله. راجع الملحق A للحصول على إرشادات حول تنصيب NOOBS على بطاقة ذاكرة فارغة إن لم يكن مثبتًا مسبقًا. فأرة ولوحة مفاتيح بمنفذ USB يتيحان لك التحكم بجهاز راسبيري باي، حيث ستعمل أية تجهيزاتٍ تأتي بمنفذ USB مع راسبيري باي بصورةٍ جيدة، سواءً السلكية منها أو اللاسلكية، لكن قد تستجر بعض لوحات المفاتيح المخصصة للألعاب، والتي تأتي مع أضواءٍ ملونة كميةً كبيرةً من الطاقة الكهربائية، وبالتالي لن يكون العمل معها موثوقًا. كابل من نوع Micro-HDMI لنقل الصوت والصورة من راسبيري باي إلى شاشة الحاسوب أو التلفاز، حيث يحمل أحد طرفيه وصلةً صغيرة الحجم من نوع micro-HDMI، بينما يحمل الطرف الآخر وصلة HDMI ذات حجمٍ قياسي لتوصيل الكابل مع جهاز العرض الذي تستخدمه. يمكنك كذلك استخدام كابل HDMI ذو طرفين قياسيين ومحوّل موائمة من HDMI إلى micro-HDMI، من أجل وصل الكابل مع راسبيري باي؛ وإذا كانت الشاشة التي تستخدمها لا تدعم HDMI، فاستخدم محوّل موائمة من micro-HDMI إلى منفذ العرض الرقمي DVI-D أو منفذ العرض الكلاسيكي VGA؛ ومن أجل توصيل راسبيري باي إلى جهاز تلفاز قديم يستخدم إشارة الفيديو المركبة composite video أو يمتلك مقبس SCART، استخدم كابل TRRS صوت وصورة، بقطر 3.5 ميليمتر مشابه لمقبس سماعات الرأس التي تتصل مع الهواتف المحمولة. تستطيع استخدام راسبيري باي بأمان دون حاضنة، لكن تجنب وضعه على سطحٍ معدني ناقلٍ للتيار الكهربائي، لما قد يسبب قصرًا في إحدى داراته، حيث يُجنّبك استخدام حاضنةٍ هذه المشكلة، لكنه يبقى أمرًا اختياريًا. تحتوي مجموعة أدوات المبتدئين على أية حال حاضنة راسبيري باي الرسمية، كما ستجد العديد من الحاضنات التي تنتجها أطرافٌ أخرى في نقاط البيع المختصة. ستحتاج أيضًا إلى كبل شبكة network cable إن كنت تنوي وصل راسبيري باي إلى شبكة سلكية محلية بدلًا من الاتصال اللاسلكي WiFi، وبالطبع لا بدّ من وصل الطرف الآخر للكابل بمحوّل شبكة network switch أو موجِّه router. لن تحتاج إلى الكابل إن قررت استخدام الاتصال اللاسلكي WiFi، لكنك ستحتاج إلى اسم الشبكة اللاسلكية التي ستنضم إليها مع مفتاحها أو عبارة المرور الصحيحة. إعداد العتاد الصلب Hardware أخرج راسبيري باي من صندوقه، وحاول إمساكه من حوافه بدلًا من المساحات الفارغة التي قد تجدها ضمن اللوحة؛ والمقصود هنا المساحات التي لا تحتوي على عناصر إلكترونية أو رقاقات، فمن الأفضل اتباع هذا الأسلوب من الآن وصاعدًا. انتبه جيدًا عندما تقترب من أرجل المنصات Header pins؛ فأي التواءٍ في أحدها قد يوقعك بمشاكلٍ أبسطها صعوبة التعامل مع أية طرفياتٍ قد تضيفها إلى راسبيري باي عبر هذه المنصات، وأسوؤها هو حدوث قِصَر في أحد الدارات وتضرر الجهاز بالكامل. فعلى الرغم من متانة راسبيري باي، لا يعني ذلك إطلاقًا أنه لا يتضرر. تجميع الحاضنة إذا قررت وضع جهازك في حاضنة لمزيدٍ من الحماية، فستكون خطوتك الأولى هي تثبيت لوحة راسبيري باي ضمنها؛ وفي حال استخدمت حاضنة راسبيري باي الرسمية، فابدأ بفصلها إلى قطعتين، القاعدة الحمراء والغطاء الأبيض. ومن أجل تجميع الحاضنة، اتبّع الخطوات التالية: 1- احمل القاعدة بحيث تصبح النهاية المرتفعة على يسارك والمنخفضة إلى يمينك. 2- أمسك اللوحة (دون وجود بطاقة الذاكرة) من غلافي منفذي USB ومحوّل الشبكة المحلية، ثم أَمِل اللوحة قليلًا، وادفع بمنافذ USB-C وmicro-HDMI وAV إلى الفتحات المقابلة ضمن القاعدة الحمراء. أنزل الناحية الأخرى التي تمسكها رويدًا رويدًا لتأخذ مكانها. 3- ثبّت مشابك الغطاء الأبيض في فتحتهما على يسار القاعدة فوق مدخل بطاقة الذاكرة، ثم اضغط الطرف الآخر للغطاء بلطفٍ نحو الأسفل حتى تسمع صوت الإطباق. إدخال وتوصيل بطاقة الذاكرة microSD اقلب حاضنة راسبيري باي إذا كنت تستخدم واحدة، ثم ازلق بطاقة الذاكرة ضمن حاضنتها، بحيث تكون لصاقة عنوان البطاقة بالاتجاه المعاكس للوحة راسبيري باي، ولن تدخل البطاقة على أية حال إلا باتجاهٍ واحدٍ وينبغي أن تدخل بسلاسةٍ دون ضغط زائد. ستنزلق بطاقة الذاكرة داخل الحاضنة وتتوقف دون صوت إقفال. إذا أردت إزالة البطاقة لاحقًا، فماعليك سوى إمساك نهايتها الظاهرة وسحبها بلطفٍ نحو الخارج، كما يجب الانتباه إلى ضرورة دفع البطاقة للداخل بلطفٍ أولًا لتحريرها إذا كنت تستخدم نماذجًا قديمة من راسبيري باي، لكن هذا غير ضروري أبدًا في النموذجين باي 3 و4. وصل لوحة المفاتيح والفأرة صِل كابل لوحة المفاتيح من نوع USB إلى أي منفذٍ من منافذ USB الأربعة، سواءً من النوع 2.0، أو 3.0 الموجودة على لوحة باي. إذا استخدمت الحاضنة الرسمية لباي، فستجد إشارةً في مؤخرتها إلى منفذ USB المخصص للفأرة، وإذا لم تستخدم الحاضنة فصِل الفأرة بأيٍ من منافذ USB الشاغرة. سيدخل مقبس USB للفأرة أو لوحة المفاتيح في المنفذ الموجود على باي بكل سهولة ودون ضغطٍ زائد، لكن إذا واجهتك صعوبةٌ في إدخاله، فلا بد من وجود مشكلةٍ ما، لذلك تأكد قبل كل شيء من جهة مقبس USB إذا كان اتجاه دخوله صحيحًا، وإلا فغيّر الاتجاه. وصل شاشة العرض صِل نهاية كابل micro-HDMI صغيرة الحجم بمنفذ micro-HDMI على راسبيري باي بجوار مدخل التغذية USB-C، وصِل الطرف الآخر ذو القياس المعياري بشاشة العرض. إذا اشتملت شاشتك على أكثر من منفذ HDMI، فتذكر رقم المنفذ الذي وصلت به راسبيري باي، لأنك ستُضطر إلى تغيير دخل الشاشة إلى هذا المنفذ وإلا لن ترى شيئًا. إذا لم ترى رقمًا بجانب المنفذ، فبدِّل منافذ دخل الشاشة عن طريق جهاز التحكم الخاص بها واحدًا تلو الآخر حتى ترى راسبيري باي. التوصيل الاختياري لراسبيري باي بكابل الشبكة المحلية Ethernet استخدم كابل شبكة محلية (كابل إيثرنت Ethernet) مزوّد بمقبس RJ45 في كلا الطرفين، ثم ادفع أحد النهايتين ضمن منفذ الشبكة السلكية في راسبيري باي، بحيث يكون الظفر البلاستيكي للمقبس نحو الأسفل حتى تسمع صوت القفل. ولنزع الكابل، اضغط الظفر البلاستيكي نحو الأعلى، ثم اسحب الكابل نحو الخارج بلطف. صِل الطرف الآخر للكابل إلى أي منفذٍ حر في موزع الشبكة السلكية network hub أو في الموجّه router بنفس الطريقة. وصل التغذية الكهربائية إلى راسبيري باي هي الخطوة الأخيرة في عملية التركيب. لا تُوصل التغذية الكهربائية حتى تكون مستعدًا لإعداد برمجيات الجهاز، حيث لا يملك راسبيري باي مفتاحًا لفصل ووصل الطاقة، وسيعمل بمجرد تغذيته بالكهرباء. صِل بداية مقبس USB-C لعلبة التغذية بالمنفذ المقابل له على راسبيري باي، حيث ينبغي أن يدخل المقبس بسهولة ضمن المنفذ بكلا الوضعين. إذا جاءت علبة التغذية مع كابلٍ قابل للنزع، فتأكد أنّ طرفه الآخر متصلٌ بجسم مصدر التغذية. صِل علبة التغذية إلى مأخذ الطاقة الكهربائية ثم شغّله. عندها سيبدأ راسبيري باي العمل مباشرةً. إعداد نظام تشغيل راسبيري باي عليك إعداد برمجيات الجهاز وعلى رأسها نظام التشغيل الذي يتحكم بوظائف باي حتى يعمل جهازك جديًا، لهذا صُمم برنامج NOOBS، وهو اختصارٌ إلى برمجيات الجهاز الجديد New Out-Of-Box Software، ويُستخدم لجعل العملية سهلةً قدر الإمكان؛ حيث يعطيك البرنامج حرية الاختيار بين عدة أنظمة تشغيل لتثبيتها تلقائيًا بالنيابة عنك. وأفضل ما في الأمر أنك ستنجز ذلك بنقر الفأرة عدة مرات فقط. عند تشغيل الجهاز للمرة الأولى، أو عند إقلاعه تمهيديًا Boot بوجود نسخة حديثة التثبيت من برنامج NOOBS على بطاقة الذاكرة microSD، سترى شاشةً تحمل شعار راسبيري باي ونافذةً صغيرةً في الزاوية اليسارية العليا تشير إلى تقدم العملية، وبعد فترةٍ وجيزة قد تستمر دقيقة سترى للمرة الأولى بطاقة الذاكرة التي تحمل برنامج NOOBS كما تبدو في الصورة التالية: برنامج NOOBS دون تثبيت أي نظام تشغيل ستعرض عليك تلك القائمة أنظمة التشغيل الممكن تثبيتها على جهازك، حيث تضم هذه القائمة نظامي تشغيل معياريين، هما: راسبيان Raspbian: وهو إصدارٌ من نظام تشغيل ديبيان لينوكس Debian Linux مصُممٌ خصيصًا لراسبيري باي. ليبريليك LibreELEC: وهو نسخةٌ من برنامج Kodi Entertainment Centre. تستطيع كذلك تحميل أنظمة تشغيل أخرى إن كنت متصلًا بالشبكة السلكية أو عن طريق تفعيل الاتصال اللاسلكي WiFi إذا كان متوفرًا من خلال النقر على الأيقونة (Wifi networks (w ضمن شريط الأيقونات العلوي لقائمة NOOBS. و لتثبيت نظام التشغيل، انقر بزر الفأرة اليساري فوق الخيار Raspbian Full، سترى بعد ذلك أنّ لون الأيقونة (Install (i لم يعد رماديًا وهذا دليلٌ على أن نظام التشغيل الذي اخترته أصبح جاهزًا للتثبيت. انقر على الأيقونة (Install (i بزر الفأرة اليساري وسترى عندها رسالة تحذير تخبرك بأن تثبيت نظام التشغيل سيمحو أية بياناتٍ قد خزنتها سابقًا على بطاقة microSD، وتستثني بالطبع الرسالة برنامج NOOBS الذي يبقى على حاله. انقر "Yes" لتبدأ عملية التثبيت. قد يستغرق تثبيت النظام من 10 إلى 30 دقيقة بناءً على سرعة بطاقة الذاكرة microSD، حيث يسمح لك شريط التقدم أسفل النافذة بمتابعة حالة تثبيت النظام. سترى أيضًا شريط عرض يُظلِّل بعض ميزاته الأساسية (سنتعرف أكثر على هذا الشريط وعلى نظام التشغيل في المقال التالي المتمحور حول استثمار نظام تشغيل راسبيري باي)، وحالما ينتهي تثبيت النظام ستظهر لك نافذة تحمل الزر "OK"، عندها انقر على هذا الزر لتعيد إقلاع باي على نظام التشغيل الذي ثبتّه توًّا. قد تلاحظ أثناء الإقلاع الكثير من الأسطر النصية المعروفة باسم رسائل الإقلاع تمر على الشاشة. قد يستغرق الأمر دقيقةً أو دقيقتان عند إقلاع نظام التشغيل راسبيان للمرة الأولى، حيث يكيّف النظام نفسه ليستغل المساحة الفارغة من بطاقة الذاكرة microSD أفضل استغلال، وستلاحظ أنّ العملية ستكون أسرع بكثير خلال الإقلاعات اللاحقة. أخيرًا، سترى لبرهة نافذةً تحمل شعار راسبيري باي قبل أن يظهر لك سطح مكتب راسبيان Raspbian ومعالج إعداد النظام setup wizard. وهكذا يكون تثبيت نظام التشغيل على راسبيري باي قد اكتمل تمامًا وأصبح جاهزًا للإعداد؛ وهذا ما ستتعلمه في المقال التالي. ترجمة -وبتصرف- للفصل الثاني Getting started with your Raspberry Pi من كتاب The official Raspberry Pi Beginners Guide. اقرأ أيضًا المقال السابق: تعرف على جهاز راسبيري باي Raspberry Pi المدخل الشامل لتعلم علوم الحاسوب أنظمة التشغيل للمبرمجين
-
يمكن لرسوم JavaScript التعامل مع حالات لا يمكن أن تتعامل معها CSS، مثل التحرك على مسار معقد مختلف عن منحنيات بيزيه Bezier curves باستخدام دالة توقيت، أو رسوميات متحركة على لوحة رسم. استخدام الدالة setInterval يمكن إنجاز الرسوم المتحركة في صورة سلسلة من الإطارات، والتي تكون عادةً تغيرات صغيرةً في خصائص HTML/CSS، فعلى سبيل المثال: يؤدي تغيير قيمة الخاصية style.left من 0px إلى 100px إلى تحريك العنصر، وإذا زدنا هذه القيمة ضمن الدالة setInterval، فسيؤدي تغير مقداره 2px مع تأخير ضئيل، لتكرار العملية بمقدار 50 مرةً في الثانية، مما يجعل الحركة سلسةً وناعمةً، ويُتَّبع هذا الأسلوب في السينما، فعرض 24 إطارًا في الثانية يجعل الصورة سلسةً. إليك الشيفرة المجردة pseudo-code للفكرة: let timer = setInterval(function() { if (animation complete) clearInterval(timer); else increase style.left by 2px }, 20); // تغير بمقدار 2 بكسل بتأخير 20 ميلي ثانية يعطي 50 إطار في الثانية وهذا مثال أكثر تعقيدًا: let start = Date.now(); // تذكر وقت البدء let timer = setInterval(function() { // كم مضى من الوقت منذ البداية let timePassed = Date.now() - start; if (timePassed >= 2000) { clearInterval(timer); // أنهي الحركة بعد ثانيتين return; } // ارسم إطار الحركة في اللحظة الحالية draw(timePassed); }, 20); // عندما يتغير الوقت بين 0 و2000 ميلي ثانية // تتغير قيمة الخاصية من 0 بكسل إلى 400 بكسل function draw(timePassed) { train.style.left = timePassed / 5 + 'px'; } إليك المثال النموذجي التالي: شيفرة الملف index.html: <!DOCTYPE HTML> <html> <head> <style> #train { position: relative; cursor: pointer; } </style> </head> <body> <img id="train" src="https://js.cx/clipart/train.gif"> <script> train.onclick = function() { let start = Date.now(); let timer = setInterval(function() { let timePassed = Date.now() - start; train.style.left = timePassed / 5 + 'px'; if (timePassed > 2000) clearInterval(timer); }, 20); } </script> </body> </html> وستكون النتيجة: See the Pen JS-p3-05-JavaScript-animations -ex1 by Hsoub (@Hsoub) on CodePen. استخدام الدالة requestAnimationFrame لنفترض تنفيذ عدة حركات انتقالية معًا. إذا شغّلنا هذه الرسوم منفصلةً فسيعيد المتصفح -وعلى الرغم من أنّ لكل حركة دالة (setInterval(..., 20 خاصةً- رسم الصور بمعدل أكبر بكثير من 20 ميلي ثانية، ويحدث هذا لوجود أوقات بداية مختلفة لكل حركة، وبالتالي سيختلف تكرار التغيّر الذي ضبطناه عند 20 ميلي ثانية بالنسبة لكل حركة، وهكذا سنكوِّن حالات تشغيل مستقلةً لكل حركة انتقالية تتكرر كل 20 ميلي ثانية، وبكلمات أخرى انظر الشيفرة التالية: setInterval(function() { animate1(); animate2(); animate3(); }, 20) والتي ستكون أخفّ من ناحية التنفيذ على المتصفح من الشيفرة: setInterval(animate1, 20); // حركة انتقالية مستقلة setInterval(animate2, 20); // في أماكن مختلفة من السكربت setInterval(animate3, 20); ينبغي تجميع عمليات الرسم المستقلة لتسهيل الأمر على المتصفح، ولتخفيف الحِمل على وحدة المعالجة إلى جانب إظهار حركة أكثر نعومةً. تذكر دائمًا أنه يجب ألا نشغل عملية الرسم كل 20 ميلي ثانية، لأنها قد تزيد حمولة وحدة المعالجة، أو لوجود أسباب لتقليل عملية إعادة الرسم، مثل الحالة التي تكون ضمن نافذة مخفية للمتصفح. ولتمييز ذلك في JavaScript سنستخدم ميزةً تُدعى توقيت الحركة Animation timing، والتي تزوّدنا بالدالة requestAnimationFrame التي تتعامل مع هذه الأمور وأكثر، وإليك صيغة استخدامها: let requestId = requestAnimationFrame(callback) تجدول الدالة requestAnimationFrame دالة الاستدعاء callback للعمل في أقرب وقت يريد فيه المتصفح تنفيذ الحركة، فإذا نفَّذنا أي تغييرات على العناصر ضمن الدالة callback، فستُجمّع مع غيرها من دوال الاستدعاء التي تجدولها الدالة requestAnimationFrame ومع رسوميات CSS، وهكذا سيعيد المتصفح الحسابات الهندسية، ثم يعيد الرسم مرةً واحدةً بدلًا من مرات متعددة. يمكن استخدام القيمة requestId التي تعيدها الدالة requestAnimationFrame في إلغاء الاستدعاء: // ألغ تنفيذ الاستدعاءات المجدولة cancelAnimationFrame(requestId); لدالة الاستدعاء callback وسيط واحد، وهو الوقت الذي انقضى منذ بداية تحميل الصفحة مقدرًا بالميكروثانية، والذي يمكن الحصول عليه باستدعاء التابع performance.now. يُنفَّذ الاستدعاء callback مبكرًا إلا في حالة التحميل الزائد للمعالج، أو عندما تقارب بطارية الحاسوب المحمول على النفاد أو لأسباب مشابهة، وتظهر الشيفرة التالية الوقت المستغرق خلال مرات التنفيذ العشرة الأولى للدالة requestAnimationFrame، وهو عادةً بين 10-20 ميلي ثانية: <script> let prev = performance.now(); let times = 0; requestAnimationFrame(function measure(time) { document.body.insertAdjacentHTML("beforeEnd", Math.floor(time - prev) + " "); prev = time; if (times++ < 10) requestAnimationFrame(measure); }) </script> See the Pen JS-p3-05-JavaScript-animations -ex2 by Hsoub (@Hsoub) on CodePen. الرسومات المتحركة المهيكلة Structured animation سننشئ الآن دالةً أكثر عموميةً مبنيةً على الدالة requestAnimationFrame: function animate({timing, draw, duration}) { let start = performance.now(); requestAnimationFrame(function animate(time) { // يتحرك الزمنين 0 و1 let timeFraction = (time - start) / duration; if (timeFraction > 1) timeFraction = 1; // حساب الحالة الراهنة للرسوم المتحركة let progress = timing(timeFraction) draw(progress); // تنفيذ الرسم if (timeFraction < 1) { requestAnimationFrame(animate); } }); } تقبل الدالة animate ثلاثة معاملات تصف الحركة، وهي: duration: الزمن الكلي لتنفيذ الحركة مقدرًا بالميلي ثانية. (timing(timeFraction: دالة توقيت تشابه الخاصية transition-timing-function في CSS، وتعطي نسبة الوقت الذي انقضى (0 عند البداية و1 عند النهاية)، وتعيد ما يدل على اكتمال الحركة، مثل الإحداثي y عند رسم منحني بيزيه، ولنتذكر أن الدالة الخطية تعني أن الحركة ستتقدم بانتظام وبالسرعة ذاتها: function linear(timeFraction) { return timeFraction; } وسيبدو الرسم البياني للدالة الخطية كالتالي: وهي مشابهة تمامًا للخاصية transition-timing-function، وسنرى لاحقًا بعض أشكال الاستخدام الأخرى. (draw(progress: وهي الدالة التي تأخذ معاملًا هو مقدار اكتمال الحركة وترسمه، وتشير القيمة progress=0 إلى حالة بداية الحركة، بينما تشير القيمة progress=1 إلى حالة النهاية، فهي الدالة التي ترسم الحركة فعليًا، إذا يمكنها نقل عنصر مثلًا: function draw(progress) { train.style.left = progress + 'px'; } أو تنفيذ أي شيء آخر، وبالتالي يمكننا تحريك أي شيء بالطريقة التي نريد، لنحرّك العنصر width من 0 حتى 100% باستخدام هذه الدالة: شيفرة الملف animate.js: unction animate({duration, draw, timing}) { let start = performance.now(); requestAnimationFrame(function animate(time) { let timeFraction = (time - start) / duration; if (timeFraction > 1) timeFraction = 1; let progress = timing(timeFraction) draw(progress); if (timeFraction < 1) { requestAnimationFrame(animate); } }); } شيفرة الملف index.html: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <style> progress { width: 5%; } </style> <script src="animate.js"></script> </head> <body> <progress id="elem"></progress> <script> elem.onclick = function() { animate({ duration: 1000, timing: function(timeFraction) { return timeFraction; }, draw: function(progress) { elem.style.width = progress * 100 + '%'; } }); }; </script> </body> </html> ستكون النتيجة كالتالي: وإليك الشيفرة المستخدمة: animate({ duration: 1000, timing(timeFraction) { return timeFraction; }, draw(progress) { elem.style.width = progress * 100 + '%'; } }); يمكننا بهذا الأسلوب تنفيذ أي دوال توقيت ورسم على خلاف CSS، ولا ترتبط دوال التوقيت بمنحني بيزيه فقط، كما يمكن للدالة draw أن تتخطى الخصائص إلى إنشاء عناصر جديدة لرسوميات نحتاجها، مثل الألعاب النارية. دوال التوقيت اطلعنا في الفقرات السابقة على أبسط الدوال وهي الدالة الخطية، لنرى الآن بعض الدوال الأخرى، حيث سنجرب بعض الحركات الانتقالية باستخدام دوال توقيت مختلفة. دالة القوة من الدرجة n يمكن استعمال progress بدلالة القوة من الدرجة n لتسريع الحركة، مثل الدالة التربيعية (أي من الدرجة 2): function quad(timeFraction) { return Math.pow(timeFraction, 2) } إليك الرسم البياني: لترى النتيجة انقر على الشكل التالي: كما يمكنك استعمال الدالة التكعيبية (من الدرجة 3)، وسترى أن سرعة الحركة ستزداد بزيادة درجة القوة، إليك نموذجًا تكون فيه progress من الدرجة 5: الدالة المثلثية القطعية arc صيغة الدالة: function circ(timeFraction) { return 1 - Math.sin(Math.acos(timeFraction)); } الرسم البياني: المثال النموذج: دالة إطلاق السهم back عند اطلاق السهم bow shooting فسنسحب وتر القوس ثم نحرره، وخلافًا للدالتين السابقتين، ستعتمد الدالة على معامل إضافي x هو ثابت المرونة elasticity coefficient، والذي يُعرِّف المسافة التي نسحب بها وتر القوس: function back(x, timeFraction) { return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x) } الخط البياني للدالة عندما x = 1.5: المثال النموذجي عند نفس القيمة للمعامل x: دالة الارتداد bounce عندما نرمي كرةً فستسقط للأسفل وترتد عدة مرات ثم تتوقف. تسلك الدالة bounce هذا السلوك تمامًا لكن بترتيب معكوس، حيث يبدأ الارتداد مباشرةً، وتستخدم هذه الدالة بعض الثوابت الخاصة: function bounce(timeFraction) { for (let a = 0, b = 1, result; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } } } إليك نموذجًا يستخدم دالة الإرتداد: دالة الحركة المرنة elastic إليك دالةً أخرى "مرنةً" تقبل معاملًا إضافيًا x يضبط المجال الابتدائي للحركة: function elastic(x, timeFraction) { return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction) } الخط البياني للدالة عندما x=1.5: إليك نموذجًا عن استخدام الدالة: الدوال بترتيب معكوس تعرفنا حتى اللحظة على دوال التوقيت التي تُعرف بتحويلات الدخول السهل easeIn، لكننا قد نحتاج أحيانًا إلى عرض الحركة بترتيب معكوس. تُنفَّذ هذه الحركات بالتحويل الذي يُعرف باسم الخروج السهل easeOut. التحويل easeOut تُوضع الدالة timing في هذا التحويل ضمن المُغلِّف timingEaseOut: timingEaseOut(timeFraction) = 1 - timing(1 - timeFraction) بعبارة أخرى لدينا دالة التحويل makeEaseOut التي تقبل دالة توقيت نظاميةً وتعيد المُغلّف الذي يحيط بها: // accepts a timing function, returns the transformed variant function makeEaseOut(timing) { return function(timeFraction) { return 1 - timing(1 - timeFraction); } } يمكن على سبيل المثال اختيار الدالة bounce التي شرحناها سابقًا وتطبيقها: let bounceEaseOut = makeEaseOut(bounce); وهكذا لن تكون الحركة الارتدادية في بداية الحركة بل في نهايتها، وستبدو الحركة أفضل: شيفرة الملف style.css: #brick { width: 40px; height: 20px; background: #EE6B47; position: relative; cursor: pointer; } #path { outline: 1px solid #E8C48E; width: 540px; height: 20px; } شيفرة الملف index.html: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> <script src="https://js.cx/libs/animate.js"></script> </head> <body> <div id="path"> <div id="brick"></div> </div> <script> function makeEaseOut(timing) { return function(timeFraction) { return 1 - timing(1 - timeFraction); } } function bounce(timeFraction) { for (let a = 0, b = 1, result; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } } } let bounceEaseOut = makeEaseOut(bounce); brick.onclick = function() { animate({ duration: 3000, timing: bounceEaseOut, draw: function(progress) { brick.style.left = progress * 500 + 'px'; } }); }; </script> </body> </html> وستكون النتيجة: سنرى هنا كيف سيغيّر التحويل سلوك الدالة، إذ ستعرَض التأثيرات الموجودة في بداية الحركة -مثل الارتداد- في النهاية، ففي الشكل التالي ستجد الارتداد الاعتيادي باللون الأحمر، والارتداد وفق تحويل الخروج السهل باللون الأزرق. الارتداد الاعتيادي: يرتد الكائن عند القاع، ثم يقفز بحدّة إلى القمة في النهاية. باستخدام easeOut الخروج السهل: يقفز أولًا إلى القمة ثم يرتد هناك. التحويل easeInOut يمكن أن نُظهر التأثير المطلوب في بداية ونهاية الحركة معًا، ويُدعى هذا التحويل بالدخول والخروج السهل easeInOut. سنحسب حالة الحركة باعتماد تابع توقيت ما كالتالي: if (timeFraction <= 0.5) { // نصف الحركة الأول return timing(2 * timeFraction) / 2; } else { // النصف الثاني للحركة return (2 - timing(2 * (1 - timeFraction))) / 2; } شيفرة المُغلِّف: function makeEaseInOut(timing) { return function(timeFraction) { if (timeFraction < .5) return timing(2 * timeFraction) / 2; else return (2 - timing(2 * (1 - timeFraction))) / 2; } } bounceEaseInOut = makeEaseInOut(bounce); إليك مثالًا نموذجيًا: شيفرة الملف style.css: #brick { width: 40px; height: 20px; background: #EE6B47; position: relative; cursor: pointer; } #path { outline: 1px solid #E8C48E; width: 540px; height: 20px; } شيفرة الملف index.html: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> <script src="https://js.cx/libs/animate.js"></script> </head> <body> <div id="path"> <div id="brick"></div> </div> <script> function makeEaseInOut(timing) { return function(timeFraction) { if (timeFraction < .5) return timing(2 * timeFraction) / 2; else return (2 - timing(2 * (1 - timeFraction))) / 2; } } function bounce(timeFraction) { for (let a = 0, b = 1, result; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } } } let bounceEaseInOut = makeEaseInOut(bounce); brick.onclick = function() { animate({ duration: 3000, timing: bounceEaseInOut, draw: function(progress) { brick.style.left = progress * 500 + 'px'; } }); }; </script> </body> </html> وستكون النتيجة: يدمج التحويل كائنين رسوميين معًا، وهما التحويل easeIn (الاعتيادي) في النصف الأول للحركة، والتحويل easeOut (المعكوس) للنصف الثاني. سنرى التأثير بوضوح عند الموازنة بين التحويلات الثلاث easeIn وeaseOut وeaseInOut عند تطبيقها على دالة التوقيت circ: easeIn: باللون الأحمر. easeOut: باللون الأخضر. easeInOut: باللون الأزرق. كما نرى طبقنا على النصف الأول من الحركة easeIn، وعلى النصف الآخر easeOut، لذا ستبدأ الحركة وتنتهي بنفس التأثير. دالة draw أكثر تميزًا يمكننا القيام بأكثر من مجرد تحريك العنصر، وكل ما علينا فعله هو كتابة شيفرة مناسبة للدالة draw، إليك طريقةً لإظهار ارتداد أثناء كتابة نص مثلًا: شيفرة الملف style.css: textarea { display: block; border: 1px solid #BBB; color: #444; font-size: 110%; } button { margin-top: 10px; } شيفرة الملف index.html: <!DOCTYPE HTML> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> <script src="https://js.cx/libs/animate.js"></script> </head> <body> <textarea id="textExample" rows="5" cols="60">He took his vorpal sword in hand: Long time the manxome foe he sought— So rested he by the Tumtum tree, And stood awhile in thought. </textarea> <button onclick="animateText(textExample)">Run the animated typing!</button> <script> function animateText(textArea) { let text = textArea.value; let to = text.length, from = 0; animate({ duration: 5000, timing: bounce, draw: function(progress) { let result = (to - from) * progress + from; textArea.value = text.substr(0, Math.ceil(result)) } }); } function bounce(timeFraction) { for (let a = 0, b = 1, result; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } } } </script> </body> </html> وستكون النتيجة كالتالي: خلاصة يمكن أن تساعدك JavaScript في تنفيذ الرسوميات التي لا تستطيع CSS التعامل معها، أو تلك التي تتطلب تحكمًا دقيقًا. تّنفّذ حركات JavaScript باستخدام التابع المدمج requestAnimationFrame الذي يسمح بإعداد دالة استدعاء تُشغَّل عندما يحضّر المتصفح نفسه لعملية إعادة الرسم Repaint. لن تُنفَّذ عملية إعادة الرسم إطلاقًا عندما تكون الصفحة في الخلفية، وبالتالي لن تعمل دالة الاستدعاء وسيُعلّق تنفيذ الحركة، ولن يكون هناك استهلاك للموارد. إليك الدالة المساعدة animate التي تحضّر معظم الرسوميات المتحركة التي تحتاجها: function animate({timing, draw, duration}) { let start = performance.now(); requestAnimationFrame(function animate(time) { // timeFraction goes from 0 to 1 let timeFraction = (time - start) / duration; if (timeFraction > 1) timeFraction = 1; // calculate the current animation state let progress = timing(timeFraction); draw(progress); // draw it if (timeFraction < 1) { requestAnimationFrame(animate); } }); } الخيارات هي: duration: الزمن الكلي للحركة مقدرًا بالميلي ثانية. timing: الدالة التي تحسب مقدار تقدم الحركة، حيث تقبل الدالة قيمًا زمنيةً هي نسبة بين 0 و1، وتعيد مقدار تقدم العملية. draw: الدالة التي ترسم الحركة. وبالطبع يمكن تحسين هذه الدوال وإضافة العديد من الأمور الأخرى، لكن لا يتكرر استخدام رسوميات JavaScript كثيرًا، فهي تستخدَم لإظهار شيء مهم لا لأمور تقليدية، لذا يمكنك إضافة الميزة التي تريدها عند الحاجة. يمكن أن تستخدم JavaScript أي دوال توقيت، وقد غطينا الكثير منها في هذا الفصل وطبقنا عليها تحويلات عدةً، إذًا لسنا مقيدين بمنحنيات بيزيه كما هي الحال في CSS. يمكننا استخدام draw لتحريك أي شيء وليس خصائص CSS فقط. مهام لإنجازها 1. تحريك كرة مرتدة أنشئ كرةً ترتد كما في المثال التالي: افتح المثال في بيئة تجريبية. الحل لجعل الكرة ترتد، سنسنتعمل الخاصية top والخاصية position:absolute مع الكرة والخاصية position:relative مع الملعب، ويكن إحداثيات أرضية الملعب هي field.clientHeight. تشير الخاصية top إلى بداية أعلى الملعب لذا يجب أن تتغير من 0 إلى field.clientHeight - ball.clientHeight وهو أدنى موضع يمكن أن تنخفض إليه حافة الكرة العلوية. يمكن تطبيق تأثير الارتداد باستعمال دالة التوقيت bounce في الوضع easeOut. إليك الشيفرة النهاية الناتجة: let to = field.clientHeight - ball.clientHeight; animate({ duration: 2000, timing: makeEaseOut(bounce), draw(progress) { ball.style.top = to * progress + 'px' } }); افتح الحل في بيئة تجريبية. 2. حرك الكرة المرتدة إلى اليمين اجعل الكرة ترتد إلى اليمين كالتالي: اكتب الشيفرة بحيث تكون مسافة الانتقال إلى اليمين هي 100px. الحل احتجنا في التمرين السابق إلى تحريك خاصية واحدة فقط، بينما سنحتاج في هذا التمرين إلى تحريك خاصية إضافية هي elem.style.left. نريد تغيير الاحداثيات الأفقية للكرة بزيادتها تدريجيًا نحو اليمين أثناء السقوط، لذا سنضيف حركة إضافة anumate يمكن أن نستعمل معها دالة التوقيت linear ولكن تبدو makeEaseOut(quad) أفضل بكثير. إليك الشيفرة النهاية الناتجة: let height = field.clientHeight - ball.clientHeight; let width = 100; // animate top (bouncing) animate({ duration: 2000, timing: makeEaseOut(bounce), draw: function(progress) { ball.style.top = height * progress + 'px' } }); // animate left (moving to the right) animate({ duration: 2000, timing: makeEaseOut(quad), draw: function(progress) { ball.style.left = width * progress + "px" } }); افتح الحل في بيئة تجريبية. ترجمة -وبتصرف- للفصل JavaScript Animation من سلسلة The Modern JavaScript Tutorial اقرأ أيضًا المقال السابق: إنشاء رسوم متحركة باستخدام CSS النسخة العربية الكاملة لكتاب: التحريك عبر CSS iframe { border: 1px solid #e7e5e3 !important; width: 100%; } iframe div#path { margin: auto; }
-
لربما تكون قد سمعت عبارات مثل هذه: "ليس لدينا متسع من الوقت لاختبار (النسخة أو مسار المستخدم أو التصميم أو الميزات…) الآن، سنصدر المنتج حاليًا ونتابع لاحقًا ردود أفعال العملاء" إن عَملت سابقًا على تجربة مستخدم UX. يبني المطورون منتجاتهم بسرعة أكبر مما مضى، ويُفترض من المصممين متابعتهم خطوةً بخطوة. وعلى الرغم من فهم المصممين لضرورة اختبار تصاميمهم في قرارة أنفسهم، تتجاهل معظم المنظمات المعنية هذا الأمر، إذ ينبغي على المصممين التنقل المضني في دوائر المعطيات والأفكار حول مواضيع تصاميمهم للوصول إلى رصيد فعال من الأبحاث، ومع ذلك يباشرون أعمالهم بكمٍ غير مثالي من البيانات المطلوبة. عَمِلتُ -قبل أن أكون مديرًا تنفيذيًا وأحد مؤسسي شركة Maze- رئيسًا لمصممي تجربة مستخدم لعملاء مثل McKinsey وRocket Internet وPSG، وأدركت منذ ذلك الوقت حاجة المصممين إلى إطار عمل يُعتمد عليه، بحيث يساعد في تحديد المدخلات بسرعة في مرحلة مبكرة، وغالبًا أثناء عملية التصميم، وهكذا جمّعنا كل هذه الأفكار معًا في Maze وسنطلعكم على تفاصيل ذلك في هذه المقالة. إطار عمل الاختبار السريع: تعريفه وفوائده طورنا إطار عمل الاختبار السريع Rapid Testing Framework لنمنح فرق التصميم والإنتاج والتسويق أفكارًا قابلة للتطبيق في مختلف المراحل. فكل قرار يُتخذ أثناء عملية تطوير المنتج من اختيار وتطوير الميزات الصحيحة وتصميم مسار المستخدم User flow أو كتابة نسخة لتجربة المستخدم هي قرارات يمكن اختبارها ولا بدّ من تقييمها من قبل المستخدمين. سنتحدث في الفقرات القادمة عمّا تقدمه الاختبارات لفريق التصميم. الحصول على بعض الإضاءات خلال عملية التصميم تبدأ عملية التصميم قبل أن تبدأ حتى بتنفيذ أية رسومات أو مخططات، وغالبًا ما تبدأ عندما يناقش الفريق الافتراضات المطروحة حول ما يتوقعه المستخدم وما يحتاجه. يتيح لك إشراك المستخدمين والحصول على آرائهم باكرًا خلال عملية التصميم، إمكانية الغوص في تفاصيل المشاكل وتفادي الخوض في الكثير من الحلول المتوقعة، فكلما سعيت في إشراك مستخدمي منتجك مبكرًا، ستحصل على آرائهم وتجني الفوائد المرجوة منها في تحسين منتجك بسرعة أكبر. وكما تشير Helen Tsvirinka -مصممة المنتجات في شركة Shopify-، فإن الاختبارات سترفع عائدات استثمارك كثيرًا موازنةً باعتمادك على الافتراضات فقط. تقييم التجارب بمساعدة المستخدمين قبل إنهاء المنتج إن لم تعرض منتجك أثناء بنائه على المستخدمين، فسينتهي الأمر بفريقك إلى هدر الوقت والموارد على بناء ميزات عديمة الفائدة أو ميزات قد لا تعمل، فإن كنت ستطلع على آراء مستخدميك بعد أن تطرح منتجك، فستخسر نتيجة سباق بأكمله. إنّ وضع ما تبنيه في أيدي مستخدميك باكرًا وفي مرحلة التصميم عادةً، يعني أنك قادر على تقييم تجربة العملاء قبل إنهاء منتجك. تسمح الاختبارات لك ولفريقك أن تنتقل من مرحلة الفكرة إلى التصميم النهائي وأنت واثق أنك عالجت مسألةً واقعيةً ووجدت لها حلًا فعالًا. بناء قاعدة معرفية حول ما ينفع وما لا ينفع كلما اختبرت منتجك أكثر أثناء عملية التصميم، حزت على معلومات أكثر عن مستخدميك ومتطلباتهم وما ينفع منتجك وما لا ينفعه. وعلى الرغم من كم البيانات التي ستقدمه الاختبارات لك ولفريقك، فقد تكون هذه البيانات موزعةً داخل منظومة العمل مثل غيرها من الإضاءات والأفكار التي تحملها الفرق المختلفة أو أفراد الفريق الواحد. لن تمنحك الاختبارات السريعة فوائدًا على المدى القصير فقط عندما تسمح لك ببناء تجربة مستخدم مميزة، لكنها قيِّمة على المدى الطويل أيضًا، لأنها تزودك بطريقة معيارية لتوثيق ما تجده نافعًا وما تجده خلاف ذلك. كيف تستخدم إطار عمل الاختبار السريع في اختبار أي تجربة مستخدم لا تقتصر فائدة إطار عمل الاختبار السريع Rapid Testing على فريق التصميم، بل على عمل المنتج الأساسي للمؤسسة، إذ يتيح لكل طواقم العمل من المسوقين إلى المديرين تقييم عملياتهم، وبالتالي التقليل من الوقت المهدور على بناء ما هو خاطئ. استخدم الحلقة التكرارية Input-Objective-Test-Analysis واختصارًا IOTA في اتخاذ قراراتك، وإليك طريقة عملها: المدخلات Input المدخل هو الحالة التي تحتاج فيها إلى اتخاذ قرار، مثل الميزة التي نصممها أو النسخة التي سنستخدمها أو النموذج الأولي الذي سنبني عليه. وإليك بعض التساؤلات التي تدل على المدخلات القابلة للاختبار: ما هي الوظيفة التي تقدمها الميزة لمستخدميك؟ هل من السهل التنقل ضمن مسار المستخدم؟ هل التصميم سهل الاستخدام؟ هل النسخة المطروحة جاهزة لهذا العمل؟ الهدف Objective سيكون تحديد الهدف هو الخطوة التالية لتحديد المدخلات، وهو النقطة التي ستكون عند بلوغها قادرًا على تقييم القرار الذي اتخذته، فقد يكون هدفك مثلًا هو إكمال 80% على الأقل من المشاركين في الاختبار كل المهام، عند اختبار قابلية استخدام تصميم. إن لم تحقق الهدف من أول اختبار، فكرر العملية مجددًا حتى ينجح 80% من المشاركين في إتمام جميع المهام وفق التصميم الذي تبنيه. فإما أن تنتقل في نهاية الأمر إلى المرحلة التالية من العملية، أو أن تعود إلى لوحة الرسم لتتعمق أكثر في تفاصيل التجربة التي تقدمها لمستخدميك. الاختبار Test يأتي تاليًا تحديد طريقة العمل المثلى وفقًا للهدف الذي اعتمدته، فإن كنت ستختبر مثلًا الانطباع الأول عن علامتك التجارية الجديدة وملاحظة العملاء لوجودها، فسيكون اختبار الثواني الخمسة أو مقابلة تسأل أسئلةً فضفاضةً مناسبين جدًا. التحليل Analysis ستقيّم أخيرًا نتائج الاختبار وتوازنها مع الهدف المنشود، فإن حققت نتائجك الهدف، فستتمكن من الانتقال إلى الخطوة التالية في عملية التصميم. بينما عليك العودة إلى الحل الذي اعتمدته لتصميمك إن لم تكن النتائج مقنعة، ثم تلافي العيوب حتى تصل إلى هدفك أو أن تلغي القرار باعتماد هذا التصميم. مثال عن استخدام إطار العمل IOTA لنلق نظرةً على تجربتنا في Maze عندما حاولنا إنشاء تطبيق يستخدم صفحةً لعرض حالات تصميمية قد تلهم المصممين لإنشاء المزيد من المشاريع عند اشتراكهم في التطبيق. سنعرض لكم تاليًا كيف سخّرنا إطار العمل IOTA لاتخاذ القرار بالمضي قًدمًا في تصميمنا: المدخلات: كان مُدخلنا هو: هل ستلهم هذه الصفحة المستخدمين بعد رؤيتها في تطبيقنا لتصميم مشاريع أكثر؟ الهدف: كان هدفنا هو مشاركة 60% على الأقل من المستخدمين الذين زاروا الصفحة، وذلك بإنشائهم مشروعًا جديدًا عليها. الاختبار: صممنا نموذجًا أوليًا واختبرناه على المستخدمين الموجودين لنعرض حالة مسار المستخدم الذي بنيناه في الصفحة. التحليل: وجدنا عند تحليل النتائج أنّ غالبية المستخدمين لم يفهموا الغرض من بناء هذه الصفحة، وقررنا عندها أنّ الصفحة لا تهم مستخدمينا ولا يحتاجونها، وبالتالي أوقفنا تطويرها. أفضل الممارسات لاعتماد معايير خاصة بالاختبارات بعد أن أضفنا الاختبارات إلى مراحل التصميم بالاعتماد على عملاء Maze، وصلتُ إلى خلاصة لأفضل ما يمكن تطبيقه من ممارسات لتسريع الاختبارات ضمن مؤسستنا. وإليك بعض الممارسات المفتاحية لاعتماد معايير الاختبارات لمؤسستك: 1. إيصال فائدة الاختبار من الأمور الأساسية إيصال الفائدة من اختبارك باكرًا إلى أعضاء فريقك وإلى المديرين التنفيذيين والمهتمين بمنتجك. ابدأ بالاطلاع على خبرة أعضاء فريقك السابقة في الاختبارات، فقد تجد بعض النقص الذي عليك ترميمه. إليك بعض الأفكار لتبدأ: نظّم مناسبات أو مناقشات عبر الإنترنت لتشارك أهمية هذا الاختبار. اعرض أمثلةً مفتاحيةً تدل على الفائدة التي أتاحها الاختبار للمنتج ولفريق التصميم لتحقيق أهدافهم. اطلب المساعدة من المهتمين وأعضاء الفريق من خلال الانخراط في مقابلات أو استطلاعات رأي أو اختبارات استخدام لمنتجك. اعرض بانتظام الأمور المهمة التي استخلصتها على قنوات واسعة الانتشار. 2. تفهم إطارات عمل اتخاذ القرار إنّ معرفة نقاط القوة في صنع القرار داخل مؤسستك وكيفية استغلالها هي طريقة أخرى لاتخاذ القرارات. وكما يقول بيزود سيرجاني مؤسس Yet Another Studio، والمدير السابق لقسم الأبحاث وعمليات التحليل في Stack: "ينبغي أن نوجه البحث نحو قرار، فمعرفة ما الذي ستقرره سيساعدك على فهم ما تبحث عنه وكيف ستُجري هذا البحث". قد لا تتمكن من التأثير على كل القرارات في البداية، لكن مع بعض الفرص ستكون قادرًا على إيصال الفائدة من الاختبارات التي يساهم فيها المستخدمون. 3. ابن مجتمعا من المتابعين أو استفد من تلك الموجودة أصلا يعزز بناء مجتمعات المتابعين الذين ينخرطون في الاختبارات السريعة الفرص التي قد تزيد من إمكاناتك. لقد أنشأنا في Maze لجنةً استشاريةً للعملاء Customer Advisory Board نتواصل من خلالها مع الأعضاء عند اتخاذ قرارات استراتيجية، أو اختبار ميزة جديدة، أو عند تقييم أفكارنا. يمكنك بناء مجتمع للاختبارات من خلال إعداد مجتمع على Slack أو أنا، أو أية قناة أخرى تفضلها. ألق نظرةً إن شئت على Sketch Labs مثلًا، أو على Freetrade’s Community. الاختبار والتعلم أساس نجاح الفريق لمسنا منذ أن تبنينا إطار عمل الاختبار السريع توسّع آفاق فرق الإنتاج من الأفكار المحدودة والمنعزلة إلى منظومة تعلّم تضم كافة الفرق. لقد ساعد ذلك الجميع من مصممين وباحثين ومديري إنتاج ومسوقين في الحصول على الأفكار والإرشادات والتعاون في التعلّم والبحث، وأجد ذلك طريقةً حقيقيةً لنشر ديمقراطية الوصول إلى البيانات وتكوين مؤسسة قادرة على دفع أعضائها نحو مستقبل أكثر إشراقًا في بحثهم عن أفضل السبل لتلبية حاجات المستخدمين. ترجمة -وبتصرف- للمقال How to rapidly test any experience لكاتبه JONATHAN WIDAWSKI. اقرأ أيضًا كيفية استخدام اختبارات تجربة المستخدم لتحسين قيمة استمرار التعامل مع العميل CLV طرق مثبتة لتخصيص تجربة المستخدم كيفية تحسين إعدادات تطبيق بناء على تجربة المستخدم الفرق بين إمكانية الوصول والشمولية في تصميم تجربة المستخدم كيفية إجراء بحث في تجربة المستخدم لتحسين مشاريعك تصميم تجربة تهيئة المستخدم User Onboarding
-
يمكن استخدام لغة الأنماط الانسيابية CSS في إنشاء رسوم متحركة بسيطة، دون الحاجة إلى شيفرة JavaScript إطلاقًا، لكن يمكننا بالطبع استخدام JavaScript للتحكم برسوم CSS المتحركة وجعلها أفضل بكتابة القليل من الشيفرة. الحركة الانتقالية في CSS إنّ فكرة الحركة الانتقالية transition في CSS بسيطة جدًا، حيث نصف خاصيةً محددةً وآليةً لإظهار التغيرات فيها على شكل رسوم متحركة، فعندما تتغير قيمة الخاصية سيُظهر المتصفح هذا التغيير في حركة، وبالتالي كل ما علينا فعله هو تغيير الخاصية، وسينفذ المتصفح دفقًا من التغيرات المتتالية فيها. تُحرّك شيفرة CSS التغييرات في الخاصية background-color لمدة ثلاث ثوان: .animated { transition-property: background-color; transition-duration: 3s; } فلو كان لأي عنصر الصنف CSS الذي سميناه animated، فستظهر أي تغيرات على الخاصية background-color في هذا العنصر في حركة لمدة 3 ثوان. انقر على الزر الذي ستُظهره الشيفرة التالية لتحريك الخلفية: <button id="color">Click me</button> <style> #color { transition-property: background-color; transition-duration: 3s; } </style> <script> color.onclick = function() { this.style.backgroundColor = 'red'; }; </script> See the Pen JS-P3-03-Fetch-Download-progress-ex8 by Hsoub (@Hsoub) on CodePen. توصف الحركة الانتقالية في CSS بأربعة خصائص، هي: transition-property. transition-duration. transition-timing-function. transition-delay. سنشرح هذه الخصائص بعد قليل، لكن دعونا نلاحظ أنّ الخاصية transition ستسمح بالتصريح عن الخصائص الأربعة السابقة معًا وفق الترتيب التالي:property duration timing-function delay، بالإضافة إلى قدرتها على إظهار الحركة الانتقالية على عدة خصائص معًا. يُظهر النقر على الزر في الشيفرة التالية الحركة الانتقالية للخاصيتين color وfont-size: <button id="growing">Click me</button> <style> #growing { transition: font-size 3s, color 2s; } </style> <script> growing.onclick = function() { this.style.fontSize = '36px'; this.style.color = 'red'; }; </script> See the Pen JS-P3-05-CSS-animations-ex2 by Hsoub (@Hsoub) on CodePen. سنشرح الآن خصائص الحركة الانتقالية. الخاصية transition-property نكتب ضمن هذه الخاصية قائمةً بخصائص CSS التي نريد إظهار تغييراتها على شكل حركة انتقالية، مثل left وmargin-left وheight وcolor وغيرها، أو يمكن أن نختار all التي تعني تحريك كل الخصائص . توجد خصائص لا يمكن تحريكها، لكن معظم الخصائص شائعة الاستعمال وقابلة للتحريك. الخاصية transition-duration يمكن أن نحدد في هذه الخاصية المدة التي ستجري فيها الحركة الانتقالية، وينبغي أن يكون التوقيت بتنسيق CSS ومقدرًا بالثانية s أو بالميلي ثانية ms. الخاصية transition-delay نحدد في هذه الخاصية فترة الانتظار قبل عرض الحركة الانتقالية، فلو كانت قيمتها ثانيةً واحدةً وقيمة الخاصية transition-duration ثانيتن، فستُعرض الحركة الانتقالية بعد ثانية من تغيّر الخاصيّة، وستستمر لمدة ثانيتين، كما يمكن استخدام قيم سالبة أيضًا، وعندها ستبدأ الحركة مباشرةً، إلا أنّ نقطة البداية ستنسحب قليلًا، فلو كانت قيمة الخاصية transition-delay هي "-1" ثانية وقيمة الخاصية transition-duration هي "2" ثانية، فستبدأ الحركة من المنتصف، ولمدة ثانية واحدة. لاحظ تغير الأعداد من 0 إلى 9 في الرسم المتحرك التالي باستخدام الخاصية translate في CSS: شيفرة الملف script.js: stripe.onclick = function() { stripe.classList.add('animate'); }; شيفرة الملف style.css: #digit { width: .5em; overflow: hidden; font: 32px monospace; cursor: pointer; } #stripe { display: inline-block } #stripe.animate { transform: translate(-90%); transition-property: transform; transition-duration: 9s; transition-timing-function: linear; } شيفرة الملف index.html: <!doctype html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="style.css"> </head> <body> Click below to animate: <div id="digit"><div id="stripe">0123456789</div></div> <script src="script.js"></script> </body> </html> وستكون النتيجة: تجري الحركة الانتقالية للخاصية transform بالشكل التالي: #stripe.animate { transform: translate(-90%); transition-property: transform; transition-duration: 9s; } تضيف شيفرة JavaScript السابقة الصنف animate إلى العنصر، وتبدأ الحركة عند تنفيذ الأمر: stripe.classList.add('animate'); يمكن أن نبدأ التحريك انطلاقًا من نقطة ما من الحركة الانتقالية، أي من رقم محدد -وقد يكون متعلقًا بالثانية الحالية مثلًا- وباستخدام قيمة سالبة للخاصية transition-delay. لو نقرت على الرقم في المثال التالي، فستبدأ الحركة من الثانية الحالية: شيفرة الملف script.js: stripe.onclick = function() { let sec = new Date().getSeconds() % 10; stripe.style.transitionDelay = '-' + sec + 's'; stripe.classList.add('animate'); }; شيفرة الملف style.css: #digit { width: .5em; overflow: hidden; font: 32px monospace; cursor: pointer; } #stripe { display: inline-block } #stripe.animate { transform: translate(-90%); transition-property: transform; transition-duration: 9s; transition-timing-function: linear; } شيفرة الملف index.html: <!doctype html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="style.css"> </head> <body> Click below to animate: <div id="digit"><div id="stripe">0123456789</div></div> <script src="script.js"></script> </body> </html> وستكون النتيجة: يمكن تنفيذ ذلك باستخدام JavaScript لكن بإضافة سطر آخر من الشيفرة: stripe.onclick = function() { let sec = new Date().getSeconds() % 10; // مثلًا تعني القيمة -3 هنا أن الحركة ستبدأ من الثانية الثالثة stripe.style.transitionDelay = '-' + sec + 's'; stripe.classList.add('animate'); }; الخاصية transition-timing-function تصف دالة التوقيت هذه كيفية توزيع الحركة الانتقالية أثناء فترة العرض، كأن تبدأ ببطء ثم تسرع، أو العكس. ستبدو هذه الخاصية في البداية معقدة، لكنها ستغدو بسيطةً إذا فهمناها، كما ستقبل نوعين من القيم هما منحني بيزيه Bezier curve، أو الدالة steps، لنبدأ مع المنحني كونه الأكثر استخدامًا. منحني بيزيه يمكن إعداد منحنى بيزيه بأربعة نقاط حاكمة تحقق الشروط التالية: إحداثيات النقطة الأولى (0,0). إحداثيات النقطة الأخيرة (1,1). ينبغي أن يكون الإحداثي x لنقطتي المنتصف بين 0 و1، بينما يمكن أن نختار أي قيمة للإحداثي y. إن صيغة منحني بيزيه هي: (cubic-bezier(x2, y2, x3, y3، إذًا علينا فقط أن نحدد النقطتين الحاكمتين الثانية والثالثة، لأنّ الأولى والأخيرة ثابتتان. تحدد دالة التوقيت سرعة تحريك عملية تشكيل المنحني: يمثل المحور x محور الزمن الذي يبدأ بالنقطة "0"، وينتهي بالنقطة "1" لقيمة transition-duration. يمثل المحور y مقدار اكتمال العملية، ويبدأ بالنقطة "0" وينتهي بالنقطة "1" لقيمة الخاصية التي نريد إظهار الحركة الانتقالية لها. المثال الأبسط هو التحريك المنتظم، أي الخطي، وذلك باستخدام المنحني (cubic-bezier(0, 0, 1, 1، الذي سيبدو بالشكل التالي: عندما يمر الوقت (x) ستكتمل عملية التحريك (y) بثبات وتناغم من القيمة 0 إلى 1. وفي مثالنا التالي ستتحرك صورة القطار من اليسار إلى اليمين بسرعة ثابتة، إذا نقرت عليه: شيفرة الملف style.css: .train { position: relative; cursor: pointer; width: 177px; height: 160px; left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); } شيفرة الملف index.html: <!doctype html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="style.css"> </head> <body> <img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'"> </body> </html> وستكون النتيجة: إليك شيفرة استخدام الخاصية transition بناءً على منحني بيزيه: .train { left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); /* JavaScript sets left to 450px */ } لكن كيف سنرى القطار وهو يُبطئ؟ يمكن استعمال منحني بيزيه آخر من الشكل (cubic-bezier(0.0, 0.5, 0.5 ,1.0، وفي المقال التالي سنرى كيفية بدء العملية بسرعة، حيث يتحرك القطار سريعًا ثم يتباطأ. شيفرة الملف style.css: .train { position: relative; cursor: pointer; width: 177px; height: 160px; left: 0px; transition: left 5s cubic-bezier(0.0, 0.5, 0.5, 1.0); } شيفرة الملف index.html: <!doctype html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="style.css"> </head> <body> <img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'"> </body> </html> وستكون النتيجة: شيفرة CSS: .train { left: 0; transition: left 5s cubic-bezier(0, .5, .5, 1); /* JavaScript sets left to 450px */ } يمكنك استخدام العديد من المنحنيات الجاهزة، مثل linear أو ease أو ease-in أو ease-out أو ease-in-out، وإليك جدولًا بالمنحنيات الموافقة لها: وفي ملاحظة هامة، تجدر الإشارة إلى أن * تستخدَم ease قيمةً افتراضيةً إذا لم نحدد دالة توقيت timing-function. وكما هو واضح يمكن استعمال ease-out في حالة القطار الذي يُبطئ حركته: .train { left: 0; transition: left 5s ease-out; /* transition: left 5s cubic-bezier(0, .5, .5, 1); */ } لكنه يبدو مختلفًا نوعًا ما. قد تتجاوز الحركة الانتقالية المدى عند استخدام منحني بيزيه. ويمكن أن تحمل النقاط الحاكمة في منحني بيزيه أي قيمة للإحداثي y حتى القيم السالبة أو الضخمة، وبهذا قد يمتد المنحني على مساحة ضيقة جدًا أو واسعة جدًا، وقد تتجاوز الرسوم المتحركة مداها الطبيعي. لاحظ الشيفرة التالية: .train { left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); /* JavaScript sets left to 400px */ } ينبغي إظهار حركة انتقالية للخاصية left بين القيمتين 100px و400px، لكنك بالنقر على صورة القطار سترى ما يلي: أولًا سيتراجع القطار إلى الخلف، وستغدو قيمة left أقل من 100px. يتقدم بعدها القطار إلى الأمام لتتجاوز left القيمة 400px بقليل. ثم يعود مجددًا إلى القيمة 400px للخاصية left. شيفرة الملف style.css: .train { position: relative; cursor: pointer; width: 177px; height: 160px; left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); } شيفرة الملف index.html: <!doctype html> <html> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="style.css"> </head> <body> <img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='400px'"> </body> </html> وستكون النتيجة على النحو الآتي: ما جرى واضح جدًا إذا نظرنا إلى رسم منحني بيزيه لهذه الحركة، فقد نقلنا قيمة الإحداثي y للنقطة الثانية تحت الصفر، وجعلنا قيمته بالنسبة للنقطة الثالثة تتجاوز 1، لذا سيخرج شكل المنحني عن الشكل النظامي للمنحني من الدرجة الرابعة، فقيمة y خارج المجال المعياري بين 0 و1. وكما نعرف، تقيس y مدى اكتمال الحركة الانتقالية، حيث تتطابق القيمة y = 0 مع بداية الحركة، والقيمة y = 1 مع نهايتها. إذًا ستنقل القيمة y<0 نقطة البدء إلى ما قبل البداية، والقيمة y>1 إلى ما بعدها. يُعَد هذا التغيير بسيطًا طبعًا، فلو ضبطنا y على القيمة 99 أو -99، فسيقفز القطار بسرعة أكبر بكثير خارج المجال، لكن كيف سننشئ منحني بيزيه من أجل مهمة معينة؟ هنالك أدوات كثيرة، كما يمكن تنفيذه ضمن مواقع عديدة على الإنترنت. دالة التوقيت steps تسمح دالة التوقيت ([steps(number of steps[, start/end بتقسيم الحركة الانتقالية إلى خطوات، لنرى ذلك من خلال مثال قائمة من الأرقام التي لا تبدي أي حركة: شيفرة الملف style.css: #digit { border: 1px solid red; width: 1.2em; } #stripe { display: inline-block; font: 32px monospace; } شيفرة الملف index.html: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> <div id="digit"><div id="stripe">0123456789</div></div> </body> </html> وستكون النتيجة: سنُظهر الأرقام منفصلة، وذلك بجعلها غير مرئية خارج النافذة الحمراء، بعدها سنحرك القائمة نحو اليسار في كل خطوة، وبالتالي سنحتاج إلى 9 خطوات لتنفيذ الأمر: #stripe.animate { transform: translate(-90%); transition: transform 9s steps(9, start); } شيفرة الملف style.css: #digit { width: .5em; overflow: hidden; font: 32px monospace; cursor: pointer; } #stripe { display: inline-block } #stripe.animate { transform: translate(-90%); transition-property: transform; transition-duration: 9s; transition-timing-function: steps(9, start); } شيفرة الملف index.html: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> Click below to animate: <div id="digit"><div id="stripe">0123456789</div></div> <script> digit.onclick = function() { stripe.classList.add('animate'); } </script> </body> </html> وستكون النتيجة: يُمثّل الوسيط الأول للدالة (steps(9, start عدد الخطوات، أي ستقسم الحركة الانتقالية إلى 9 أجزاء -10% لكل خطوة-، كما سيُقسم الفاصل الزمني تلقائيًا إلى 9 أجزاء أيضًا، فلو كانت قيمة الخاصية transition تعادل 9 ثوان، فسيظهر كل رقم لمدة ثانية؛ أما الوسيط الثاني فيأخذ إحدى القيمتين start أو end، حيث تعني القيمة start أننا نريد تنفيذ الخطوة الأولى مباشرةً عند بداية الحركة، ويمكنك ملاحظة ذلك خلال الحركة، فعندما ننقر على الرقم سيتغير إلى "1" -أي الخطوة الأولى- مباشرةً، ثم يكمل ثانيةً وهكذا. تتقدم العملية كالتالي: -10% - 0s: يحدث التغيير الأول في بداية الثانية الأولى مباشرةً. 1s – -20%. … 8s – -80%. تُظهر الثانية الأخيرة القيمة الأخيرة. بينما تعني القيمة end وجوب تطبيق التغيير في نهاية كل ثانية، ويكتمل في نهاية الثانية المعدودة. وتتقدم العملية كالتالي: 0s – 0: لا يتغير شيء في الثانية الأولى. 1s – -10%: يحدث التغير الأول عند نهاية الثانية الأولى مباشرةً. 2s – -20%. … 9s – -90%. إليك مثالًا نموذجيًا عن استخدام الدالة (steps(9, start. شيفرة الملف style.css: #digit { width: .5em; overflow: hidden; font: 32px monospace; cursor: pointer; } #stripe { display: inline-block } #stripe.animate { transform: translate(-90%); transition-property: transform; transition-duration: 9s; transition-timing-function: steps(9, end); } شيفرة الملف index.html: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="style.css"> </head> <body> Click below to animate: <div id="digit"><div id="stripe">0123456789</div></div> <script> digit.onclick = function() { stripe.classList.add('animate'); } </script> </body> </html> وستكون النتيجة: إليك بعض القيم المختصرة: step-start: تماثل (steps(1, start. step-end: تماثل (steps(1, end. لا تستخدم هذه القيم إلا نادرًا لأنها لا تُصنَّف حركات فعلًا، بل تغيرات تحدث بالخطوة. الحدث transitionend يقع الحدث transitionend عندما تنتهي الحركة المبنية على خصائص CSS، ويستخدم لتنفيذ عمل ما بعد انتهاء الحركة، حيث يمكننا مثلًا ضم حركات مختلفة، فالسفينة في المثال التالي ستبدأ رحلتها وتستمر بالعودة عند النقر، وفي كل مرة تبتعد نحو اليمين أكثر: تبدأ الحركة نتيجة تنفيذ الدالة go، التي يُعاد تنفيذها في كل مرة تنتهي فيها الحركة، وتغير اتجاه الحركة: boat.onclick = function() { //... let times = 1; function go() { if (times % 2) { // sail to the right boat.classList.remove('back'); boat.style.marginLeft = 100 * times + 200 + 'px'; } else { // sail to the left boat.classList.add('back'); boat.style.marginLeft = 100 * times - 200 + 'px'; } } go(); boat.addEventListener('transitionend', function() { times++; go(); }); }; لكائن الحدث transitionend عدة خصائص مميزة، هي: event.propertyName: يعيد اسم الخاصية التي أنهت حركتها الانتقالية، وتظهر فائدتها عند تحريك عدة خصائص معًا. event.elapsedTime: الوقت الذي استغرقته الحركة بالثواني، دون احتساب transition-delay. القاعدة keyframes يمكن ضم عدة حركات بسيطة معًا باستخدام قاعدةkeyframes@ من CSS، والتي تحدد اسم الحركة الانتقالية، وقواعد مكانها وزمانها وكيفيتها، بعدها سنتمكن باستخدام الخاصية animation من ربط الحركة بالعنصر وتخصيص معاملات إضافية قد نحتاجها، إليك مثالًا مع الشرح: <div class="progress"></div> <style> @keyframes go-left-right { /* "go-left-right" أعط القاعدة اسمًا*/ from { left: 0px; } /* left: 0px حرك ابتداءً من قيمة */ to { left: calc(100% - 50px); } /* 100%-50px: حرك نحو اليسار */ } .progress { animation: go-left-right 3s infinite alternate; /* طبق الحركة التي عرفناها في الأعلى على العنصر المدة 3 ثوان عدد المرات: لانهائي بدّل اتجاه الحركة في كل مرة */ position: relative; border: 2px solid green; width: 50px; height: 20px; background: lime; } </style> See the Pen JS-P3-05-CSS-animations-ex3 by Hsoub (@Hsoub) on CodePen. ستجد العديد من المقالات التي تشرح القاعدة @keyframes من CSS مع ميزاتها المفصلة، ولن تحتاجها غالبًا إلا عندما يكون كل شيء في موقعك متحركًا باستمرار. الأداء يمكن تحريك أغلب خاصيات CSS بما أنها قيم رقمية مثل width و color و font-size، فيعمل المتصفح عندما تحرك هذه الخاصيات على تغيير هذه القيم تدريجيًا في كل إطار من الإطارات مما يعطي إيحاء بتأثير حركي سلس، ولكن انتظر، لا تبدو كل الحركات سلسلة كما تريدها لأن تغيير بعض خاصيات CSS عملية مجهدة. لنغوص أكثر في التفاصيل، عندما يكتشف المتصفح حدوث تغير في التنسيق، فإنه يمر بثلاثة خطوات حتى يخرج الصفحة بالتنسيق الجديد وهي: التخطيط Layout: إعادة حساب إحداثيات ومواضع كل عنصر من عناصر الصفحة، ثم الشكل Paint: إعادة حساب كيف يجب أن يظهر كل عنصر بعد تموضعه في مكانه وكل شيء حرفيًا بما فيها الخلفيات والألوان، ثم التركيب Composite: تصيير النتائج النهائية وإخراجها وعرضها على بكسلات الشاشة وتطبيق تحويلات CSS إن وجدت. تكرر هذه الخطوات خلال إجراء تحريك عبر CSS لكل إطار من الإطارات، رغم أن خاصيات CSS التي لا تؤثر على الإحداثيات أو التموضع مثل تغيير خاصية اللون color فقد تتخطى الخطوة الأولى وينتقل المتصفح إلى الخطوة الثانية مباشرةً ثم الثالثة وقد تتخطى بعض الخاصيات حتى الخطوة الثانية وتنتقل إلى الخطوة الثالثة مباشرةً، ويمكنك أن تطلع على قائمة خاصيات CSS والمراحل التي تستهدفها من موقع CSS Triggers. معلومٌ أن تلك الخطوات تأخذٌ وقتًا في الحساب خصوصًا إن حوت الصفحة على عناصر كثيرة وكانت تخطيطها معقدًا، فقد يُلاحظ تأخر في الحركة على أغلب الأجهزة مسببًا حركة مضطربة مرتعشة، لذا تتصف الحركات المطبقة على الخاصيات التي تتخطى حساب التخطيط (ويفضل أيضًا الشكل أي الخطوة الثانية) بالسرعة والسلاسة. تعد الخاصية transform خيارًا جيدًا لسببين هما: تستهدف التحويلات في CSS صندوق العنصر ككل (تدوير، أو قلب أو تمديد أو إزاحة …إلخ.) لا تؤثر التحويلات على العناصر المجاورة للعنصر المستهدف. وفقًا ذلك، تطبق التحويلات على عناصر يكون فيها التخطيط والشكل محسوبين مسبقًا وتنتقل مباشرةً إلى الخطوة الثالثة من الخطوات السابقة، أي أن المتصفح يحسب التخطيط (الحجم والمواضع) ثم يضيف لها تسنيق الشكل من ألوان وواجهات وغيرهما في المرحلة الثانية ثم يطبق التحويل الممثل بالخاصية transform على صندوق العنصر إن وجدت. أي تغييرات (تحريكات) تطبق على الخاصية transform لا تستدعي المرور ضمن الخطوة الأولى والثانية مطلقًا، علاوة على أن المتصفح يستغل قدرات الحاسوب الرسومية (بطاقة العرض التي تكون مدمجة في المعالج أو منفصلة) لمعالجة التحويلات مما يزيد من كفاءة العملية. مما سبق نجد أن الخاصية transform قوية للغاية ويمكنك عبرها تدوير عنصرٍ أو قلبه أو تطويله أو تقصيره أو حتى تحريكه وغيرها (انظر صفحة الخاصية على موسوعة حسوب)، فيمن استعمال transform: translateX(…) بدلًا من استعمال الخاصية left أو margin-left أو استعمال transform: scale لزيادة حجم عنصر وهكذا. لاحظ أن الخاصية opacity لا تمر على الخطوة الأولى من الخطوات السابقة (أيضًا تتخطى الخطوة الثانية في محرك Mozilla Gecko)، ويمكن استعمالها لتطبيق تأثيرات مثل الإظهار أو الإخفاء أو التلاشي، ويمكن استعمال transform معها لإنشاء حركات سلسلة جميلة. انظر مثلًا المثال التالي الذي يتحرك فيه العنصر بمجرد الضغط عليه إلى اليمين مقدار 300 بكسل ثم يختفي: <img src="https://js.cx/clipart/boat.png" id="boat"> <style> #boat { cursor: pointer; transition: transform 2s ease-in-out, opacity 2s ease-in-out; } .move { transform: translateX(300px); opacity: 0; } </style> <script> boat.onclick = () => boat.classList.add('move'); </script> See the Pen JS-P3-05-CSS-animations-ex4 by Hsoub (@Hsoub) on CodePen. وإليك أيضًا مثال آخر أكثر تعقيدًا يُطبَّق فيه إطارات الحركة @keyframes: <h2 onclick="this.classList.toggle('animated')">click me to start / stop</h2> <style> .animated { animation: hello-goodbye 1.8s infinite; width: fit-content; } @keyframes hello-goodbye { 0% { transform: translateY(-60px) rotateX(0.7turn); opacity: 0; } 50% { transform: none; opacity: 1; } 100% { transform: translateX(230px) rotateZ(90deg) scale(0.5); opacity: 0; } } </style> See the Pen JS-P3-05-CSS-animations-ex5 by Hsoub (@Hsoub) on CodePen. خلاصة تسمح الحركات الانتقالية التي تدعمها CSS بإجراء تغيرات ناعمة على خاصية أو أكثر، وهي تقنية جيدة للكثير من مهام الرسوم المتحركة، كما يمكننا استخدام رسوم JavaScript المتحركة وهذا ما سنراه في جزئية لاحقة من هذه السلسلة. النقاط الأساسية التي تجعل الرسومات المتحركة باستخدام CSS محدودةً موازنةً برسوم JavaScript هي: الإيجابيات. تنفَّذ الأشياء البسيطة ببساطة. سريعة وخفيفة على المعالج. السلبيات. رسوم JavaScript أكثر مرونةً، إذ يمكنها تنفيذ أي منطق رسومي، مثل "انفجار" عنصر مثلًا. لا تعتمد رسوميات JavaScript على تغيرات الخاصية، بل يمكن إنشاء عناصر جديدة في JavaScript مثل جزء من الرسوميات. في أمثلتنا الأولى حركنا font-size وleft وwidth وheight، لكن إذا رغبنا في أداء أفضل في مشاريعنا الحقيقية، فيجب أن نستخدم ()transform: scale و()transform: translate. يمكن تنفيذ الرسوميات المتحركة باستخدام CSS كما شرحناها في هذا المقال، وسيسمح لنا الحدث transitionend باستخدام JavaScript بعد انتهاء الحركة الانتقالية، وبالتالي ستتكامل جيدًا مع الشيفرة. سنتعرف في المقال القادم على رسوم JavaScript المتحركة التي تساعدنا في تنفيذ حالات أكثر تعقيدًا. مهام لإنجازها تحريك طائرة في CSS أظهر رسمًا متحركًا يشابه صورة الطائرة في الأسفل: يزداد حجم الصورة من 40x24px إلى 400x240px. تستغرق العملية 3 ثوان. اطبع كلمة "!Done" عند الانتهاء. لا يجب أن تتوقف العملية بالنقر على الصورة أثناء التنفيذ. افتح المثال في بيئة تجريبية الحل إليك الشيفرة: /* original class */ #flyjet { transition: all 3s; } /* JS adds .growing */ #flyjet.growing { width: 400px; height: 240px; } انتبه إلى أن الحدث transitionend يقع مرتين، مرة لكل خاصية، وبذلك ستظهر الرسالة مرتين إن لم نضع أي شرط تحقق. تحرك الطائرة 2 أعد التمرين السابق لكن اجعل الحجم يتجاوز الحجم الأقصى للصورة 400x240px، ثم يعود إلى هذا الحجم. الحل نريد اختيار أنسب منحني بيزيه لتلك الحركة، إذ يجب أن تحقيق y>1 في موضع ما للطائرة لتحقيق تأثير القفزة مثل cubic-bezier(0.25, 1.5, 0.75, 1.5)، انظر المخطط: افتح الحل في بيئة تجريبية. تحريك دائرة أنشئ الدالة showCircle(cx, cy, radius) التي تظهر دائرةً تنمو. cx,cy: يمثلان إحداثيات مركز الدائرة بالنسبة إلى إحداثيات النافذة. radius: نصف قطر الدائرة. انقر الزر لترى كيف سيظهر الحل: تجد المثال في هذه البيئة التجريبية وتجد حله في هذه البيئة التجريبية. تحريك دائرة مع دالة استدعاء انطلاقًا من المهمة السابقة، نحتاج إلى دائرة تُظهر رسالةً داخلها. ينبغي أن تظهر الرسالة مباشرةً بعد اكتمال الحركة، أي ظهور الدائرة بحجمها الكامل، وإلا سيبدو المنظر سيئًا. ترسم الدالة (showCircle(cx, cy, radius الدائرة، لكن لا يمكنها أن تدلّك على اكتمال الرسم، لذا أضف دالة استدعاء callback مثل معامل إلى الدالة السابقة (showCircle(cx, cy, radius, callback، حيث تُستدعى عندما ينتهي الرسم المتحرك. ينبغي أن تتلقى دالة الاستدعاء callback العنصر <div> للدائرة مثل وسيط. إليك مثالًا: showCircle(150, 150, 100, div => { div.classList.add('message-ball'); div.append("Hello, world!"); }); النموذج: تجد حل المثال في هذه البيئة التجريبية. ترجمة -وبتصرف- للفصل css-animations من سلسلة The Modern JavaScript Tutorial. اقرأ أيضًا المقال السابق: منحنى بيزيه وأهميته في الرسوميات وصناعة الحركات في جافاسكربت تأثيرات الانتقال والحركة في CSS منحنى بيزيه وأهميته في الرسوميات وصناعة الحركات في جافاسكربت table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } p iframe { border: 1px solid #e7e5e3 !important; }
-
- 1
-
- تحريك
- animations
-
(و 1 أكثر)
موسوم في: