رسوميات SMIL المتحركة قد اندثرت: دليلك إلى البدائل


جميل بيلوني

حظيت مواصفات لغة التكامل المتزامن للوسائط المتعدّدة (Synchronized Multimedia Integration Language، أو SMIL اختصارا)، وهي معيار مختص بترميز رسوميات SVG ، بشهرة كبيرة لتوفيرها إمكانيات عديدة للتعامل مع رسوميات SVG المتحركة. للأسف دعم SMIL يتناقص بمرور الزمن على محركات WebKit القياسية ولم تكن مدعومة أساسًا على متصفحات Microsoft (وغالبًا لن تدعمها أبدًا في المستقبل). لكن لا تقلق، يمكن علاج ذلك. هنا سنكتشف بدائل للميزات الخاصة في SMIL، ونبيِّن طرقًا بديلة لأداء تلك المهام وجعلها تحظى بدعم لفترة أطول.

ميزة SMIL: التحريك وفقًا لمسار

أحد أهم مميزات SMIL المتعلقة بالحركة الواقعية هي القدرة على التحريك وفقًا لمسار محدد. القليل فقط من الأشياء تتحرك في خط مستقيم على أرض الواقع، لذلك القدرة على التحكم بالرسوميات على مسار محدد مسبقًا يساعد على محاكاة الحركة الواقعية.

باستعمال الخاصية animateMotion، كان يمكنك تمرير مسار SVG وتحديد بيانات الحركة والاتجاه ثم تحديد العنصر المراد تحريكه بإسناد معرّفه للرابط xlink:href="#thingtoanimate"‎ كما في المثال التالي:

<animateMotion 
xlink:href="#lil-guy" 
dur="3s" 
repeatCount="indefinite" 
fill="freeze" 
path="M 25,50 C 37.5,25 37.5,25 50,0 75,50 75,50 100,100 50,100 50,100 0,100 12.5,75 12.5,75 25,50 Z" />

البديل باستخدام CSS

لحسن الحظ، القدرة على التحريك وفقًا لمسار مدمجة في لغة CSS. رغم ذلك مجال الدعم المتوفر لها لا يزال محدودًا (متصفحات كروم، وأوبرا، وفيرفكس على سطح المكتب، وبعض متصفح أندرويد).

بالنسبة لمتصفح سفاري، فالأمر تطلب الإبلاغ عن الأعطال لطلب ميزة التحريك وفقًا لمسار كخاصية CSS.

يتطلب استخدام التحريك وفقًا لمسار على CSS تحديد مسار الحركة باستعمال نقاط مرور كالتالي:

.move-me {
offset-path: path('M3.9,74.8c0,0,0-106.4,75.5-42.6S271.8,184,252.9,106.9s-47.4-130.9-58.2-92s59.8,111.2-32.9,126.1 S5.9,138.6,3.9,74.8z');
}

نتيجة المثال أعلاه يمكن الحصول على بيانات المسار ونقاط المرور من نتائج مخرجات تصميم SVG على Illustrator ثم تحسينها عن طريق أداةSVGOMG.

سنتأكد في هذا المثال أن العنصر سيتبع المسار من نقطة البداية وصولًا لنهايته ويمكنك ملاحظة أنه مسار مغلق، أي أن العنصر سينتهي به المطاف عائدًا لنقطة البداية مكررًا مساره. حدّدت هنا قيم الإطارات المفتاحية وخُصّصت القيمة 100% فقط لأن القيمة الافتراضية محددة عند 0:

@keyframes motionpathguy {
  100% {
    motion-offset: 100%;
    }
}

ثم نحدد خيارات تحريك العنصر:

.move-me {
  animation: motionpathguy 10s linear infinite both;
}

بديل: أسلوب GreenSock للتحريك

إذا كنت تسعى لخيارات الدعم الأوسع والأكثر مرونة في التطبيق، فعليك باستخدام مكتبة GreenSock المكتوبة بجافاسكريبت. يوفّر ملحق Bezier-Plugin دعمًا يمتد حتى الإصدار السابع من متصفح مايكروسوفت بدون استعمال عناصر SVG، وحتى الإصدار التاسع في حال أردت استعمال عناصر SVG (وهو أشمل دعم متوفّر لتحريكات SVG).

يجدر بنا ذكر أنك ستحتاج إلى إدراج مصفوفة لإحداثيات المسار:

bezier: {
  type: "soft",
  values:[{x:10, y:30}, {x:-30, y:20}, {x:-40, y:10}, {x:30, y:20}, {x:10, y:30}],
  autoRotate: true
}

يمكنك استعمال خيار التدوير التلقائي على غرار SMIL. وإذا كنت تفتقد قدرات SMIL على التحكم بالانعكاس التلقائي أو تحديد موضع الدوران التلقائي وزاويته، تتيح لك المكتبة التحكم بالدوران حتى 90 درجة من الميلان أو أي درجة من التحكم قد تحتاجها.

autorotate: [
  قيمة الموضع الأول "x",
  قيمة الموضع الثاني "y",
  "rotationY" أو "rotation" قيمة الحركة الدائرية، عادةً ,
  قيمة رقمية تمثل زاوية بداية الدوران، مثلًا "10",
  false أو true قيمة منطقية  
  (radians/degree) لاختيار وحدة قياس الزاوية 
]

يمكنك التحكم بدوران العنصر على SMIL أثناء حركته بالتلاعب بالمسار أو المسار، أما في مكتبة GreenSock فيمكنك أداء ذلك بسهولة بإيقاف التدوير التلقائي وبرمجة الدوران بسلسلة تعليمات، أيضًا يمكنك التلاعب بخصائص عناصر SVG كما كنت تفعل على SMIL برغم أنه أقل أناقة وأصعب في التتبع أثناء تحضيره.

TweenMax.set("#foo" {
   rotation: 90 // أو أي قيمة مناسبة
});

يمكنك الاختيار بين القيم thru، أو soft، أو quadratic، أو cubic لتحديد نوع المسار. تعليمات الشرح لكل منها موجودة على مكتبة GreenSock البرمجية. نقطة قوة thru هي القدرة على التأثير على مدى انحناء عنصر. إذا عدَدْنا أن نقاط المرور هي إحداثيات للتنقل منها وإليها، مدى الانحناء سيحدد اتجاه المسار عند الانتقال بين تلك النقاط. مدى الانحناء 0 سيكون اتجاهًا مباشرًا، 1 سيكون مائلًا قليلًا، 2 سينتج مسارًا منحنيًا، 3 وأكثر سيجعل المسار ينغلق على نفسه.

إليك مثال حي.

مؤخرًا، أثبت GreenSock قدرته على تمرير بيانات المسار مثل CSS و SMIL. جاء ذلك في صيغة تمديد (Extension) لملحق MorphSVG الخاص بها. يُضاف الملحق ويُستعمَل كالتالي:

TweenMax.to("#lil-guy", 3, {
  bezier: {
    MorphSVGPlugin.pathDataToBezier("#path", {align: "#lil-guy" }), 
    type: "cubic"
  },
  ease: Linear.easeNone,
  repeat: -1
});
<path id="path" d="M 25,50 C 37.5,25 37.5,25 50,0 75,50 75,50 100,100 50,100 50,100 0,100 12.5,75 12.5,75 25,50 Z" fill="none" />

إليك النتيجة حية.

سيعيّن الوضع الافتراضي أعلى مجموعة العناصر المُراد تحريكها، أي العنصر المسمى lil-guy#، إلى المسار، أي أنّ الركن العلوي الأيسر هو ما سيمرّ على المسار. بصريًا ستبدو الحركة غير متناسقة لذا قمنا بجعل العنصر lil-guy# يستخدم نقطة مركزية باستعمال تعليمات TweenLite.set:

TweenLite.set("#lil-guy", {xPercent:-50, yPercent:-50}); 

يمكنك موازنة تلك المسارات بتمرير كائن إلى الوسيط الثاني في التابع TweenLite.set، وتعيين قيم offsetX و offsetY في التابع pathDataToBezier. كن حذرًا هنا فقد تحتاج لتوسيع صندوق العرض (viewBox) في SVG حتى لاتتداخل العناصر قيد التحريك ببعضها، أو تفقد بعض أجزائها خارج الصندوق.

// أزح إحداثيات المسار بمقدار 125 بكسلًا أفقيًا و 50 بكسلًا رأسيًا
TweenMax.to("#lil-guy", 3, {
  bezier: {
      values: MorphSVGPlugin.pathDataToBezier("#path", {
    offsetX: 125, 
    offsetY: 50, 
    align: "#lil-guy"
    }),
    type: "cubic"
  },
  ease: Linear.easeNone,
  repeat: -1
});

إليك النتيجة حية.

يمكنك حتى تعريف مصفوفة إحداثيات لهذا التموضع:

// ضاعف إحداثيات المسار بمقدار 1.25 
// أزحه بمقدار 120 بكسل على المحور الأفقي 
// ارفعه بمقدار 30 بكسل على المحور الرأسي
TweenMax.to("#lil-guy", 3, {
    bezier: {
      values: MorphSVGPlugin.pathDataToBezier("#path", {
          matrix:[1.5,0,0,1.5,120,-30], 
  align:"lil-guy"}),
    type: "cubic"
  },
  ease: Linear.easeNone,
  repeat: -1
});

إليك النتيجة حية.

يوجد خيار آخر يتمثّل في تحديد قيمة المحاذاة إلى relative، وهو ما سيمنع العنصر من القفز خارج مساره بتثبيت الموضع لكل الإحداثيات المرتبطة إلى القيم x:0, y:0.

في الأمثلة السابقة استعملنا المحاذاة بالتزامن مع مجموعة عناصر lil-guy#. لمعلومات محدثة عن هذه الميزة لملحق Bezier، تفقَّد التوثيق على مكتبة GreenSockالبرمجية.

ميزة SMIL: تحوير الشكل

سابقًا كان باستطاعتك تمرير بيانات المسار كقيم داخل خصائص الرسوميات بهدف تحوير الشكل.

البديل: مكتبة Snap.svg أو SVG Morpheus

توفر بعض المكتبات البرمجية مثل Snap.svg و SVG Morpheus القدرة على تحوير الشكل أو المسار. لكن يجب مطابقة عدد نقاط الشكل الأساسي والمحوَّر وإلا تشوَّه الشكل الناتج. بسبب ذلك، عليك مراقبة الشكل الناتج من التحوير باستمرار أثناء التصميم أو نسِّق مع مصممك لتحصل على بيانات تلك النقاط مع تلافي إضافة نقاط غير ضرورية حتى لا تتسبب بإبطاء الرسوميات الناتجة.

بديل: ملحق MorphSVG من GreenSock

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

لمَّا كان ملحق MorphSVG يُعدل على بيانات المسار، فستحتاج إلى استعمال خيار convertToPath إذا احتجت لتعديل الشكل:

MorphSVGPlugin.convertToPath("ellipse"); 
    // or circle, rect, etc

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

يقدّم الملحق ميزات إضافية جديرة بالذكر. الأولى هي ميزة findShapeIndex متعددة الاستخدامات. لنقل إن الشكل الحالي المحَّر لا يعجبك (برغم أن إعادة الضبط التلقائي عادةً ما تفي بالغرض). ستُحمِّل الملحق الخاص (ليس عليك القلق بشأن زيادة حجم مشروعك، فلا داعي للملحق بعد مرحلة التطوير)، ثم تمرر قيمتين: معرَّف الشكل الأول، ومعرّف الشكل الثاني. ستظهر لديك واجهة رسومية يمكنك بها الانتقال بين القيم، وأيضًا ستستعمل التكرار على نحو افتراضي (repeat: -1) لتستمر إعادة الحركة بين الأشكال.

findShapeIndex("#hex", "#star");
// findShapeIndex() يمكنك تعليق السطر البرمجي أعلاه لإلغاء تفعيل واجهة

إليك النتيجة حية.

بمجرد حصولك على تلك القيم الإضافية، يمكنك تحديد قيمة shapeIndex داخل مصفوفة morphSVG:

TweenLite.to("#hex", 1, {morphSVG: { shape: "#star", shapeIndex: 1 }});

الميزة الثانية لهذا الملحق هي قدرته على تحليل المسارات، لا يوجد ملحق آخر يمكّنك من ذلك. أخيرًا يمكن إعادة استعمال قيمة المعرّف الخاصة ببداية الحركة بدون الحاجة لتخزينها. من الجدير بالذكر أنه عند الإطلاق الأول لهذا الملحق لم تكن هذه الميزات موجودة، لكن GreenSock ظلّت تدعمه بالميزات بلستمرار وبما أننا لم نعد ملزمين بعدد معين للنقاط يمكننا توسيع آفاقنا لمزيد من التأثيرات. هنا مثال على دخان.

ميزة SMIL: أحداث التفاعل مع الصفحة

وفَّر SMIL تأثيرات مثل التحويم على العناصر والضغط عليها وعند الرغبة باستعمال أي منها يمكنك تحديد الحدث باستعلام مثل begin="click"‎ أو begin="hover"‎:

<animate 
    xlink:href="#rectblue"
    attributeName="x"
    from="0"
    to="300" 
    dur="1s"
    begin="click"
    values="20; 50"
    keyTimes="0; 1"
    fill="freeze" />

بديل: لغة JavaScript

هنالك أحداث مدمجة ضمن اللغة مثل onmouseenter و onmouseleave لتأثيرات الحومان والضغط. يمكنك استعمالها لتفعيل رسوميات JavaScript.

البديل: التعاون بين JavaScript و CSS

يمكنك استعمال JavaScript لتغيير صنف عنصر ما أو تغيير تنسيقات CSS الخاصة به مباشرة. كمثال سنغير حالة الرسوميات لتتفعل بناءً على حدث ما:

.st0 {
  animation: moveAcross 1s linear both;
  animation-play-state: paused;
}
@keyframes moveAcross {
    to {
       transform: translateX(100px);
   }
}
document.getElementById("rectblue").addEventListener("click", function() {
    event.target.style.animationPlayState = "running";
});

إما إذا اعتدت على استعمال jQuery فالاستعلام كالتالي:

$(".st0").on("click", function() {
    $(this).css("animation-play-state", "running");
});

إليك النتيجة حية.

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

البديل: Greensock

إعادة الحركة باستخدام مكتبة GreenSock أكثر بساطة. يمكننا إضافة الرسوميات إلى جدول زمني للتحكم بإيقاف واستئناف الرسوميات بالضغط على العنصر. طريقة التنفيذ هذه شبيهة بما اعتدته من SMIL حيث لا نحتاج للتعامل مع نسخ أو إعادة إدخال أي نقاط شجرية للصفحة أو تغيير خصائص العناصر:

// TimelineLite إنشاء نسخة من
var tl = new TimelineLite();

// إضافة رسوميات إلى جدول زمني
tl.to(foo, 0.5, { left: 100 });

$(".st0").on("click", function() {
    tl.restart();
});

ميزة SMIL: تشغيل X بعد اكتمال Y

يستطيع SMIL إنشاء أحداث بتوقيت أكثر تعقيدًا مثل begin="circ-anim.begin + 1s"‎ (بدء حركة عنصر بعد ثانية من بدء حركة عنصر آخر). وهذا مفيد بالأخص عند إنشاء رسوميات متسلسلة.

بديل: CSS

على CSS يمكنك إنشاء تسلسل للرسوميات بتحديد تأخير على الرسوميات اللاحقة:

.foo {
  animation: foo-move 2s ease both;
}
.bar {
  animation: bar-move 4s 2s ease both; 
  /* تتطابق القيمة ‏2s‏ مع مدة التكرار في العنصر الأول أعلاه  */
}

طريقة العمل قد تصبح مسببة للإزعاج إذ عليك تذكر تغيير الفجوة الزمنية الأولى وكذلك التأخير لكل عنصر.

بديل آخر: برمجة CSS مسبقة (SASS)

إعداد وإدارة تلك الفجوات الزمنية يمكن تسهيله إذا استعملت متغيرات Sass مثلًا:

$secs: 2s;
.foo {
    animation: foo-move $secs ease both;
}
.bar {
    animation: bar-move 4s $secs ease both; 
}

هكذا ستتأكد أن تحديث قيمة واحدة لن يؤثر على التزامن. لكن إذا أردت تتبع الرسوميات عند اكتمالها، فجافاسكريبتيقدم دعمًا وظيفيًا أصليًّا باستعمال animationEnd:

$("#rectblue").on("animationend", function() {   
  $(this).closest("svg").find("#rectblue2").css("animation-play-state", "running");     
});
#rectblue2 {
    animation: moveAcross 2s 1s ease both;
    animation-play-state: paused;
}

إليك النتيجة حية (قد تحتاج للنقرعلى زر إعادة التشغيل لرؤية الحركة).

لتنفيذ التأخير يمكننا جدولة توقيت ظهور العناصر عبر خاصيات تأخير رسوميات CSS للعناصر أو باستعمال setTimeout:

setTimeout(function timeoutHandler() {
  // أدرج الرسوميات هنا، بغض النظر عن لغة البرمجة المستخدمة
}, 1000); //  انتظر لثانية واحدة

بديل آخر: Greensock

هذا هو الخيار المفضل لديّ للحصول على تحكم دقيق بالمسار الزمني الخاص بالرسوميات، إذ يمكنك أداء ذلك بعدة طرق مختلفة.

  • خط زمني بسيط:
// TimelineLite أنشئ    
var tl = new TimelineLite();

// أضف الرسوميات لبداية الخط الزمني
tl.to(foo, 0.5, { left: 100 });

// استعمل قيمة الموضع (+=1) لبدء الرسوميات التالية بعد ثانية واحدة من انتهاء الحالية
tl.to(foo, 0.5, { left: 200 }, "+=1");
  • خط زمني بوصف نسبي:
// أضف علامة متأخرة 0.5 ثانية لتحديد موضع الإطار التالي
tl.add("العلامة")
// استخدم العلامة لتحديد ظهور الرسوميات بعد الثانية التالية 
tl.to(foo, 0.5, { scale: 0 }, "العلامة+=1");

// أو استعمل العلامة لإنشاء واجهة للتفاعل 
tl.play("العلامة");

من الأفضل استعمال الوصف النسبي حيث تختار نقطة زمنية ما لتقوم بتفعيل العديد من الثأثيرات أو تأخيرها. حتى لو كانت خاضعة لتعديل زمني، وليس عليك القيام بأي إعادة لحسابات CSS.

الفائدة من استعمال المسار الزمني هي القدرة على التحكم الدقيق بعدة عناصر من مكان واحد كما يمكنك استعمال خاصيّات مثل تأخير التكرارات.

يقدم SMIL أيضًا خاصية repeatDur التي تمكنك من تحديد مدة التكرار المحدد، إذا لم ترغب بالقيمة الافتراضية "repeatDur="01:30 يمكنك التحكم بالسرعة بمساعدة GreenSock وبالتالي تعدي مدة التكرار باستعمال timeScale (n) أو repeat: -1. خلاف ذلك، يمكنك تحديد التكرار بالخاصية "repeatDur="indefinite.

جدول بياني للبدائل

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

SMIL الشفرة البديلة التقنية البديلة
keyTimes ‎@keyframes CSS
keySplines cubic-bezier CSS
restart restart()‎ GSAP
calcMode="discrete"‎ steps()‎ CSS
remove kill(); clear(); clearProps: "all"‎ GSAP
freeze animation-play-state: paused CSS
freeze pause()‎ GSAP
fill animation-fill-mode CSS
repeatCount="indefinite"‎ animation-iteration-count: infinite;‎ CSS
repeatCount="indefinite"‎ repeat: -1 GSAP
whenNotActive detect animation-play-state in JS CSS, vanilla JavaScript or jQuery
animateMotion path motion-path CSS
animateMotion path bezier GSAP
animate values (path morphing) MorphSVG GSAP
begin="hover"‎ mouseover, mouseenter, mouseout, mouseleave jQuery, vanilla JS
begin="circ-anim.begin + 1s"‎ animation-delay: $vars;‎ SASS
begin="circ-anim.begin + 1s"‎ timeline, position parameter ex "+=1"‎ GSAP

ترجمة - وبتصرف - للمقال SMIL is dead! Long live SMIL! A Guide to Alternatives to SMIL Features لصاحبته Sarah Drasner.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن