البحث في الموقع
المحتوى عن 'local storage'.
-
ربما تتساءل: "كيف أستطيع البدء باستخدام HTML5 إن لم تكن تدعمها المتصفحات القديمة؟" لكن السؤال نفسه مُضلِّل، فليست HTML5 شيئًا واحدًا كبيرًا، وإنما هي مجموعة من الميزات المتفرقة، فلا يمكنك الكشف عن "دعم HTML5" لأن هذا غير منطقي، وإنما يمكنك الكشف عن دعم الميزات المتفرقة مثل canvas أو تشغيل الفيديو أو تحديد الموقع الجغرافي. تقنيات الاكتشاف عندما يُحمِّل متصفحك صفحة ويب، فإنه يُنشِئ ما نسميه "Document Object Model" (اختصارًا DOM)، الذي هو مجموعة من الكائنات التي تُمثِّل عناصر HTML الموجودة في الصفحة، فكل عنصر -كل وسم <p> أو <div> أو <span>- يُمثَّل في DOM بكائن مختلف (هنالك كائنات عامة مثل window و document، التي لا ترتبط بعناصر محددة). تتشارك جميع كائنات DOM بمجموعةٍ من الخاصيات المشتركة، لكن لبعض الكائنات خاصيات أكثر من بعضها الآخر. ويكون لدى بعض الكائنات خاصيات فريدة في المتصفحات التي تدعم ميزات HTML5؛ لذلك يكون من الكافي عادةً إلقاء نظرة على DOM لتعرف ما هي الميزات المدعومة. هنالك أربع تقنيات أساسية لاكتشاف دعم المتصفح لميزة مُحدَّدة، هذه هي بالترتيب من الأبسط إلى الأكثر تعقيدًا: التحقق من وجود خاصية معينة في كائن عام (مثل window أو navigator)، مثلما هو عليه الحال مع دعم تحديد الموقع الجغرافي. إنشاء عنصر، ثم التحقق من وجود خاصية معيّنة في ذاك العنصر، مثلما هو عليه الحال مع تحديد دعم canvas. إنشاء عنصر، ثم التحقق من وجود دالة (method) معينة في ذاك العنصر، ثم استدعاء تلك الدالة والتحقق من القيمة التي تُعيدها. مثلما هو عليه الحال مع معرفة صيغ الفيديو المدعومة. إنشاء عنصر، وضبط خاصية فيه إلى قيمةٍ معيّنة، ثم التحقق من أنَّ تلك الخاصية قد احتفظت بقيمتها. مثل ما هو عليه الحال مع معرفة أنواع حقول <input> المدعومة. Modernizr: مكتبة اكتشاف دعم ميزات HTML5 Modernizr هي مكتبة JavaScript مفتوحة المصدر مُرخَّصة برخصة MIT مهمتها اكتشاف الدعم للعديد من ميزات HTML5 و CSS3، وأنصحك باستعمال آخر إصدار منها دومًا. عليك -لاستعمالها- تضمينها عبر عنصر <script> في ترويسة (header) صفحتك كما يلي: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Dive Into HTML5</title> <script src="modernizr.min.js"></script> </head> <body> ... </body> </html> سيعمل Modernizr تلقائيًا، فلا توجد هنالك دالة modernizr_init() لكي تستدعيها. فعندما يُشغَّل السكربت، فإنه يُنشِئ كائنًا عامًا اسمه Modernizr يحتوي على مجموعة من الخاصيات المنطقية (أي True أو False) لكل خاصية يمكنه الكشف عن دعمها. فعلى سبيل المثال، إن كان متصفحك يدعم استخدام canvas، فإن قيمة الخاصية Modernizr.canvas ستكون True، وإن لم يكن متصفحك يدعمها، فستكون قيمة الخاصية Modernizr.canvas مساويةً إلى False. if (Modernizr.canvas) { // لنرسم بعض الأشكال } else { // لا يوجد دعم لخاصية canvas } الكشف عن دعم Canvas تُعرِّف HTML العنصر <canvas> على أنه "لوحة نقطية ذات أبعاد معينة يمكن استخدامها لعرض المخططات ورسومات الألعاب وغيرها من الصور المرئية برمجيًا". ويُمثِّل مستطيلًا في صفحتك حيث تستخدم JavaScript لرسم أي شيء تريده فيه، وتُعرِّف HTML5 مجموعةً من الدوال (تدعى "canvas API") لرسم الأشكال (shapes) وتعريف المسارات (paths) وإنشاء التدرجات اللونية وتطبيقات التحويلات (transformations) على العناصر. سنستخدم تقنية الكشف عن الدعم الثانية للتحقق من دعم المتصفح للعنصر canvas، فإن دعم متصفحك واجهة canvas البرمجية (API) فهذا يعني أن الكائن في DOM الذي يُمثِّل عنصر <canvas> سيملك الدالة getContext()، وإن لم يكن يدعم متصفحك واجهة canvas البرمجية، فسيملك الكائن المُنشَأ لعنصر <canvas> الخاصيات العامة لكل العناصر، وليس من بينها أي شيءٍ متعلقٍ بتقنية canvas. function supports_canvas() { return !!document.createElement('canvas').getContext; } تبدأ هذه الدالة عملها بإنشاء عنصر <canvas> لا حول له ولا قوة، لكن ذاك العنصر لن يرتبط مطلقًا بصفحتك، ولن يراه أحد، وإنما سيبقى عائمًا بالذاكرة دون أن يفعل شيئًا. return !!document.createElement('canvas').getContext; وبعد إنشاء عنصر <canvas>، ستختبر وجود الدالة getContext()، فستكون هذه الدالة موجودةً إذا كان المتصفح يدعم واجهة canvas البرمجية. return !!document.createElement('canvas').getContext; وفي النهاية، استعملنا علامة النفي المزدوجة (!!) كي تكون النتيجة قيمةً منطقية (أي true أو false). return !!document.createElement('canvas').getContext; تكتشف هذه الدالة الدعم لأغلبية مكونات واجهة canvas البرمجية، بما في ذلك الأشكال، والمسارات، والتدرجات اللونية والأنماط. لكنها لا تكتشف وجود المكتبة الخارجية explorercanvas التي تسمح باستخدام واجهة canvas البرمجية في متصفح Internet Explorer. وبدلًا من كتابة الدالة السابقة بنفسك، تستطيع استعمال Modernizr للكشف عن دعم واجهة canvas البرمجية. if (Modernizr.canvas) { // لنرسم بعض الأشكال } else { // لا يوجد دعم لخاصية canvas :( } هنالك اختبارٌ منفصلٌ للتحقق من دعم واجهة canvas text البرمجية، وذلك في الفقرة الآتية. الكشف عن دعم النصوص في عنصر Canvas حتى لو كان يدعم متصفحك واجهة canvas البرمجية، فقد لا يدعم واجهة canvas text البرمجية، فنَمَتْ واجهة canvas البرمجية على مر الزمن، وأُضيفت دوال النصوص في فترةٍ لاحقة، لهذا هنالك بعض المتصفحات التي تدعم canvas لكن قبل أن يكتمل العمل على دوال النصوص. سنستخدم تقنية الكشف عن الدعم الثانية للتحقق من دعم المتصفح للعنصر canvas، فإن دعم متصفحك واجهة canvas البرمجية (API) فهذا يعني أن الكائن في DOM الذي يُمثِّل عنصر <canvas> سيملك الدالة getContext()، وإن لم يكن يدعم متصفحك واجهة canvas البرمجية، فسيملك الكائن المُنشَأ لعنصر <canvas> الخاصيات العامة لكل العناصر، وليس من بينها أي شيءٍ متعلقٍ بتقنية canvas. function supports_canvas_text() { if (!supports_canvas()) { return false; } var dummy_canvas = document.createElement('canvas'); var context = dummy_canvas.getContext('2d'); return typeof context.fillText == 'function'; } تبدأ الدالة السابقة بالتحقق من دعم canvas باستخدام دالة supports_canvas() التي رأيتها في القسم السابق، فإن لم يكن يدعم متصفحك واجهة canvas البرمجية، فهو بالتأكيد لن يدعم إضافة النصوص إلى عناصر canvas. if (!supports_canvas()) { return false; } ثم ستُنشِئ عنصر <canvas> جديد ثم تحاول الوصول إلى "لوحة الرسم" (drawing context)، ومن المؤكد أن ما سبق سيعمل دون مشاكل لأن الدالة supports_canvas() قد تحققت من وجود الدالة getContext() في جميع عناصر canvas. var dummy_canvas = document.createElement('canvas'); var context = dummy_canvas.getContext('2d'); وفي النهاية، ستتحقق إن كان لدى لوحة الرسم الدالة fillText()، فإن كانت تملكها، فهنالك دعمٌ للنصوص في canvas. return typeof context.fillText == 'function'; وبدلًا من كتابة الدالة السابقة بنفسك، تستطيع استعمال Modernizr للكشف عن دعم واجهة canvas text البرمجية. if (Modernizr.canvastext) { // لنضع بعض النصوص } else { // لا يوجد دعم لخاصية canvas text } الكشف عن دعم الفيديو أضافت HTML5 عنصرًا جديدًا هو <video> لتضمين مقاطع الفيديو في صفحات الويب، كان تضمين الفيديو في السابق من المستحيلات دون استخدام إضافات خارجية مثل Apple QuickTime® أو Adobe Flash®. صُمِّمَ عنصر <video> ليُستعمَل دون الحاجة إلى أيّة سكربتات للكشف عن الدعم، فيمكنك تحديد عدِّة مسارات لمقاطع الفيديو، وستختار المتصفحات التي تدعم HTML5 video أحدها بناءً على الصيغ التي تدعمها. المتصفحات التي لا تدعم HTML5 video ستتجاهل وسم <video> تمامًا. لكنك تستطيع استخدام ذلك لصالحك بإخبارها أن تُشغِّل المقطع باستخدام إضافة خارجية. برمَج Kroc Camen حلًا اسمه Video for Everybody! الذي يستخدم دعم الفيديو في HTML5 عند توفره، لكنه سيعود إلى استخدام QuickTime أو Flash في المتصفحات القديمة. لا يستعمل هذا الحل JavaScript مطلقًا، ويعمل نظريًا على أي متصفح، بما في ذلك متصفحات الهواتف المحمولة. إذا أردت القيام بأكثر من مجرد وضع الفيديو في صفحتك وتشغيله، فستحتاج إلى استخدام JavaScript، نستخدم التقنية الثانية للتحقق من دعم الفيديو. فإذا كان متصفحك يدعم HTML5 video، فإن كائن DOM الذي سيُنشئه المتصفح لتمثيل عنصر <video> سيملك الخاصية canPlayType(). وإن لم يكن يدعم متصفحك HTML5 video، فإن كائن DOM المُنشَأ لعنصر <video> سيملك الخاصيات العامة لأي عنصر. يمكنك التحقق من دعم الفيديو عبر هذه الدالة: function supports_video() { return !!document.createElement('video').canPlayType; } وبدلًا من كتابة الدالة السابقة بنفسك، تستطيع استعمال Modernizr للكشف عن دعم HTML5 video. if (Modernizr.video) { // لنشغل مقاطع الفيديو } else { // لا يوجد دعم للفيديو :( // ربما علينا استخدام QuickTime أو Flash بدلًا منه } سأشرح -في الدرس الخاص بالفيديو- حلًا آخر يستعمل تقنيات الكشف السابقة لتحويل عناصر <video> إلى مشغلات فيديو مبنية على تقنية Flash، وذلك لتشغيل الفيديو على المتصفحات التي لا تدعم HTML5 video. هنالك اختبارٌ منفصلٌ للتحقق من صيغ الفيديو التي يمكن للمتصفح تشغيلها، مشروحٌ في الفقرة الآتية. صيغ الفيديو مَثَلُ صيغ الفيديو كمَثَلِ اللغات المكتوبة، فقد تحتوي صحيفة إنكليزية على نفس المعلومات التي تحتويها صحيفة عربية، لكن إن كنت تقرأ اللغة العربية فقط، فستكون إحدى تلك الصحيفتين مفيدةً لك. ولتشغيل مقطع فيديو، يجب أن يفهم المتصفح "اللغة" التي كُتِبَ فيها هذا المقطع. تسمى "اللغة" التي تكتب فيها مقاطع الفيديو "بالمرماز" (codec) الذي هو الخوارزمية المستخدمة لترميز (encode) مقطع الفيديو إلى سلسلة من البتات، وهنالك عدِّة مرمزات، لكن أيها تستعمل؟ في الواقع، من المؤسف أن المتصفحات لم تتوافق على مرماز معيّن، لكنهم قللوا الخيارات إلى خيارين فقط. أحدهما يكلف مالًا (بسبب براءة الاختراع)، لكنه يعمل في متصفح Safari وفي iPhone (وهو يعمل أيضًا في مشغلات Flash إن كنت ستستعمل حلًا مثل Video for Everybody!). أما المزمار الآخر فهو مجاني ويعمل على المتصفحات مفتوحة المصدر مثل Chromium و Firefox. نستخدم التقنية الثالثة لمعرفة صيغ الفيديو المدعومة. وإذا كان متصفحك يدعم HTML5 video، فإن كائن DOM الذي سيُنشئه المتصفح لتمثيل عنصر <video> سيملك الخاصية canPlayType(). ستخبرك الطريقة الآتية إن كان متصفحك يدعم صيغة فيديو معينة. تتحقق هذه الدالة من دعم الصيغة المحمية بحقوق براءة الاختراع والمدعومة من أجهزة Mac و iPhone. function supports_h264_baseline_video() { if (!supports_video()) { return false; } var v = document.createElement("video"); return v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); } تبدأ هذه الدالة بالتحقق من دعم HTML video في المتصفح، وذلك باستخدام الدالة supports_video() التي رأيتها في القسم السابق. فإن لم يكن يدعم متصفحك HTML5 video، فهو بالتأكيد لن يدعم أيّة صيغة من صيغ الفيديو. if (!supports_video()) { return false; } ثم ستُنشِئ الدالة عنصر <video> (لكن دون أن تضيفه إلى الصفحة، أي أنه لن يكون مرئيًا) وتستدعي الدالة canPlayType()، من المؤكد وجود هذه الدالة لأن الدالة supports_video() تحققت منها في الخطوة السابقة. var v = document.createElement("video"); في الواقع، "صيغة الفيديو" هي مجموعة من عدِّة أشياء، فبكلامٍ تقني، أنت تسأل المتصفح إن كان يستطيع تشغيل فيديو H.264 بنمط Baseline مع صوت AAC LC في حاوية MPEG-4. return v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); لن تُعيد الدالة canPlayType() القيمة true أو false، وإنما ستعيد سلسلة نصية آخذةً بعين الاعتبار الطبيعة المُعقَّدة لصيغ الفيديو: "probably" إن كان المتصفح واثقًا أنه يستطيع تشغيل هذه الصيغة "maybe" إن ظن المتصفح أنه يستطيع تشغيل هذه الصيغة "" (سلسلة نصية فارغة) إن كان المتصفح متأكدًا أنه لن يستطيع تشغيل هذه الصيغة أما الدالة الآتية فهي تتحقق من دعم صيغة الفيديو المفتوحة المدعومة من متصفح Firefox وغيره من المتصفحات مفتوحة المصدر. العملية مماثلة تمامًا لما سبق، لكن الاختلاف الوحيد هو في السلسلة النصية التي تمررها إلى الدالة canPlayType(). تقنيًا، أنت تسأل المتصفح إن كان قادرًا على تشغيل فيديو Theora مع صوت Vorbis في حاوية Ogg. function supports_ogg_theora_video() { if (!supports_video()) { return false; } var v = document.createElement("video"); return v.canPlayType('video/ogg; codecs="theora, vorbis"'); } في النهاية، WebM هو مرماز فيديو جديد ومفتوح المصدر (وغير محمي ببراءة اختراع) تم دعمه في الإصدارات الجديدة من المتصفحات الحديثة، بما في ذلك Chrome، و Firefox، و Opera. ويمكنك استخدام نفس التقنية السابقة لاكتشاف دعم صيغة WebM. function supports_webm_video() { if (!supports_video()) { return false; } var v = document.createElement("video"); return v.canPlayType('video/webm; codecs="vp8, vorbis"'); } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.5 أو ما بعده) لاكتشاف الدعم لمختلف صيغ الفيديو في HTML5. if (Modernizr.video) { // لنشغل مقاطع الفيديو! لكن ما صيغتها؟ if (Modernizr.video.webm) { // لنجرب WebM } else if (Modernizr.video.ogg) { // لنجرب Ogg Theora + Vorbis في حاوية Ogg } else if (Modernizr.video.h264){ // لنجرب H.264 video + AAC audio في حاوية MP4 } } التخزين المحلي Local Storage يوفر التخزين المحلي لمواقع الويب طريقةً لتخزين المعلومات على حاسوبك ثم استعادتها لاحقًا، مفهومها مشابه لمفهوم "الكعكات" (cookies)، لكنها مصممة لكمية معلومات أكبر، فالكعكات محدودة الحجم، ويرسلها المتصفح إلى خادوم الويب في كل مرة يطلب فيها صفحة جديدة (مما يأخذ وقتًا أطول ويستهلك بيانات التراسل). أما تخزين HTML5 فهو يبقى في حاسوبك، وتستطيع مواقع الويب الوصول إليه عبر JavaScript بعد أن يتم تحميل الصفحة. س: هل التخزين المحلي هو جزءٌ من HTML5؟ ولماذا وضعوه في معيار منفصل؟ ج: الجواب المختصر هو "نعم"، التخزين المحلي هو جزءٌ من HTML5. أما الجواب الأطول فهو أن التخزين المحلي كان جزءًا من معيار HTML5 الرئيسي، لكنه أصبح معيارًا منفصلًا بسبب شكوى بعض الأشخاص في مجموعة عمل HTML5 أن HTML5 أصبحت كبيرة جدًا. حتى لو كان ذلك يشبه تقسيم شطيرة إلى قطع صغيرة لتقليل كمية الحريرات :-| حسنًا، أهلًا بك في العالم العجيب للمعايير القياسية. سنستخدم تقنية الكشف الأولى للتحقق من دعم المتصفح للتخزين المحلي، فإن دَعَم متصفحك التخزين المحلي، فستكون هنالك خاصية localStorage في كائن window العام. وإن لم يكن يدعم متصفحك التخزين المحلي، فلن تكون الخاصية localStorage معرَّفةً. وبسبب علِّة في الإصدارات القديمة من Firefox، سيسبب هذا الخيار بحدوث استثناء (exception) إن كانت الكعكات (cookies) مُعطَّلةً، لذلك وضعنا الاختبار في عبارة try..catch. function supports_local_storage() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch(e){ return false; } } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم للتخزين المحلي. if (Modernizr.localstorage) { // متوفرة! window.localStorage } else { // لا يوجد دعم للتخزين المحلي // ربما تجرب Gears أو مكتبة أخرى } لاحظ أنَّ JavaScript حساسة لحالة الأحرف، إذ تُدعى خاصية Modernizr "localstorage" (جميعها بأحرفٍ صغيرة)، أما خاصية DOM فهي window.localStorage (حرف S كبير). س: ما مدى أمان قاعدة بيانات التخزين المحلي في HTML5؟ هل يستطيع أحدٌ قراءتها؟ ج: أي شخص لديه وصولٌ فيزيائيٌ لحاسوبك قد يستطيع عرض (أو حتى تعديل) البيانات الموجودة في قاعدة بيانات التخزين المحلي في HTML5. ويستطيع أي موقع ويب قراءة وتخزين القيم الخاصة به، لكن لا يستطيع الوصول إلى القيم التي خزنتها المواقع الأخرى، وهذا يسمى same-origin restriction. Web Workers توفر ميزة Web Workers طريقةً قياسيةً لتشغيل JavaScript في الخلفية، إذ تستطيع تشغيل عدِّة "خيوط" (threads) التي تعمل في نفس الوقت تقريبًا (تذكر طريقة تشغيل الحاسوب لعدِّة تطبيقات معًا في آنٍ واحد)، تستطيع تلك الخيوط التي تعمل في الخلفية أن تجري عمليات حسابية معقدة، أو أن تجري طلبيات شبكيّة، أو أن تصل إلى التخزين المحلي في أثناء استجابة صفحة الويب إلى تفاعل المستخدم معها. نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم واجهة Web Worker البرمجية، وذلك إن وُجِدَت الخاصية Worker في الكائن العام window، وإن لم يكن يدعم متصفحك واجهة Web Worker البرمجية، فستكون خاصية Worker غير معرفة. function supports_web_workers() { return !!window.Worker; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم لواجهة Web Workers البرمجية. if (Modernizr.webworkers) { // window.Worker متوفرة! } else { // لا يوجد دعم لواجهة Web Workers :( // ربما تجرب Gears أو مكتبة أخرى } لاحظ أنَّ JavaScript حساسة لحالة الأحرف، إذ تُدعى خاصية Modernizr "webworkers" (جميعها بأحرفٍ صغيرة)، أما خاصية DOM فهي window.Worker (حرف W كبير في Worker). تطبيقات الويب دون اتصال Offline Web Applications يمكن ببساطة قراءة صفحات الويب الثابتة دون اتصال: اتصل إلى الإنترنت، حمِّل صفحة الويب، اقطع اتصالك بالإنترنت، ثم سافر إلى بلدٍ آخر، واقرأ الصفحة في وقت فراغك (يمكنك تخطي خطوة السفر إلى بلدٍ آخر لتوفير الوقت). لكن ماذا عن تطبيقات الويب مثل Gmail أو Google Docs؟ الفضل يعود إلى HTML5، التي تُمكِّن الجميع (وليس فقط Google) من بناء تطبيقات ويب تعمل دون اتصال. تبدأ تطبيقات الويب التي تعمل دون اتصال كتطبيقات تعمل بوجود اتصال بالإنترنت، ففي أول مرة تزور فيها تطبيق ويب يدعم العمل دون اتصل، فسيخبر الخادومُ متصفحَك ما هي الملفات التي يحتاج لها كي يعمل دون اتصال. قد تكون هذه الملفات من أي نوع: صفحات HTML، أو JavaScript، أو الصور أو حتى مقاطع الفيديو. وبعد أن ينزِّل متصفحك كل الملفات الضرورية، ستستطيع إعادة زيادة موقع الويب حتى لو لم تكن متصلًا بالإنترنت، وسيلاحظ متصفحك أنَّك غير متصل وسيستعمل الملفات التي نزلها من قبل. وعندما تتصل مجددًا بالإنترنت، فيمكن رفع أيّة تعديلات أجريتها على خادم الويب البعيد. نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم تشغيل تطبيقات الويب دون اتصال، وذلك إن وُجِدَت الخاصية applicationCache في الكائن العام window، وإن لم يكن يدعم متصفحك تشغيل تطبيقات الويب دون اتصال، فستكون خاصية applicationCache غير معرفة؛ يمكنك التحقق من دعم تشغيل تطبيقات الويب دون اتصال مع هذه الدالة: function supports_offline() { return !!window.applicationCache; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم لتشغيل تطبيقات الويب دون اتصال. if (Modernizr.applicationcache) { // window.applicationCache متوفرة! } else { // لا يوجد دعم للتطبيقات دون اتصال :( // ربما تجرب Gears أو مكتبة أخرى } لاحظ أنَّ JavaScript حساسة لحالة الأحرف، إذ تُدعى خاصية Modernizr "applicationcache" (جميعها بأحرفٍ صغيرة)، أما خاصية DOM فهي window.applicationCache (حرف c كبير في Cache). تحديد الموقع الجغرافي Geolocation يفيد تحديد الموقع الجغرافي في معرفة أين أنت في هذا الكوكب و (اختياريًا) مشاركة تلك المعلومات مع الأشخاص الذين تثق بهم، هنالك أكثر من طريقة لمعرفة أين أنت: عبر عنوان IP، أو عبر اتصال شبكتك اللاسلكية، أو أيّ برج تغطية خلوية تتصل منه، أو عبر عتاد GPS الذي يحسب إحداثيات موقعك الحالي عبر المعلومات التي ترسلها الأقمار الاصطناعية في السماء. س: هل تحديد الموقع الجغرافي جزءٌ من HTML5؟ ولماذا تتحدث عنه إذًا؟ ج: لقد أضيف دعم تحديد الموقع الجغرافي من المتصفحات مع ميزات HTML5 الجديدة. لكن إذا ابتغينا الدقة، يُوفَّر معيار تحديد الموقع الجغرافي من مجموعة عمل Geolocation، التي هي مجموعة عمل منفصلة عن مجموعة عمل HTML5، لكننا سنتحدث عن تحديد الموقع الجغرافي في هذه السلسلة على أيّة حال، لأنه جزءٌ من التطوير الذي يحدث في الويب في الوقت الراهن. نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم واجهة تحديد الموقع الجغرافي البرمجية، وذلك إن وُجِدَت الخاصية geolocation في الكائن العام navigator، وإن لم يكن يدعم متصفحك تحديد الموقع الجغرافي، فستكون خاصية geolocation غير معرفة؛ يمكنك التحقق من دعم تحديد الموقع الجغرافي مع هذه الدالة: function supports_geolocation() { return !!navigator.geolocation; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr لاكتشاف الدعم لتحديد الموقع الجغرافي. if (Modernizr.geolocation) { // لنكتشف أين أنت الآن! } else { // لا يوجد دعم لتحديد الموقع الجغرافي :( // ربما تجرب Gears أو مكتبة أخرى } إن لم يدعم متصفحك واجهة تحديد الموقع الجغرافي البرمجية داخليًا، فلا تيأس. فهنالك Gears التي هي إضافة مفتوحة المصدر للمتصفحات من Google التي تعمل على ويندوز و Mac OS X ولينُكس والهواتف العاملة بنظامَي ويندوز وأندرويد. حيث توفر ميزاتٍ للمتصفحات القديمة التي لا تدعم الأشياء الجديدة التي تحدثنا عنها في هذا الدرس. إحدى الميزات التي توفرها Gears هي تحديد الموقع الجغرافي، لكنها ليست مطابقة لواجهة navigator.geolocation البرمجية، لكنها تخدم نفس الغرض. هنالك واجهات برمجية لتحديد المواقع لأنظمة الهواتف القديمة مثل BlackBerry، و Nokia، و Palm، و OMTP BONDI. سيشرح الدرس الخاص بتحديد الموقع الجغرافي بالتفصيل كيفية استخدام مختلف الواجهات البرمجية السابقة. أنواع الإدخال في النماذج Input Types أنت تعرف الكثير عن نماذج الويب، صحيح؟ أنشِئ عنصر <form> ثم أضف بعض عناصر <input type="text"> إليه وربما عنصر <input type="password">، ثم أنهِ النموذج بزر <input type="submit">. حسنًا، ذلك جزءٌ يسيرٌ من النماذج، إذ أضافت HTML5 حوالي ثلاثة عشر نوعًا من أنواع المدخلات التي يمكنك استعمالها في نماذجك. <"input type="search"> لصناديق البحث <"input type="number"> لإدخال الأرقام <"input type="range"> للمزلاج (slider) لتحديد مجال <"input type="color"> لاختيار الألوان <"input type="tel"> لأرقام الهواتف <"input type="url"> لعناوين الويب <"input type="email"> لعناوين البريد الإلكتروني <"input type="date"> التقويم لاختيار التاريخ <"input type="month"> للأشهر <"input type="week"> للأسابيع <"input type="time"> لبصمات الوقت <"input type="datetime"> لبصمات الوقت والتاريخ بدقة <"input type="datetime-locale"> للوقت والتاريخ المحليين سنستخدم التقنية الرابعة لاكتشاف أنواع الحقول المدعومة في النماذج. في البداية سننشِئ عنصر <input> في الذاكرة، وسيكون نوع الحقل الافتراضي لجميع عناصر <input> هو "text"، وسيتضح لك لماذا هذا مهمٌ جدًا. var i = document.createElement("input"); ثم سنضبط خاصية type في عنصر <input> إلى نوع حقل الإدخال الذي تريد معرفة إن كان مدعومًا أم لا. i.setAttribute("type", "color"); إن كان يدعم متصفحك نوع حقل الإدخال المعين، فستحتفظ خاصية type بالقيمة التي ضبطتها، أما إن لم يكن يدعم متصفحك نوع الحقل المعيّن، فسيتجاهل القيمة التي ضبطتها وستبقى قيمة الخاصية type مساويةً إلى "text". return i.type !== "text" بدلًا من كتابة 13 دالة منفصلة يدويًا، تستطيع استخدام Modernizr لاكتشاف الدعم لجميع أنواع حقول الإدخال المُعرَّفة في HTML5. يُعيد Modernizr استخدام عنصر <input> وحيد لكي يكتشف ما هي أنواع حقول الإدخال المدعومة. ثم يبني جدولًا من نوع hash باسم Modernizr.inputtypes يحتوي على 13 مفتاح (خاصيات type في HTML5) و 13 قيمة منطقية (أي true إن كان الحقل مدعومًا، أو false إن لم يكن كذلك). if (!Modernizr.inputtypes.date) { // لا يوجد دعم لحقل <input type="date"> :( // ربما تستعمل مكتبة Dojo أو jQueryUI } النص البديل Placeholder بالإضافة إلى أنواع حقول الإدخال الجديدة، تضمنت HTML5 بعض الإضافات الصغيرة للنماذج. أحدها هو إمكانية وضع نص بديل (placeholder) في حقل الإدخال. يُعرَض النص البديل في حقل الإدخال لطالما كان الحقل فارغًا، وبمجرد أن تكتب شيئًا في الحقل، فسيختفي ذاك النص البديل. هنالك لقطات للشاشة في درس نماذج الويب يمكنك النظر إليها إن واجهت صعوبةً في تخيله. سنستخدم تقنية الكشف عن الدعم الثانية للتحقق من دعم المتصفح للنص البديل في حقول الإدخال، فإن دعم متصفحك النص البديل فهذا يعني أن الكائن في DOM الذي يُمثِّل عنصر <input> سيملك الخاصية placeholder (حتى لو لم تضع خاصية placeholder في شيفرة HTML)، وإن لم يكن يدعم متصفحك النص البديل، فلن يملك الكائن المُنشَأ لعنصر <input> الخاصية placeholder. function supports_input_placeholder() { var i = document.createElement('input'); return 'placeholder' in i; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم للنص البديل في حقول الإدخال. if (Modernizr.input.placeholder) { // يمكنك استعمال النص البديل في حقول الإدخال! } else { // لا يوجد دعم للنص البديل :( // استعمل سكربت لفعل ذلك } التركيز التلقائي على النماذج autofocus تستعمل مواقع الويب JavaScript للتركيز (focus) على حقل من حقول الإدخال في نماذج الويب. على سبيل المثال، الصفحة الرئيسية لمحرك البحث Google ستُركِّز تلقائيًا على حقل البحث كي تستطيع كتابة ما الذي تريد البحث عنه مباشرةً دون الحاجة إلى النقر على حقل الإدخال، ربما هذا ملائم للكثيرين، لكنه مزعج للمستخدمين المتقدمين أو لأولي الاحتياجات الخاصة، فإن ضغطت على زر المسافة (space) متوقعًا أن تُمرِّر إلى الأسفل، فلن يتم ذلك، لوجود المؤشر في حقل إدخال (وستُكتب مسافة فارغة في حقل الإدخال بدلًا من التمرير)، وإن ركزت على حقل إدخال مختلف في أثناء تحميل الصفحة، فسيحرك سكربت التركيز التلقائي التركيز إلى الحقل المُحدَّد بعد إكمال تحميل الصفحة، مما يؤدي إلى كتابتك في المكان الخاطئ. ولأن التركيز التلقائي كان يتم عبر JavaScript، فمن الصعب التعامل مع جميع الحالات الغريبة، وليس هنالك ملجأ من التركيز التلقائي للحقول لِمَن لا يريد ذلك! ولحل هذه المشكلة، قدَّمَت HTML5 خاصية autofocus في جميع عناصر نماذج الويب. وهذه الخاصية تفعل تمامًا كما هو واضح من اسمها: تنقل التركيز إلى حقل إدخال معين، ولأنها من شيفرة HTML بدلًا من كونها سكربت، فإن سلوكها سيكون متناغمًا ومتماثلًا في كل مواقع الويب، ويمكن لصانعي المتصفحات (أو مطوري الإضافات) توفير طريقة للمستخدمين لكي يُعطِّلوا إمكانية التركيز التلقائي. سنستخدم تقنية الكشف عن الدعم الثانية للتحقق من دعم المتصفح للتركيز التلقائي في حقول الإدخال، فإن دعم متصفحك التركيز التلقائي فهذا يعني أن الكائن في DOM الذي يُمثِّل عنصر <input> سيملك الخاصية autofocus (حتى لو لم تضع خاصية autofocus في شيفرة HTML)، وإن لم يكن يدعم متصفحك التركيز التلقائي، فلن يملك الكائن المُنشَأ لعنصر <input> الخاصية autofocus. يمكنك اكتشاف دعم التركيز التلقائي عبر هذه الدالة: function supports_input_autofocus() { var i = document.createElement('input'); return 'autofocus' in i; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم للتركيز التلقائي في حقول الإدخال. if (Modernizr.input.autofocus) { // التركيز التلقائي يعمل! } else { // التركيز التلقائي غير مدعوم :( // استعمل سكربت لفعل ذلك } البيانات الوصفية Microdata البيانات الوصفية (Microdata) هي الطريقة القياسية لتوفير هيكليّة معنوية لصفحات الويب. على سبيل المثال، يمكنك استعمال البيانات الوصفية لتصرِّح أن صورةً ما مرخَّصة بإحدى رخص المشاع الإبداعي. وكما سترى لاحقًا في هذه السلسلة، يمكنك استعمال البيانات الوصفية لتوصيف صفحة "معلومات عني"، فيمكن للمتصفحات –أو لإضافات المتصفحات– أو لمحركات البحث تحويل تلك البيانات الوصفية إلى vCard، التي هي صيغة معيارية لمشاركة معلومات الاتصال؛ يمكنك أيضًا تعريف أنواع خاصة بك من البيانات الوصفية. معيار البيانات الوصفية في HTML5 يتضمن شيفرات HTML (تستعملها محركات البحث بشكلٍ أساسي) ومجموعة من دوال DOM (تستعملها المتصفحات بشكلٍ أساسي). لا حرج في تضمين البيانات الوصفية في صفحات الويب، فهي مجرد خاصيات ذات معنى خاص، وستتجاهلها محركات البحث التي لا تستطيع تفسير البيانات الوصفية. لكن إن كنت تريد الوصول إلى أو تعديل البيانات الوصفية عبر DOM، فعليك أن تتحقق أن متصفحك يدعم واجهة البيانات الوصفية البرمجية (API). نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم واجهة البيانات الوصفية البرمجية، وذلك إن وُجِدَت الدالة getItems() في الكائن العام document، وإن لم يكن يدعم متصفحك البيانات الوصفية، فستكون الدالة getItems() غير معرفة. function supports_microdata_api() { return !!document.getItems; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr لاكتشاف الدعم للبيانات الوصفية. if (Modernizr.microdata) { // هنالك دعمٌ للبيانات الوصفية! } else { // البيانات الوصفية غير مدعومة :( } التأريخ History API واجهة التأريخ البرمجية في HTML5 هي طريقة معيارية لتعديل تأريخ (history) المتصفح عبر السكربتات، جزءٌ من هذه الواجهة -التنقل في التأريخ- كان متوفرًا في الإصدارات السابقة من HTML. أما الجزء الجديد في HTML5 هو طريقة إضافة مدخلات جديدة إلى تأريخ المتصفح، والاستجابة عندما تُحذَف تلك المدخلات عبر ضغط المستخدم لزر الرجوع، وهذا يعني أن URL سيبقى يعمل عمله كمُعرِّف فريد للمورد الحالي، حتى في التطبيقات التي تعتمد اعتمادًا كبيرًا على السكربتات التي لا تجري عملية تحديث لكامل الصفحة. نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم واجهة التأريخ البرمجية، وذلك إن وُجِدَت الدالة pushState() في الكائن العام history، وإن لم يكن يدعم متصفحك واجهة التأريخ البرمجية، فستكون الدالة pushState() غير معرفة. function supports_history_api() { return !!(window.history && history.pushState); } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.6 أو ما بعده) لاكتشاف الدعم لواجهة التأريخ البرمجية. if (Modernizr.history) { // يمكنك تعديل تأريخ المتصفح! } else { // لا يوجد دعم لتعديل التأريخ :( // استعمل مكتبة مثل History.js } مراجع إضافية مكتبات JavaScript: Modernizr، مكتبة اكتشاف الدعم لميزات HTML5 geo.js، مكتبة لإضافة الدعم لواجهة تحديد المواقع Video for Everybody! ترجمة -وبتصرّف- للفصل Detecting HTML5 Features من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال التالي: الرسم عبر عنصر canvas في HTML5 المقال السابق: نظرة على تاريخ HTML - الجزء الثاني النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
-
- local storage
- فيديو
- (و 9 أكثر)
-
كانت البرمجيات المكتبية تتفوق على تطبيقات الويب بإمكانية تخزين المعلومات محليًا تخزينًا دائمًا؛ حيث يوفِّر نظام التشغيل عادةً طبقةً وسيطةً لتخزين وقراءة بيانات خاصة بالتطبيق مثل الإعدادات وحالة التشغيل، وقد تُخزَّن هذه القيم في سجل النظام (registry) أو ملفات ini أو ملفات XML أو في مكانٍ آخر وفقًا للتقاليد المُتبَعة في نظام التشغيل؛ أما لو احتاج التطبيق المكتبي إلى تخزينٍ محليٍ أكثر تعقيدًا من مجرد تخزين البيانات على شكل "المفتاح/القيمة"، فيمكنك أن تُضمِّن قاعدة البيانات الخاصة بتطبيقك، أو أن تبتكر صيغة ملفات للتخزين، أو غيره ذلك من الحلول. لكن على مرِّ التاريخ، لم تملك تطبيقات الويب هذا الامتياز، وعلى الرغم من ابتكار الكعكات (Cookies) في بدايات الويب لكن كان الغرض منها هو التخزين المحلي لكميةٍ قليلةٍ من البيانات، إلا أنَّ هنالك ثلاثة أسباب تمنعنا من استخدامها لهذا الغرض: ستُضمَّن الكعكات في كل طلبية HTTP، مما يؤدي إلى حدوث بطء في تطبيق الويب بسبب نقل نفس البيانات مرارًا وتكرارًا دون داعٍ ستُضمَّن الكعكات في كل طلبية HTTP، وهذا يعني إرسال البيانات دون تشفير عبر الإنترنت (إلا إذا كان يُخدَّم تطبيق الويب عندك عبر طبقة SSL) المساحة التخزينية للكعكات محدودة إلى حوالي 4 كيلوبايت من البيانات، وهي كافية لإبطاء تطبيقك (انظر أعلاه)، لكنها ليست كافية لتخزين شيءٍ مفيدٍ ما نحتاج له حقًا هو: مساحة تخزينية كبيرة موجودة على جهاز العميل يمكن أن تبقى حتى بعد تحديث الصفحة لن تُنقَل طوال الوقت إلى الخادوم جميع المحاولات -قبل HTML5- لتحقيق ما سبق كانت غير مرضية لمختلف الأسباب. لمحة تاريخية عن محاولات تخزين البيانات محليا قبل HTML5 لم يكن هنالك سوى متصفح Internet Explorer في بدايات الويب، أو على الأقل هذا ما حاولت مايكروسوفت إيهام العالم به، ولتحقيق هذه الغاية، وكجزءٍ من الحرب الكبرى الأولى للمتصفحات، ابتكرت مايكروسوفت ميزاتٍ كثيرة ووضعتها في متصفحها -Internet Explorer- الذي أنهى تلك الحرب. واحدة من تلك الميزات تُسمى "DHTML Behaviors" وكان أحد خصائصها يُدعى userData. تسمح ميزة userData لصفحات الويب أن تُخزِّن 64 كيلوبايت كحد أقصى لكل نطاق (domain)، وذلك عبر هيكلية تعتمد على XML (أما النطاقات الموثوقة، مثل مواقع إنترانت [intranet]، فتستطيع تخزين 10 أضعاف الكمية؛ وكانت 640 كيلوبايت في ذاك الوقت أكثر من كافية). لم يوفِّر IE أي مربع حوار لأخذ إذن المستخدم، ولم تكن هنالك إمكانية لزيادة كمية البيانات التي يمكن تخزينها محليًا. في عام 2002، أضافت شركة Adobe ميزةً في Flash 6 التي اكتسبت الاسم "Flash cookies"، لكن هذه الميزة كانت معروفةً ضمن بيئة Flash بالاسم Local Shared Objects؛ باختصار، تسمح هذه الميزة لكائنات Flash أن تُخزِّن 100 كيلوبايت من البيانات كحد أقصى لكل نطاق. طوَّر Brad Neuberg نموذجًا أوليًا لجسرٍ يربط تقنية Flash بلغة JavaScript أسماه AMASS (اختصار للعبارة AJAX Massive Storage System)، لكنه كان محدودًا بسبب بعض المشكلات في تصميم صيغة Flash. لكن في 2006، ومع مجيء ExternalInterface في Flash 8، أصبح من الممكن بسهولة وسرعة الوصول إلى الكائنات المشتركة المخزنة محليًا (Local Shred Objects أو اختصارًا LSOs) من JavaScript؛ ولهذا السبب أعاد Brad كتابة AMASS ودمجها مع Dojo Toolkit تحت الاسم dojox.storage. وبهذا مَنَحَ Flash كل نطاق 100 كيلوبايت من التخزين المحلي "مجانًا"، وستُطلَب موافقة المستخدم عند كل زيادة في تخزين البيانات (1 ميغابايت، 10 ميغابايت، وهكذا). في عام 2007، أصدرت Google إضافة Gears، التي هي إضافة مفتوحة المصدر للمتصفحات غرضها هو توفير إمكانيات إضافية إليها (تحدثنا سابقًا عن Gears في سياق /توفير واجهة برمجية لتحديد الموقع الجغرافي لمتصفح IE/). توفِّر Gears واجهة برمجية (API) للوصول إلى قاعدة بيانات SQL مدمجة فيها مبنيةٌ على محرك قواعد البيانات SQLite. يمكن لإضافة Gears تخزين كمية غير محدودة من البيانات لكل نطاق في جداول قاعدة بيانات SQL بعد أخذ إذن المستخدم. في تلك الأثناء، أكمل Brad Neuberg وآخرون مشوارهم في تطوير dojox.storage لتوفير واجهة موحَّدة لمختلف الإضافات، وبحلول 2009 أصبح بمقدور dojox.storage أن تكتشف دعم (وتوفر واجهة موحدة) لبرمجية Adobe Flash و Gears و Adobe AIR والنموذج الأولي من التخزين المحلي في HTML5 الذي كان مُطبَّقًا في الإصدارات القديمة من Firefox فقط. عندما تنظر إلى تلك الحلول، فستكتشف أنَّ جميعها كان خاصًا بمتصفح معيّن أو كان يتبع لإضافة خارجية. وعلى الرغم من الجهود البطولية لتوحيد تلك الاختلافات (dojox.storage) إلا أنَّ تلك الحلول تملك واجهات برمجية مختلفة جذريًا عن بعضها، ولكلٍ منها حدود قصوى لمقدار المساحة التخزينية المتوفرة، ولكلٍ منها تجربة مستخدم مختلفة. هذه هي المشكلة التي أتت HTML5 لحلها: توفير واجهة برمجية معيارية، ومطبَّقة في جميع المتصفحات، دون الحاجة إلى استخدام إضافات خارجية. مدخل إلى التخزين المحلي في HTML5 ما أشير إليه على أنَّه "التخزين المحلي في HTML5" (HTML5 Storage) هو مواصفة باسم "Web Storage" التي كانت جزءًا من معيار HTML5، لكنها انقسمت وأصبحت معيارًا مستقلًا لأسباب ليست مهمة. بعض الشركات المسؤولة عن المتصفحات تطلِق عليها الاسم "التخزين المحلي" (Local Storage) أو "تخزين DOM" (DOM Storage). ازداد تعقيد موضوع التسميات خصوصًا بعد ظهور عدد من المعايير الجديدة التي سأناقشها في نهاية هذا الدرس. إذًا، ما هو التخزين المحلي في HTML؟ بشكل مبسّط: هو طريقة تتمكن صفحات الويب من خلالها أن تُخزِّن البيانات على شكل "المفتاح/القيمة" محليًا داخل متصفح الويب في حاسوب العميل. ومثل الكعكات، ستبقى البيانات موجودةً حتى بعد إغلاقك للسان الصفحة في المتصفح، أو إغلاق المتصفح. لكن على عكس الكعكات، لن تُرسَل البيانات تلقائيًا إلى خادوم الويب البعيد؛ وعلى النقيض من كل المحاولات السابقة لتوفير ميزة التخزين المحلي، هذه الميزة موجودة داخليًا في متصفحات الويب، لذلك ستكون متاحة للاستخدام حتى لو لم تتوفر إضافاتٌ خارجيةٌ للمتصفح. ما هي المتصفحات التي تدعمها؟ حسنًا، التخزين المحلي في HTML5 مدعومٌ من أغلبية المتصفحات، وحتى القديمة منها. IE Firefox Safari Chrome Opera iPhone Android 8.0+ 3.5+ 4.0+ 4.0+ 10.5+ 2.0+ 2.0+ تستطيع الوصول إلى التخزين المحلي في HTML5 في شيفرات JavaScript عبر الكائن localStorage الموجود في الكائن العام window؛ لكن قبل أن تستخدمها، عليك أن تكتشف دعم المتصفح لها. function supports_html5_storage() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } } لكن بدلًا من كتابة الدالة السابقة يدويًا، يمكنك استخدام Modernizr لاكتشاف دعم التخزين المحلي في HTML5. if (Modernizr.localstorage) { // window.localStorage متوفرة! } else { // لا يوجد دعم للتخزين المحلي :( // ربما تجرب dojox.storage أو مكتبة أخرى } استخدام التخزين المحلي في HTML5 يعتمد التخزين المحلي في أساسه على تخزين البيانات على شكل "مفتاح/قيمة". أي أنَّك تُخزِّن البيانات في مفتاح له اسم مُميِّز، ثم تستطيع الحصول على تلك البيانات مرةً أخرى باستخدام نفس المفتاح. ذاك المفتاح هو سلسلة نصية، ويمكن أن تكون البيانات المُخزَّنة من أي نوع تدعمه لغة JavaScript بما في ذلك السلاسل النصية والقيم المنطقية (true و false) أو الأعداد الصحيحة أو الأعداد العشرية؛ لكن في الواقع، ستُخزَّن البيانات كسلسلة نصية، وهذا يعني أنَّه لو لم تكن القيمة المُخزَّنة نصيةً فستحتاج إلى استعمال دوال مثل parseInt() أو parseFloat() لكي تحوِّل البيانات التي حصلت عليها إلى نوع البيانات الذي تريده. interface Storage { getter any getItem(in DOMString key); setter creator void setItem(in DOMString key, in any data); }; سيؤدي استدعاء الدالة setItem() مع تمرير مفتاح موجود مسبقًا إلى إعادة الكتابة فوق القيمة السابقة دون إشعار. وسيؤدي استدعاء الدالة getItem() مع تمرير مفتاح غير موجود إلى إعادة null بدلًا من رمي استثناء (throw an exception). وكما هو الحال مع بقية الكائنات في JavaScript، يمكنك أن تُعامِل الكائن localStorage على أنَّه مصفوفة ترابطية (associative array). فبدلًا من استخدام الدالتين getItem() و setItem()، تستطيع بكل بساطة أن تستعمل الأقواس المربعة (التي تستعملها للوصول إلى عناصر المصفوفات). يمكن على سبيل المثال أن نُعيد كتابة هذه الشيفرة: var foo = localStorage.getItem("bar"); localStorage.setItem("bar", foo); var foo = localStorage["bar"]; localStorage["bar"] = foo; هنالك دوالٌ أخرى لحذف قيمة مرتبطة بمفتاح معيّن، ولحذف كل ما هو مُخزَّنٌ محليًا (وهذا يعني حذف كل المفاتيح والقيم معًا). interface Storage { deleter void removeItem(in DOMString key); void clear(); }; لن يؤدي استدعاء الدالة removeItem() مع تمرير مفتاح غير موجود إلى فعل أي شيء. وأخيرًا، هنالك خاصية للحصول على العدد الكلي للقيم المُخزَّنة محليًا، ودالة للحصول على اسم كل مفتاح عبر تمرير فهرسه المكاني*. interface Storage { readonly attribute unsigned long length; getter DOMString key(in unsigned long index); }; لو استدعيتَ الدالة key() مع فهرس لا يقع بين 0 - 1 فستُعيد الدالة null. تتبّع التغييرات في مساحة التخزين المحلي إذا أردت أن تتبَّع التغييرات في مساحة التخزين (storage area) برمجيًا، فعليك أن تستعمل الحَدَث storage، الذي يُفعَّل (fired) في الكائن العام window في كل مرة تُستدعى فيها الدالة setItem() أو removeItem() أو clear() وتجري تلك الدالة تغييرًا ما. فعلى سبيل المثال، لو أعدتَ ضبط قيمة موجودة مسبقًا وكانت القيمة الجديدة مساوية للقيمة القديمة، أو استدعيت الدالة clear() لكن لم تكن هنالك أيّة قيم في مساحة التخزين، فلن يُفعَّل الحدث storage، لعدم تغيّر شيء في مساحة التخزين. الحدث storage مدعوم في كل متصفح يدعم الكائن localStorage، وهذا يتضمن Internet Explorer 8، لكن IE 8 لا يدعم الدالة المعيارية من W3C لمراقبة الأحداث addEventListener (لكنها أُضيفت في نهاية المطاف في IE 9)؛ ولهذا، إذا أردت مراقبة تفعيل الحدث storage فعليك أن تكتشف ما هي آلية الأحداث التي يدعمها المتصفح أولًا (إذا فعلتَ هذا من قبل مع الأحداث الأخرى، فيمكنك تخطي هذه الفقرة والانتقال إلى آخر القسم. مراقبة الحدث storage مماثلة تمامًا لعملية مراقبة الأحداث الأخرى التي سبقَ وأن راقبتَها؛ وإذا كنتَ تُفضِّل استخدام jQuery أو مكتبة JavaScript أخرى لتسجيل دوال مراقبة الأحداث، فتستطيع فعل ذلك مع الحدث storage أيضًا). if (window.addEventListener) { window.addEventListener("storage", handle_storage, false); } else { window.attachEvent("onstorage", handle_storage); }; ستُستدعى الدالة handle_storage مع تمرير كائن من نوع StorageEvent، عدا في متصفح Internet Explorer حيث يُخزَّن الكائن في window.event. function handle_storage(e) { if (!e) { e = window.event; } } سيكون المتغير e -عند هذه النقطة- كائنًا من نوع StorageEvent، الذي لديه الخاصيات المبيّنة في الجدول الآتي: الخاصية النوع الشرح key سلسلة نصية مفتاح القيمة التي أُضيفَت أو حُذِفَت أو عدِّلَت oldValue أي نوع القيمة (التي كُتِبَ فوقها)، أو null إذا أُضيف عنصرٌ جديد newValue أي نوع القيمة الجديدة، أو null إن حُذِفَ عنصرٌ ما url* سلسلة نصية الصفحة التي تحتوي على الدالة التي أجرت هذا التغيير ملاحظة: كان اسم الخاصية url الأصلي هو uri، وذلك لأنَّ بعض المتصفحات امتلكت هذه الخاصية قبل تغيير مواصفة التخزين المحلي. لأكبر قدر من التوافقية، عليك أن تتحقق من وجود الخاصية url، فإن لم تكن موجودًا فتحقق من قيمة الخاصية uri. لا يمكن إلغاء الأحداث في الحدث storage. فلا توجد طريقة من داخل الدالة handle_storage تستطيع إيقاف تغيير ما من الحدوث. بكل بساطة، هذه طريقة لكي يخبرك المتصفح: "هذا ما حصل لتوِّه، لا يمكنك فعل أي شيء تجاهه؛ كل ما أستطيع فعله هو إخبارك ما الذي حدث". المحدوديات في المتصفحات الحالية في حديثي عن اللمحة التاريخية عن محاولات تخزين البيانات محليًا باستخدام إضافات خارجية، حرصتُ على ذكر محدوديات كل تقنية من تلك التقنيات، مثل محدودية المساحة التخزينية. لكنني لم أذكر شيئًا عن محدوديات التخزين المحلي في HTML5 المعياري. سأعطيك الأجوبة أولًا ثم سأشرحها. الأجوبة هي -بترتيبها حسب الأهمية-: "5 ميغابايت"، و"QUOTA_EXCEEDED_ERR" و "لا". "5 ميغابايت" هي المساحة التي يُسمَح لكل موقع بالحصول عليها افتراضيًا، وهذه القيمة متساوية -على غير العادة- بين المتصفحات، على الرغم من أنَّها مذكورة في مواصفة التخزين المحلي في HTML5 على أنَّها "اقتراح". ابقِ في ذهنك أنَّك تُخزِّن سلاسل نصية، ولا تخزِّن البيانات بصيغتها الأصلية، فلو كنت تُخزِّن الكثير من الأعداد الصحيحة (integers) أو العشرية (floats)، فسيكون الفرق في طريقة تمثيل البيانات مؤثرًا، إذ يُخزَّن كل رقم من عدد عشري كمحرف (character)، وليس بالتمثيل التقليدي لعدد عشري. "QUOTA_EXCEEDED_ERR" هو الاستثناء (exception) الذي سيُرمى (thrown) عندما تتجاوز حد 5 ميغابايت. أما "لا" فهو الجواب على السؤال البدهي الذي سيخطر ببالك: "هل يمكنني طلب المزيد من المساحة التخزينية من المستخدم؟" إلى حد الساعة، لا تدعم أيّة متصفحات أي آلية يتمكن خلالها مطورو الويب من طلب المزيد من المساحة التخزينية. لكن بعض المتصفحات (مثل Opera أو Firefox) تسمح للمستخدم أن يتحكم بالحدّ الأقصى للتخزين المحلي، لكن هذا منوطٌ بالمستخدم تمامًا، ولا يمكنك -كمطوِّر ويب- الاعتماد على ذلك لبناء تطبيقك. مثال عملي عن استخدام التخزين المحلي لنأخذ مثالًا عمليًا عن التخزين المحلي في HTML. هل تتذكر لعبة الضامة التي بنيناها في الدرس الذي يتحدث عن canvas؟ هنالك مشكلة صغيرة مع هذه اللعبة: ستخسر تقدّمك في اللعبة عندما تُغلِق نافذة المتصفح. لكن باستخدام التخزين في HTML5، سنستطيع حفظ التقدّم محليًا داخل المتصفح. هذا مثالٌ حيّ للعبة بعد التعديل. حرِّك بعض القطع، ثم أغلق لسان الصفحة (أو المتصفح)، ثم أعد فتح الصفحة. فإذا كان يدعم متصفحك التخزين المحلي، فيجب أن تتذكر الصفحة السابقة خطواتك التي أجريتها في اللعبة، بما في ذلك عدد الخطوات التي تحركت بها، ومكان كل قطعة على رقعة اللعب، وحتى آخر قطعة قمتَ بتحديدها. ما هي الآلية التي اتبعناها لفعل ذلك؟ سنستدعي الدالة الآتية في كل مرّة يطرأ فيها تغيير داخل اللعبة: function saveGameState() { if (!supportsLocalStorage()) { return false; } localStorage["halma.game.in.progress"] = gGameInProgress; for (var i = 0; i < kNumPieces; i++) { localStorage["halma.piece." + i + ".row"] = gPieces[i].row; localStorage["halma.piece." + i + ".column"] = gPieces[i].column; } localStorage["halma.selectedpiece"] = gSelectedPieceIndex; localStorage["halma.selectedpiecehasmoved"] = gSelectedPieceHasMoved; localStorage["halma.movecount"] = gMoveCount; return true; } كما لاحظت، تستعمل الدالة السابقة الكائن localStorage لتخزين أنَّ المستخدم قد بدأ اللعب (المفتاح gGameInProgress، الذي هو قيمة منطقية [Boolean]). ثم ستدور حلقة for على جميع القطع (المتغير gPieces، الذي هو مصفوفة في لغة JavaScript) ثم يحفظ رقم السطر والعمود لكل قطعة؛ ثم تحفظ الدالة بعض المعلومات الإضافية عن اللعبة، بما في ذلك القطعة التي تم تحديدها (القيمة gSelectedPieceIndex، التي هي رقمٌ صحيح [integer])، وفيما إذا كانت القطعة في منتصف سلسلة من القفزات (القيمة gSelectedPieceHasMoved، التي هي قيمة منطقية)، والعدد الكلي للحركات التي قام بها اللاعب (القيمة gMoveCount، التي هي عدد صحيح). وعند تحميل الصفحة، وبدلًا من الاستدعاء التلقائي للدالة newGame() التي ستُعيد ضبط جميع المتغيرات إلى قيم مُحدَّدة مسبقًا، فسنستدعي الدالة resumeGame(). التي تتحقق -باستخدام التخزين المحلي في HTML5- فيما إذا كانت هنالك نسخة محفوظة من اللعبة مُخزَّنةٌ محليًا؛ فإن وُجِدَت، فستُستعاد تلك القيم باستخدام الكائن localStorage. function resumeGame() { if (!supportsLocalStorage()) { return false; } gGameInProgress = (localStorage["halma.game.in.progress"] == "true"); if (!gGameInProgress) { return false; } gPieces = new Array(kNumPieces); for (var i = 0; i < kNumPieces; i++) { var row = parseInt(localStorage["halma.piece." + i + ".row"]); var column = parseInt(localStorage["halma.piece." + i + ".column"]); gPieces[i] = new Cell(row, column); } gNumPieces = kNumPieces; gSelectedPieceIndex = parseInt(localStorage["halma.selectedpiece"]); gSelectedPieceHasMoved = localStorage["halma.selectedpiecehasmoved"] == "true"; gMoveCount = parseInt(localStorage["halma.movecount"]); drawBoard(); return true; } أهم فكرة في هذه الدالة هي تطبيق التحذير الذي ذكرته لك سابقًا في هذا الدرس، وسأكرره هنا: "ستُخزَّن البيانات كسلسلة نصية، وهذا يعني أنَّه لو لم تكن القيمة المُخزَّنة نصيةً فستحتاج إلى تحويل البيانات التي حصلت عليها إلى نوع البيانات الذي تريده". فعلى سبيل المثال، القيمة التي تُحدِّد فيما إذا كانت هنالك لعبة قيد اللعب (gGameInProgress) هي قيمة منطقية، وفي الدالة saveGameState() خزَّنا القيمة دون أن نلقي بالًا لنوعها: localStorage["halma.game.in.progress"] = gGameInProgress; لكن في دالة resumeGame() علينا أن نُعامِل القيمة التي أخذناها من التخزين المحلي كسلسلةٍ نصيةٍ كالآتي: gGameInProgress = (localStorage["halma.game.in.progress"] == "true"); وبشكلٍ مشابه، يُخزَّن عدد الخطوات في gMoveCount كعددٍ صحيحٍ؛ فلقد خزَّناها ببساطة في الدالة saveGameState(): localStorage["halma.movecount"] = gMoveCount; لكن في دالة resumeGame() علينا أن نحوِّل القيمة إلى عدد صحيح باستخدام الدالة parseInt() الموجودة في JavaScript: gMoveCount = parseInt(localStorage["halma.movecount"]); مستقبل التخزين المحلي في تطبيقات الويب على الرغم من أنَّ الماضي كان مليئًا بالطرق الالتفافية، لكن الوضع الراهن للتخزين المحلي في HTML5 مشرقٌ، فهنالك واجهة برمجية (API) جديدة قد وُضِعَ لها معيارٌ وطبِّق هذا المعيار في جميع المتصفحات الرئيسية على مختلف المنصات والأجهزة. فهذا أمرٌ لا تراه كل يوم كمطوِّر ويب، أليس كذلك؟ لكن ألا تتطلع إلى أكثر من "5 ميغابايت من الثنائيات على شكل "مفتاح/قيمة"؟ حسنًا، هنالك عدد من الرؤى التنافسية لمستقبل التخزين المحلي. إحدى تلك الرؤى هي اختصارٌ تعرفه بالتأكيد: SQL. أطلقَت Google في عام 2007 إضافة Gears المفتوحة المصدر التي تعمل على مختلف المتصفحات والتي احتوت على قاعدة بيانات مُضمَّنة فيها مبنية على SQLite. أثَّر هذا النموذج الأولي لاحقًا على إنشاء مواصفة Web SQL Database، والتي كانت تعرف رسميًا باسم "WebDB" التي توفر طبقةً للوصول إلى قاعدة بيانات SQL، سامحةً لك بالقيام بأشياء شبيهة بما يلي عبر JavaScript (لاحظ أنَّ الشيفرة الآتية حقيقية وتعمل على أربعة متصفحات): openDatabase('documents', '1.0', 'Local document storage', 5*1024*1024, function (db) { db.changeVersion('', '1.0', function (t) { t.executeSql('CREATE TABLE docids (id, name)'); }, error); }); كما لاحظت، ما يهم في الشيفرة السابقة هو السلسلة النصية التي مررتها إلى الدالة executeSql، ويمكن أن تحتوي تلك السلسلة النصية على أيّة تعليمات SQL مدعومة، بما في ذلك SELECT و UPDATE و INSERT و DELETE. الأمر هنا شبيهٌ ببرمجة قواعد البيانات بلغةٍ مثل PHP، إلا أنَّك تقوم بذلك عبر JavaScript! طُبِّقت مواصفات Web SQL Database من أربعة متصفحات ومنصات. IE Firefox Safari Chrome Opera iPhone Android . . 4.0+ 4.0+ 10.5+ 3.0+ 2.0+ وبكل تأكيد، لو سبق وأن استخدمت أكثر من مُحرِّك لقواعد البيانات في حياتك، فأنت تعلم أنَّ "SQL" هي مصطلح تسويقي أكثر من كونها معيارًا متكاملًا (قد يقول البعض أنَّ HTML5 كذلك، لكن لا تأبه بقولهم). حسنًا، هنالك معيار للغة SQL (يسمى SQL-92) لكن لا يوجد خادوم قواعد بيانات في العالم يتوافق تمامًا مع ذاك المعيار. فهنالك نسخة SQL لقواعد بيانات Oracle، ونسخة أخرى لقواعد MSSQL، ونسخة أخرى لقواعد بيانات MySQL، وأخرى لقواعد بيانات PostgreSQL، ولا ننسَ نسخة SQL لقواعد بيانات SQLite. وحتى كل منتج من تلك المنتجات يُضيف ميزات SQL جديدة على مرّ الزمن، وبهذا يكون قولنا "نسخة SQL لقواعد بيانات SQLite" ليس كافيًا لتحديد ما نتحدث عنه بدقّة. فعليك أن تقول "نسخة SQL التي تأتي مع قواعد بيانات SQLite ذات الإصدار X.Y.Z". كل ما سبق أدى إلى الإعلان الآتي، التي يقبع الآن في أعلى صفحة مواصفة Web SQL Database: وعلى ضوء هذا، سأعرِّفك على رؤية تنافسية أخرى لتخزينٍ محليٍ متقدم وثابت لتطبيقات الويب: Indexed Database API المعروفة رسميًا باسم "WebSimpleDB" التي اشتهرت باسم "IndexedDB". تحتوي IndexedDB على ما يُسمى "مخزن الكائنات" (object store)، الذي يتشارك مع قاعدة بيانات SQL في الكثير من المفاهيم؛ فهنالك "قواعد بيانات" (databases) فيها "سجلات" (records)، ويملك كل سجل عددًا من "الحقول" (fields)، وكل حقل له نوع بيانات معيّن، الذي يُعرَّف عند إنشاء قاعدة البيانات. تستطيع أيضًا تحديد مجموعة فرعية من السجلات، ثم تعرضها عبر "مؤشر" (cursor)، ويتم التعامل مع التغييرات على مخزن الكائنات عبر "التحويلات" (transactions). إذا سبق وأن برمجتَ قليلًا بأي نوع من أنواع قواعد بيانات SQL ، فمن المرجح أن تبدو المصطلحات السابقة مألوفةً لديك. الفرق الرئيسي هو أنَّ "مخزن الكائنات" ليس لديه "لغة استعلام بنيوية"، لا تستطيع كتابة عبارات مثل "SELECT * from USERS where ACTIVE = 'Y'" لكنك تستطيع استخدام الدوال التي يوفرها مخزن الكائنات لفتح "مؤشر" (cursor) في قاعدة البيانات "USERS"، ثم تمر عبر السجلات، وتستبعد سجلات المستخدمين غير النشيطين، ثم تستخدم دوالًا للوصول إلى قيم كل حقل في السجلات المتبقية. دعم IndexedDB موجودٌ في Firefox منذ الإصدار 4.0 (صرَّحَت Mozilla أنَّها لن تدعم Web SQL Database في متصفحها)، وChrome منذ الإصدار 11، وحتى Internet Explorer أصبح يدعم IndexedDB منذ الإصدار 10. ترجمة -وبتصرّف- لفصل "Local Storage" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال التالي: تطبيقات الويب التي تعمل دون اتصال – الجزء الأول المقال السابق: تحديد الموقع الجغرافي (GeoLocation) في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
-
عند استخدام التحريكات animations والانتقالات transitions ضمن واجهة المستخدم، فإنّه من الضروري أن يكون لها هدف واضح ومحدّد، ألا وهو تحسين تجربة المستخدم. تؤمّن لنا الانتقالات transitions الوسيلة المناسبة والمثالية لجعل الحركة سلسة وانسيابية أمام المستخدم. بدون تأثيرات الانتقال من الممكن أن يصبح المستخدم في حيرة من أمره حول الذي حدث بالضبط عند تنفيذه لأمر معين. في هذا المقال، سننشئ بعض التحريكات والانتقالات الإبداعية لإضافة وإزالة عناصر من قائمة، لقد أعجبتني الفكرة الواردة في مقال باسكويل دي سيلفيا. أمّا بالنسبة للشيفرة المسؤولة عن الإنتقالات في مقال باسكويل، فقد كتبه كريس كوير. سأعمل على تطوير المثال الوارد في مقال باسكويل، وذلك بإضافة المزيد من تأثيرات الانتقالات والتحريكات، وسأستخدم أيضاً شيفرة صغيرة من مقال كريس لإضافة خطوة إضافية في كل تحريكة، تتمثل بحجز مكان للعناصر المراد إضافتها إلى القائمة قبل إضافتها فعلياً. سأستخدم خصائص CSS بدون أي بادئة prefix وذلك بغرض الاختصار، لكنك ستجد الخصائص كاملة ضمن النص المصدري للمشروع على Github. ستعمل مقاطع الشيفرة التي ستجدها ضمن هذا الدرس على متصفحات تدعم خصائص CSS المستخدمة. لنبدأ العمل! الرماز The Markupلتوضيح فكرة الدرس بشكل جيد، أنشأت تطبيقاً بسيطاً خاصاً بالملاحظات البسيطة. يستخدم هذا التطبيق تقنية التخزين المحلي LocalStorage التي توفرها HTML5 وذلك لحفظ الملاحظات ضمن التخزين المحلي لمتصفح ويب لديك. يسمح لك التطبيق بأخذ ملاحظات وحفظها ضمن المتصفح إن أردت ذلك، في الحقيقة هو السبب الذي من أجله بنيت هذا التطبيق، وذلك من أجل ملاحظاتي الخاصة. لن أخوض في تفاصيل كيفية بناء هذا التطبيق لأنّ ذلك ليس هدف هذا الدرس. الرُماز markup المستخدم في هذا التطبيق هو مجرد نموذج form بسيط يحتوي على حقل نصي text field وزر إرسال submit button، بالإضافة إلى قائمة غير مرتّبة unordered list. ستُضاف الملاحظات إلى هذه القائمة بشكل تلقائي. كما يوجد أيضاً عنصري div لعرض التنبيهات، التي ستظهر عند حفظ أو إزالة أي عنصر، بالإضافة إلى عدّاد وزر لحذف جميع العناصر بنقرة واحدة. فيما يلي جميع رُماز HTML الذي سنحتاجه: <div class="notification undo-button"> Item Deleted. Undo? </div> <div class="notification save-notification"> Item Saved </div> <div class="reminder-container"> <header> <h1>mini reminders list</h1> </header> <form id="input-form"> <input type="text" id="text" placeholder="Remind me to.."/> <input type="submit" value="Add" /> </form> <ul class="reminders"> </ul> <footer> <span class="count"></span> <button class="clear-all"> Delete All </button> </footer> </div>يمكنك إضافة وتحرير وإزالة العناصر (الملاحظات) بالإضافة إلى إمكانية إستعادة العنصر المحذوف. في الواقع، تأتي أغلب التحريكات مرافقة لعملية إزالة وإستعادة العناصر. تُعتبر عملية إضافة العناصر بسيطة ولا تترافق مع الكثير من التحريكات، باستثناء التحريك الخاص بالظهور التدريجي fade in والسقوط إلى أسفل falling down واللذان سنتحدث عنهما عندما نبدأ العمل مع CSS. تنسيقات CSSستحصل العناصر المُضافة توّاً عن طريق JavaScript على الصنف new-item. (صنف CSS). أمّا العناصر المُزالة فستحصل على الصنف removed-item. كما ستحصل العناصر المُستعادة على الصنف restored-item. وكل صنف من الأصناف السابقة سيُفعّل التحريكات الخاصة به. ستبقى أسماء الأصناف السابقة ثابتةً لجميع الأمثلة التوضيحية، في حين ستختلف فيما بينها بالتوجيه المسؤول عن مظهر التحريك keyframes@. لنبدأ الآن بالمثال التوضيحي الأول. المثال الأول المثال الأول: العناصر المُزالة "تسقط إلى أسفل"، والعناصر المُستعادة ستعود بحركة معاكسة. ستسقط العناصر المضافة حديثاً من الأعلى، وهذا تأثير بسيط لكنه جميل. سيبدأ كل عنصر مُضاف حديثاً بالسقوط إلى أسفل وذلك من موقع يعلو 400 بيكسل عن الموقع النهائي الذي سيستقر فيه (أي الموقع النهائي ناقص 400 بيكسل) لا تنس أنّه يجب على الخاصية الفرعية animation-fill-mode أن تحمل القيمة forwards وذلك للتأكّد من أنّ العناصر ستبقى في موقعها النهائي ضمن القائمة، وإلّا فإنّها ستختفي بمجرّد انتهاء عملية التحريك. li.new-item { opacity: 0; animation: new-item-animation .3s linear forwards; } @keyframes new-item-animation { from { opacity: 0; transform: translateY(-400px); } to { opacity: 1; transform : translateY(0); } }ستسقط العناصر المزالة وتتلاشى fade out. بالنسبة لتحريكة السقوط إلى أسفل فهي بسيطة جداً، حيث ينتقل العنصر إلى أسفل وفق محور التراتيب (محور y) ليحاكي تحريكة السقوط، ويدور بينما يسقط ويتلاشى بالتدريج حتى يختفي تماماً (ستتحقّق شيفرة JavaScript من أنّ العنصر قد أُزيل كليّاً من DOM في نهاية هذه التحريكة). li.removed-item { animation: removed-item-animation 1s cubic-bezier(0.55, -0.04, 0.91, 0.94) forwards; transform-origin: 0% 100%; } @keyframes removed-item-animation { 0% { opacity: 1; transform: rotateZ(0); } 100% { opacity: 0; transform: translateY(600px) rotateZ(90deg); } }أمّا عندما نستعيد عنصرًا فستعمل تحريكة الاستعادة على عكس المنطق الموجود في تحريكة إزالة عنصر، لذلك فستكون الأُطر الأساسية keyframes مُعرّفة بشكل معاكس تماماً لما هو عليه في تحريكة إزالة عنصر: li.restored-item { animation: openspace 0.3s ease forwards, restored-item-animation 0.3s 0.3s cubic-bezier(0, 0.8, 0.32, 1.07) forwards; } @keyframes openspace { to { height: auto; } } @keyframes restored-item-animation { 0% { opacity: 0; transform: translateY(600px) rotateZ(90deg); } 10% { opacity: 1; transform: translateY(600px) rotateZ(90deg); } 100% { opacity: 1; transform: rotateZ(0); } }يمكنك أن ترى أننا نستخدم في الشيفرة السابقة مظهر تحريك اسمه openspace استعرته من مقال كريس كوير. يتأكّد مظهر التحريك هذا من أنّ العناصر التي تقع أسفل العنصر المُسترجع (إن وجدت)، ستنزلق إلى الأسفل وتفسح مجالاً للعنصر المُسترجَع ليعود إلى مكانه. إذاً عندما تنزلق العناصر إلى الأسفل لتفسح مجالاً open space للعنصر المُسترجَع، فإنّها فعلياً يجب أن تنتقل إلى الأسفل بشكل سلس، ولكن بما أنّ العناصر في هذا التطبيق لا تملك ارتفاعاً height ثابتاً، لذلك فإنّ إطار التحريك الأساسي to (انظر الشيفرة في الأعلى) سيجعل قيمة الارتفاع height لها لتصبح auto في نهاية عملية التحريك، سيؤدي ذلك لسوء الحظ إلى أنّ العناصر لن تنزلق إلى الأسفل، بل ستبدو كما لو أنّها تقفز إلى الأسفل. على أية حال توجد طريقة تجعل العناصر تغير مواقعها بشكل سلس، وهي تقنية كتب عنها ستيف ساندرسون Steve Sanderson هنا. لكنه يستخدم لهذا الغرض التموضع المطلق absolute positioning، وكمية لا بأس بها من شيفرة JavaScript. يمكنك تفقّد مقالته إذا كنت مهتماً بمعرفة المزيد عن التقنيّة التي يستخدمها، والتي تعطي في الحقيقة نتائج رائعة! المثال الثاني المثال الثاني: العناصر تكبُر وتتلاشى أمام المستخدم، وتُستَعاد بطريقة معكوسة. يعود الفضل في هذه الفكرة إلى تيم بيتروسكي Tim Pietrusky، حيث جاء بها عندما أخبرته أنّ الأفكار قد نفذت منّي بعد أن وضعت خمسة أمثلة توضيحية! في هذه الفكرة، تظهر العناصر المُضافة حديثاً (أي تلك العناصر التي لم تُزال من قبل ثم استُعيدت) بشكل تدريجي fade in ضمن موقعها. li.new-item { opacity: 0; animation: fadeIn .3s linear forwards; } @keyframes fadeIn { to { opacity: 1; } }عندما تُزال العناصر، فإنّها تكبُر وتتلاشى أمام المستخدم، أمّا العناصر المُستعادة فتسلك الأسلوب المعاكس، فعملية التحريك بالنسبة للعناصر المستعادة تماثل تماماً عملية التحريك بالنسبة للعناصر المزالة ولكن بالمقلوب. li.removed-item { animation: removed-item-animation .6s ease-out forwards; transform-origin: 50% 50%; } @keyframes removed-item-animation { 0% { opacity: 1; transform: scale(1); } 100% { opacity: 0; transform: scale(4); } } li.restored-item { animation: openspace .3s ease forwards, restored-item-animation .3s .3s ease-out forwards; } @keyframes openspace { to { height: auto; } } @keyframes restored-item-animation { 0% { opacity: 0; transform: scale(4); } 100% { opacity: 1; transform: scale(1); } }المثال الثالث المثال الثالث: ستنزلق العناصر المستعادة لتدخل من اليمين، أما العناصر المزالة فستنزلق يساراً إلى الخارج. يُعتبر المثال الثالث أبسط من سابقيه من الناحية الشكلية. فالعناصر المُضافة حديثاً سيكون لها نفس تأثير الظهور التدريجي كما في المثالين السابقين، لذلك سنتجاوز عملية التحريك الخاصة بإضافة عنصر جديد. بالنسبة للعناصر المُزالة فإنها ستنزلق يساراً إلى الخارج، مع ملاحظة تأثير جميل يحدث عند بدء عملية الإزالة باستخدام دالة توقيت من النوع Cubic Bezier، انظر إلى المثال التطبيقي لترى كيف تعمل هذه التحريكة. li.removed-item { animation: removed-item-animation .8s cubic-bezier(.65,-0.02,.72,.29); } @keyframes removed-item-animation { 0% { opacity: 1; transform: translateX(0); } 30% { opacity: 1; transform: translateX(50px); } 80% { opacity: 1; transform: translateX(-800px); } 100% { opacity: 0; transform: translateX(-800px); } }أمّا العناصر المستعادة فستنزلق إلى الداخل من اليمين، وذلك باستخدام نفس دالة التوقيت السابقة، ولكنها ليست الحالة المعاكسة تماماً لها (تفقّد المثال التطبيقي لترى النتيجة النهائية). li.restored-item { animation: openspace .3s ease forwards, restored-item-animation .5s .3s cubic-bezier(.14,.25,.52,1.56) forwards; } @keyframes openspace { to { height: auto; } } @keyframes restored-item-animation { 0% { opacity: 0; transform: translateX(300px); } 70% { opacity: 1; transform: translateX(-50px); } 100% { opacity: 1; transform: translateX(0); } } المثال الرابع المثال الرابع: ستكبر العناصر المستعادة والجديدة وتظهر تدريجياً ضمن موقعها، أمّا العناصر المزالة فإنّها ستصغر وتختفي تدريجياً. وهذا المثال بسيط أيضاً. فكل من العناصر الجديدة والمستعادة ستكبر وتظهر تدريجياً في مواقعها، أما العناصر المزالة ستصغر وتختفي تدريجياً. هناك إطارين أساسيين keyframes لهاتين التحريكتين: li.removed-item { animation: removed-item-animation .6s cubic-bezier(.55,-0.04,.91,.94) forwards; } @keyframes removed-item-animation { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0); } } li.restored-item { animation: openspace .3s ease forwards, restored-item-animation .3s .3s cubic-bezier(0,.8,.32,1.07) forwards; } @keyframes openspace { to { height: auto; } } @keyframes restored-item-animation { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } } المثال الخامس المثال الخامس: تسقط العناصر الجديدة من الأعلى إلى الأسفل. العناصر المزالة تبقى معلّقة قليلاً ثم تسقط إلى الأسفل. أما العناصر المستعادة فتنزلق إلى الداخل من اليمين. في هذا المثال، عندما نُزيل أحد العناصر فإنّه يبقى معلّقاً قليلاً قبل أن يبدأ بالسقوط الفعلي ثم الإختفاء. في الحقيقة هذا هو الجزء الأهم في هذا المثال، لأنّ العناصر الجديدة ستسقط إلى الأسفل كما في المثال الأوّل، والعناصر المستعادة ستنزلق إلى الداخل من اليمين كما في المثال الثالث، ولكن مع فرق طفيف في دالة التوقيت timing function. لذلك فإنّ التحريكة الخاصة بإزالة العناصر هي التأثير الجديد الوحيد في هذا المثال. li.restored-item { transform: translateX(300px); animation: openspace .3s ease forwards, restored-item-animation .3s .3s cubic-bezier(0,.8,.32,1.07) forwards; } @keyframes openspace { to { height: auto; } } @keyframes restored-item-animation { to { opacity: 1; transform: translateX(0); } } li.removed-item { animation: removed-item-animation 2s cubic-bezier(.55,-0.04,.91,.94) forwards; transform-origin: 0% 100%; }يعطي تغيير زاوية الدوران للعنصر ضمن أُطر frames مختلفة (الأطر الرئيسية: 0% و 20% و 40% و 60% و 70% و 90% و 100%) انطباعاً بأنّ العنصر يتأرجح بينما يكون معلّقاً، وبعد ذلك يبدأ بالسقوط إلى الأسفل. @keyframes removed-item-animation { 0% { opacity: 1; transform: rotateZ(0); } 20% { opacity: 1; transform: rotateZ(140deg); } 40% { opacity: 1; transform: rotateZ(60deg); } 60% { opacity: 1; transform: rotateZ(110deg); } 70% { opacity: 1; transform: rotateZ(90deg) translateX(0); } 90% { opacity: 1; transform: rotateZ(90deg) translateX(600px); } 100% { opacity: 0; transform: rotateZ(90deg) translateX(600px); } } المثال السادس المثال السادس: ستختفي العناصر المزالة تدريجياً وتسقط إلى الأسفل باتجاه اليسار، أما العناصر الجديدة والمستعادة فستنزلق إلى الداخل من اليمين. سيكون لكل من العناصر الجديدة والمستعادة نفس السلوك في هذا المثال، حيث ستنزلق هذه العناصر إلى الداخل من اليمين ثم تخرج بشكل طفيف من الجهة اليسرى قبل أن تستقرّ في مكانها. li.restored-item { transform: translateX(300px); animation: openspace .3s ease forwards, restored-item-animation .5s .3s cubic-bezier(.14,.25,.52,1.56) forwards; } @keyframes openspace { to { height: auto; } } @keyframes restored-item-animation { 0% { opacity: 0; transform: translateX(300px); } 70% { opacity: 1; transform: translateX(-50px); } 100% { opacity: 1; transform: translateX(0); } }ستنزلق العناصر المزالة ببطء نحو اليسار، وبعد ذلك ستسقط إلى الأسفل باتجاه اليسار وتتلاشى. من المهم الآن أن نعمل على إعداد تحويل مناسب لموضع نقطة الأصل origin (مبدأ الإحداثيات)، بحيث أنّ التأثير المسؤول عن السقوط إلى أسفل يبدو أكثر واقعية. بغية ذلك، أجريت تحويلاً على نقطة الأصل لكي تنطبق على آخر نقطة تماس بين العنصر والسطر الذي ينتمي إليه، وذلك قبل أن يبدأ بالدوران والسقوط إلى أسفل، يعطي ذلك انطباعاً بأنّ العنصر يسقط بسبب وزنه. li.removed-item { animation: removed-item-animation 1s linear; transform-origin: 390px 100%; } @keyframes removed-item-animation { 0% { opacity: 1; transform: translateX(0) rotateZ(0); } 50% { opacity: 1; transform: translateX(-400px) rotateZ(0); } 75% { opacity: 1; transform: translateX(-420px) rotateZ(-30deg); } 100% { opacity: 0; transform: translateX(-800px) rotateZ(-60deg) translateY(400px); } } خاتمةفي الواقع، الإمكانيات المتاحة لا حدّ لها تقريباً، هناك الكثير من الطرق الأكثر الإبداعية لإضافة وإزالة عناصر قائمة، وأنا على ثقة بأنّك تستطيع ابتكار تأثيرات خاصة بك، وأرجو أن يكون هذا الدرس مثيراً وملهماً. لم أدخل في القسم المتعلّق بـ JavaScript لأنّ ذلك ليس من محور اهتمام الدرس. من الملاحظ وجود خطأ ضمن متصفح Firefox (ربما يُصحّح في النسخ اللاحقة) ,والذي يسبب وميضاً للصفحة كلّما تمّ وضع التركيز على العنصر أو حتى إزالة التركيز عنه (أي عندما تضغط زر edit/save). لا أدري إذا كانت توجد طريقة لتجاوز هذا الأمر، لذلك فمن فضلك أعلمني إذا استطعت تحديد سبب ذلك الوميض وإذا كانت هناك أي طريقة لمنعه. على أية حال جُرّبت الأمثلة السابقة بشكل جيّد على المتصفحات التي تدعم Webkit. شكراً لقراءتك هذا الدرس، وأرجو أن تكون قد استمتعت فيه. بإمكانك الاطّلاع على هذه الأمثلة من هنا، أما الشيفرة المصدرية فهي متُوفّرة على هذا المُستودع. ترجمة -وبتصرّف- للمقال Creative Add/Remove Effects For List Items with CSS3 Animations لصاحبته Sara Soueidan.
-
- 3
-
- transitions
- animations
- (و 9 أكثر)