HTML5+JS
تُنشأ التأثيرات باستخدام JavaScript الخالصة (vanilla) على عنصر canvas في HTML5. في هذا القسم، سنتناول الأساسيات: رسم الأشكال، والرسم بالتكرار، وإضافة الحركة.
رسم الأشكال
Section titled “رسم الأشكال”سأستخدم في هذا الدرس قالب lightscript عامًا:
<head> <meta description="Template"/> <meta publisher="WhirlwindFX" /></head>
<body style="margin: 0; padding: 0;"> <canvas id="exCanvas" width="320" height="200"></canvas></body>
<script> // Get the canvas element from the DOM var c = document.getElementById("exCanvas"); var ctx = c.getContext("2d");
var width = 320; var height = 200; var hue = 0;
function update() { // Code goes here window.requestAnimationFrame(update); }
window.requestAnimationFrame(update);</script>الخطوط
Section titled “الخطوط”سواء كنت تستخدم خطًا واحدًا أو عدة خطوط، تبقى العملية كما هي.
- ctx.beginPath() تبدأ شكلك
- ctx.moveTo(x, y) تحدد النقطة الأولى
- ctx.lineTo(x, y) تحدد النقطة التالية وخطًا يصلها بالنقطة الأولى. استخدم عدة استدعاءات لـ lineTo لرسم أشكال أكبر.
- عيّن ctx.strokeStyle و/أو ctx.fillStyle (إذا كان الشكل مغلقًا)
- استخدم ctx.stroke() لرسم الخطوط، وctx.fill() لملء أي أشكال مغلقة. من المهم ملاحظة أن كل شيء حتى هذه النقطة افتراضي - لا يُرسم أي شيء حتى تُنفَّذ هذه الأوامر.
إليك ثلاثة أمثلة تستخدم الخطوط:
function update() { // One Line ctx.beginPath(); ctx.moveTo(30, 30); ctx.lineTo(30, 170); ctx.strokeStyle = "blue"; ctx.stroke();
// L ctx.beginPath(); ctx.moveTo(80, 30); ctx.lineTo(80, 100); ctx.lineTo(120, 100); ctx.strokeStyle = "blue"; ctx.stroke();
// Filled Triangle ctx.beginPath(); ctx.moveTo(130, 30); ctx.lineTo(130, 100); ctx.lineTo(170, 100); ctx.lineTo(130, 30); ctx.fillStyle = "red"; ctx.strokeStyle = "blue"; ctx.fill(); ctx.stroke();
window.requestAnimationFrame(update);}
من المهم ملاحظة ترتيب الرسم هنا: يُرسم المثلث المملوء أخيرًا، لذا فإنه سيغطي الشكلين الآخرين لو كانا في الموضع نفسه. بالإضافة إلى ذلك، يُرسم الحد الخارجي (stroke) للمثلث بعد الملء. ولو عكسنا هذه الخطوات، لغطّى الملء الأحمر النصف الداخلي من الحد الخارجي.
المستطيلات
Section titled “المستطيلات”المستطيلات بسيطة ومفيدة، خاصةً بالنسبة للدقة المتوسطة للوحة مفاتيح RGB. العملية مشابهة لرسم الخطوط - ما عليك سوى استبدال الأمرين moveTo وlineTo بالأمر rect(x, y, width, height).
function update() { // Basic Square ctx.beginPath(); ctx.rect(30, 30, 75, 75); ctx.strokeStyle = "blue"; ctx.stroke();
// Filled Rectangle ctx.beginPath(); ctx.rect(125, 30, 125, 100); ctx.fillStyle = "red"; ctx.strokeStyle = "blue"; ctx.fill(); ctx.stroke();
window.requestAnimationFrame(update); }
الأقواس
Section titled “الأقواس”تُرسم الأقواس بتدوير نقطة خارجية حول نقطة مركزية، ويمكن استخدامها لإنشاء دوائر كاملة. أمر القوس هو ctx.arc(x, y, radius, start angle, end angle)، حيث تُعطى قياسات الزاويتين بوحدة الراديان. وعلى عكس المستطيلات، يُرسم القوس بحيث تكون نقطة أصل الشكل (x, y) في مركز القوس.
function update() { // Half-circle ctx.beginPath(); ctx.arc(70, 100, 30, 0, Math.PI); ctx.strokeStyle = "blue"; ctx.stroke();
// 3/4 circle ctx.beginPath(); ctx.arc(140, 100, 30, 0, Math.PI * (3/2)); ctx.strokeStyle = "blue"; ctx.stroke();
// Full circle ctx.beginPath(); ctx.arc(210, 100, 30, 0, Math.PI * 2); ctx.strokeStyle = "blue"; ctx.stroke(); ctx.fillStyle = "red"; ctx.fill();
window.requestAnimationFrame(update); }
كما ترى، تُرسم الأقواس في اتجاه عقارب الساعة افتراضيًا. ويمكن عكس ذلك باستخدام زاوية نهاية أصغر من زاوية البداية، أو بإضافة “true” كوسيط أخير إلى الطريقة ctx.arc().
الرسم بالتكرار
Section titled “الرسم بالتكرار”قد يستغرق رسم ثلاثة أشكال يدويًا عشرين سطرًا من الشيفرة بسهولة، لذا إذا احتجت إلى رسم مئة شكل، فنحن بحاجة إلى نهج أكثر كفاءة. الحلقات في JavaScript طريقة بسيطة لتكرار المهام، ويمكن تطبيقها على الرسم أيضًا.
إليك مثالًا يستخدم حلقة for لرسم صف من المربعات المتناوبة (شطرنجية):
function update() {
for(let i = 0; i < 8; i++){
// 1. Create rectangle path and draw the stroke for each. ctx.beginPath(); ctx.rect(i * 20, 20, 20, 20); ctx.strokeStyle = "black"; ctx.stroke();
// 2. If 'i' is even, the fillStyle is black. Otherwise it's white. if(i % 2 == 0){ ctx.fillStyle = "black"; } else { ctx.fillStyle = "white"; } // 3. Fill the shape after setting the fillStyle. ctx.fill(); }
window.requestAnimationFrame(update); }
إليك مثالًا باستخدام حلقة while لرسم شبكة شطرنجية. سأغيّر قليلًا درجة إضاءة المربعات السوداء ودرجة لون (hue) المربعات البيضاء في كل تكرار باستخدام القوالب النصية (template literals).
function update() {
var i = 0;
while(i < 16){
// 1. X and Y calculations with 'row' and 'column' // This operation rounds the result down to the nearest integer. For i = (0-3) the row will be 0, for i = (4-7) the row will be 1, and so on var iRow = Math.floor(i / 4); // This operation finds the remainder after division, limiting column to the (0-3) range. var iCol = i % 4; // Multiply by the square width to find this square's x-origin var ix = iCol * 20; // Multiply by the square height to find this square's y-origin var iy = iRow * 20;
// 2. Path and stroke ctx.beginPath(); ctx.rect(ix, iy, 20, 20); ctx.strokeStyle = "black"; ctx.stroke();
// 3. If the row is even, every other square is filled black. If odd, switch the black squares. Each iteration adds a small amount of the lightness component to the black, and hue to the white. if(iRow % 2 == 0){ if(i % 2 == 0){ // 'Black' ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)` } else { // 'White' ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`; } } else { if(i % 2 == 0){ // 'White' ctx.fillStyle = `hsl(${10 * i}, 100%, 50%)`; } else { // 'Black' ctx.fillStyle = `hsl(1, 0%, ${5 * i}%)` } }
ctx.fill(); i++; }
window.requestAnimationFrame(update); }
إضافة الحركة
Section titled “إضافة الحركة”الرسوم المتحركة في canvas لا تحدها إلا مخيلتك ومعرفتك الرياضية. إذا كنت تشعر بعدم الثقة في حساب المثلثات أو الهندسة، فأنصحك بإجراء بعض البحث البسيط للارتقاء بمهاراتك إلى المستوى التالي. في الأمثلة القليلة التالية، سآخذ دائرة ثابتة وأربط كل متغير ممكن بحركة تذبذبية بسيطة.
المعيار الذهبي للتذبذب (الحركة المنتظمة ذهابًا وإيابًا) هو أخذ sin() أو cosine() لمؤقت زمني. ستُرجع كلتا العمليتين قيمة بين -1 و1 لأي قيمة تُمرَّر إليها، حتى لو كانت تتزايد فقط. والفرق الوحيد بينهما أنهما غير متزامنتين قليلًا - فعندما تكون قيمة cos تساوي 0 تكون قيمة sin إما -1 أو 1، والعكس صحيح. يمكننا الاستفادة من ذلك في هذا الرسم المتحرك -
function update() {
// Find current time in milliseconds, then divide to slow down the animation speed let time = Date.now() / 100;
// Draw a background so the old shapes don't stick around ctx.fillStyle = "white"; ctx.fillRect(0, 0, 320,200);
ctx.beginPath();
//USE ONE EXAMPLE AT A TIME // 1st example - side to side, y is constant ctx.arc(15 * Math.cos(time) + 160, 100, 30, 0, Math.PI * 2); // 2nd example - up and down, x is constant ctx.arc(160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2); // 3rd example - circle, x and y are variable ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30, 0, Math.PI * 2);
ctx.fillStyle = "black"; ctx.fill();
window.requestAnimationFrame(update); }من جانب إلى جانب
Section titled “من جانب إلى جانب”
صعودًا وهبوطًا
Section titled “صعودًا وهبوطًا”
دائرة كاملة
Section titled “دائرة كاملة”
لم يتبقَّ لدينا سوى بضع سمات لتغييرها بمرور الوقت - نصف قطر الدائرة، والملء، والحد الخارجي. سأترك زوايا القوس كما هي في الوقت الحالي لإبقاء الشكل بسيطًا، لأن البقية ستكون لافتة للنظر بما فيه الكفاية.
function update() {
let time = Date.now() / 100;
ctx.fillStyle = "white"; ctx.fillRect(0, 0, 320,200);
ctx.beginPath(); ctx.arc(15 * Math.cos(time) + 160, 15 * Math.sin(time) + 100, 30 + 15 * Math.sin(time), 0, Math.PI * 2);
ctx.fillStyle = `hsl(${180 + 180 * Math.cos(time)}, 100%, 50%)`; ctx.fill();
ctx.strokeStyle = `hsl(${180 + 180 * Math.sin(time)}, 100%, 50%)`; ctx.stroke();
window.requestAnimationFrame(update); }
يمكن تغيير كل سمة من سمات كل شكل أنشأناه حتى الآن بمرور الوقت، وكل رسم متحرك أصنعه هو مزيج من المهارات المذكورة أعلاه. لمزيد من التفاصيل حول صياغة رسوم متحركة فعّالة وقابلة لإعادة الاستخدام، راجع صفحة الاستدعاءات الراجعة!