اذهب إلى المحتوى

التعامل مع عنصر Canvas باستخدام جافاسكربت (رسم الأشكال)


Lujain

تعرَّفنا في المقال السّابق على العنصر Canvas الذي أُضيف إلى HTML5 ليوفّر أداة ومساحة لرسم الأشكال والتصاميم باستخدام جافاسكربت. 

سنتعلم في هذا الدّرس كيفية رسم الأشكال على Canvas باستخدام جافا سكربت مثل الدوائر، المثلَّثات والمستطيلات وسنتعرف على المنحنيات Curves لرسم أشكال أكثر تعقيدًا.

canvas-javascript.thumb.png.e54aceb6e40c

الشبكة The grid

إن أي عنصر يتم إضافته أو رسمه على Canvas يُحَدّد موقعه بواسطة الزَّاوية اليسارية العليا (0, 0) أي بإحداثيات (x, y) بحيث أنه يبعد عن المركز (0, 0) بمقدار (x, y).

565c4f20d8f4b__1.thumb.png.6bafa9b6f72d6

رسم المستطيلات Rectangles

توفر canvas ثلاث دوال لرسم المستطيلات وجميعها تأخذ معاملات argumrnts مماثلة. 

الإحداثيات (x, y) تحدد موقع المستطيل بالنسبة للمركز (0, 0) والذي يحدَّد بالزَّاوية اليسارية العليا للـ Canvas، العرض width والطول height تحددان قياس المستطيل. 

  • الدَّالة ()fillRect ترسم مستطيلًا ذي مساحة لونية solid color. 
  • الدَّالة ()strokeRect ترسم مستطيلًا Storke (مُفرغًا) ذي حواف فقط outline. 
  • الدَّالة ()clearRect تقوم بمسح مستطيل محدّد بالمعاملات x, y) ،width ،height) أي تجعله شفافًا Transparent.

ولا ننسى طبعا الدَّالة ()draw والتي تدير جميع عمليات الرَّسم على الـ canvas في الجافاسكربت.

سنرى كيفية رسم المستطيلات باستخدام الدَّوال الثلاث أعلاه. السكربت التالي يرسم ثلاثة مستطيلات بإحداثيات وقياسات مختلفة :

<script type="application/javascript"> 
function draw() { 
  var canvas = document.getElementById("canvas"); 
  if (canvas.getContext) { 
    var ctx = canvas.getContext("2d"); 
    ctx.fillStyle = "rgb(200,0,0)"; 
    ctx.fillRect(10, 10, 200, 200); 
    ctx.clearRect(30, 30, 150, 150); 
    ctx.strokeRect(50, 50, 60, 60); 
  } 
} 
</script>

565c4f21b488b__2.thumb.png.72450aa485f7d

  • باستخدام ()fillRect تم رسم مستطيل ذو مساحة لونية حمراء بقياس 200*200 عند الإحداثيات (10,10).
  • باستخدام ()clearRect تم رسم مستطيل بلا لون (شفاف) بقياس 150*150 عند الإحداثيات (30,30).
  • باستخدام ()storkRect تم رسم مستطيل stroke مُفرغ ذو حواف بقياس 60*60 عند الإحداثيات (50,50).

رسم المسارات Paths

المسارات paths هي عبارة عن مجموعة من النقاط متصلة مع بعضها البعض بواسطة خطوط صغيرة segments لتقوم بتشكيل رسومات مختلفة كأشكال منحنية، أشكال هندسية…الخ. يمكن أن يكون المسار مفتوحًا مثل نصف دائرة او مغلقًا كالمثلَّث.

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

  • ()beginPath تقوم هذه الدَّالة بإنشاء المسار بحيث يتم رسم المسار وتخزين نقاطه في قائمة List.
  • the methods هي مجموعة من الدَّوال التي تقوم بإنشاء أشكال أساسية مثل الدَّالة arc التي ترسم قوس والدّالة lineTo التي ترسم خط مستقيم.
  • ()moveTo تقوم في كل مرة برسم نقطة انطلاق جديدة لبدء الرَّسم منها.
  • ()closePath تقوم بإغلاق المسار ليتم الرَّسم خارج المسار بعدها أي البدء بمسار جديد.
  • ()stroke تحول المسار المرسوم إلى شكل مُفرَّغ ذو حواف أي دون ملأ المسار.
  • ()fill تحول المسار المرسوم إلى مسار ممتلئ بمساحة لونية solid.

الخطوة الأولى لإنشاء مسار path هي استدعاء الدَّالة ()beginPath (بشكل ضمني المسارات تُخزَّن بشكل مسارات جزئية في مصفوفة أو قائمة List مثل خطوط أو منحنيات..الخ) والتي بمجملها مسؤولة عن تكوين الشكل.

في كل مرة تتم فيها استدعاء الدَّالة ()beginPath يتم فيها إعادة إنشاء مصفوفة أو قائمة List جديدة ويمكننا البدء برسم شكل جديد.

الخطوة الثانية هي استدعاء إحدى الدَّوال التي تقوم بتحديد المسار الذي سيُرسم مثل الدَّالة ()moveTo أو الدَّالة ()lineTo.

الخطوة الثالثة هي استدعاء إحدى الدالتين ()storke أو ()fill.

الخطوة الرابعة وهي خطوة اختيارية يتم فيها استدعاء الدَّالة ()closePath هذه الدَّالة تقوم بإغلاق الشكل عن طريق رسم خط مستقيم من النُّقطة الحالية التي توقف عندها الرَّسم إلى نقطة البداية التي بدأ عندها رسم المسار.

ملاحظات:

  1. في حين كان الشكل قد أُغلق أثناء الرَّسم أو بقيت نقطة واحدة في القائمة List فهذه الدَّالة لن تقوم بعمل أي شيء.
  2. عند استدعاء الدَّالة ()fill فإن الأشكال المفتوحة ستُغلق تلقائيًّا أي لن يكون هناك داعي لاستدعاء الدَّالة ()closePath ولكن لا تنطبق هذه الحالة عند استدعاء الدَّالة ()storke.

رسم مثلث

اعتمادًا على خطوات إنشاء المسارات أعلاه سأقوم بإنشاء مسار مثلَّث كما في السكربت التالي:

<script type="application/javascript"> 
function draw() { 
  var canvas = document.getElementById("canvas"); 
  if (canvas.getContext) { 
    var ctx = canvas.getContext("2d"); 
    ctx.beginPath(); 
    ctx.moveTo(100, 100); 
    ctx.lineTo(300, 200); 
    ctx.lineTo(100, 200); 
    ctx.fillStyle = "rgb(200,0,180)"; 
    ctx.fill(); 
    ctx.closePath(); 
    ctx.beginPath(); 
    ctx.moveTo(100, 300); 
    ctx.lineTo(400, 300); 
    ctx.lineTo(400, 200); 
    ctx.fillStyle = "rgb(140,50,80)"; 
    ctx.fill(); 
  } 
} 
</script>

قمت بإنشاء مثلَّثين الأول عند النُّقطة (100, 100)، رسم خط مستقيم بواسطة الدَّالة ()lineTo إلى النُّقطة (300, 200)، خط مستقيم إلى النُّقطة (100, 200) ثم تحديد اللّون عن طريق الدَّالة ()fillStyle وبالنهاية استدعاء الدَّالة ()fill ليتم تلوين المسار والحصول على شكل مثلَّث. كذلك الأمر بالنسبة للمثلَّث الثاني.

عند تشغيل المتصفّح يظهر المثلَّثان بهذا الشكل:

565c4f2281fba_3.thumb.png.0d8d4ce1fb5630

الرسم بتحريك القلم moving pen

تخيل أنك تقوم برسم لوحة فنية كم مرة ستحتاج لترك نقطة رسم وتحريك القلم للبدء من نقطة جديدة، هذا ما تقوم به الدَّالة ()moveTo أي أنها تقوم بنقل نقطة بدء الرَّسم إلى نقطة (x, y) جديدة.

عند إنشاء canvas أو عند استدعاء الدَّالة ()beginPath ستحتاج إلى استدعاء الدَّالة ()moveTo لتحديد نقطة البداية التي ستبدأ عندها الرَّسم. أو عندما تكون بحاجة لرسم مسارات متقطعة.

للتوضيح أكثر سأقوم برسم وجه ضاحك smiley face وتلوينه كما في السكربت التالي:

<script type="application/javascript"> 
function draw() { 
  var canvas = document.getElementById('canvas'); 
  if (canvas.getContext) { 
    var ctx = canvas.getContext('2d'); 

    ctx.beginPath(); 
    ctx.fillStyle = "rgb(0, 0, 0)"; 
    ctx.arc(75, 75, 50, 0, Math.PI * 2, true); 
    ctx.stroke();
    ctx.closePath(); 

    ctx.beginPath(); 
    ctx.fillStyle = "rgb(250, 250, 0)"; 
    ctx.arc(75, 75, 50, 0, Math.PI * 2, true); 
    ctx.fill(); 
    ctx.closePath(); 

    ctx.beginPath(); 
    ctx.fillStyle = "rgb(250, 0, 0)"; 
    ctx.moveTo(110, 75); 
    ctx.arc(75, 75, 35, 0, Math.PI, false);
    ctx.fill();
    ctx.closePath(); 

    ctx.moveTo(65, 65); 
    ctx.beginPath(); 
    ctx.fillStyle = "rgb(0, 0, 0)"; 
    ctx.arc(60, 65, 5, 0, Math.PI * 2, true); 
    ctx.fill(); 
    ctx.closePath(); 

    ctx.beginPath(); 
    ctx.moveTo(95, 65); 
    ctx.fillStyle = "rgb(0, 0, 0)"; 
    ctx.arc(90, 65, 5, 0, Math.PI * 2, true); 
    ctx.fill();
    ctx.closePath(); 
  } 
} 
</script>

يحتوى الشكل على 4 مسارات مغلقة: الإطار الخارجي للوجه، الوجه، الفم، العين اليمنى، العين اليسرى. 
لاحظ أنني قمت باستدعاء الدَّالة ()beginPath أربع مرات ولم أكن بحاجة لاستدعاء الدَّالة ()closePath عندما رسمت الإطار بالرغم من أنه stroke وذلك بسبب تقاطع نقطة النهاية مع البداية. 

أيضًا عند رسم الأشكال solid لم أحتج لاستدعاء الدَّالة ()closePath وذلك لأن الدَّالة ()fill تقوم بإغلاق المسار بشكل تلقائي لتلوينه.

565c4f2354993__4png.thumb.png.32732e7a2a

الخطوط Lines

تُستخدم الدَّالة ()lineTo لرسم خطوط مستقيمة بحيث ترسم الخط من النُّقطة الحالية إلى نقطة محددة بـ (x, y).

النقطة الحالية تتعلق بآخر مسار تم رسمه (أي أن النُّقطة الأخيرة لرسم المسار الأول هي نقطة بداية رسم المسار الثاني)

يمكنك تغيير النُّقطة الحالية عبر استدعاء الدَّالة ()moveTo كما شرحنا في المثال السابق.

لنقم بعمل مثال آخر لرسم مثلَّثين أحدهما solid والآخر stroke باستخدام الدالتين ()lineTo و ()moveTo واستدعاء الدَّالة ()lineTo مرتين لكل عملية رسم مثلَّث كما في السكربت التالي:

<script> 
function draw() { 
  var canvas = document.getElementById('canvas'); 
  if (canvas.getContext) { 
    var ctx = canvas.getContext('2d'); 

    // Filled triangle 
    ctx.beginPath(); 
    ctx.moveTo(100, 100); 
    ctx.lineTo(180, 100); 
    ctx.lineTo(100, 180); 
    ctx.fill(); 

    // Stroked triangle 
    ctx.beginPath(); 
    ctx.moveTo(125, 125); 
    ctx.lineTo(125, 45); 
    ctx.lineTo(45, 125); 
    ctx.closePath(); 
    ctx.stroke(); 
  } 
} 
</script>

565c4f249094e__5.thumb.png.67badbd92a461

قمت باستدعاء الدَّالة ()beginPath عند البدء في رسم الشكل ثم استدعاء الدَّالة ()moveTo للانتقال لنقطة بداية الرَّسم. ثم بعد ذلك استدعاء الدَّالة ()LineTo مرّتين لرسم المثلَّث بالإحداثيات المناسبة.

الآن استدعاء الدَّالة ()closePath عند الانتهاء من رسم المثلَّث من نوع storke لإغلاق المسار لأنه في حال عدم استدعاء هذه الدَّالة سيقوم برسم خطين فقط وليس مثلَّثا كاملًا.

الأقواس Arcs

يتم رسم الأقواس باستخدام الدالتين ()arc أو ()arcTo.

تقوم الدالة (arc(x, y, radius, startAngle, endAngle, anticlockwise برسم قوس عند النُّقطة (x, y) بمقدار القطر radius r ببداية الزَّاوية startAngle وانتهاءً بالزَّاوية endAngle وبالاتجاه anticlockwise (القيمة الافتراضية تكون مع اتجاه عقارب الساعة clockwise).

تقوم الدالة (arcTo(x1, y1, x2, y2, radius برسم قوس بمجموعة نقاط وقطر radius يتصل بالنُّقطة السابقة بواسطة خط مستقيم. 

إنَّ الدَّالة ()arcTo تأخذ 5 معاملات arguments: 

  • (x, y) وهما إحداثيي مركز الدائرة التي سيرسم عندها القوس
  • القطر raduis هو قطر الدائرة
  • المعاملان startAngle و endAngle يُحدّدان بداية ونهاية نقاط القوس بالراديان radians على طول قوس الدائرة حيث أنها تقاس من المحور x
  • المعامل anticlockwise هو متغير منطقي Boolean عندما يأخذ القيمة true سيتم رسم القوس عكس اتجاه عقارب الساعة وإلا سيتم رسم القوس باتجاه عقارب الساعة. 

ملاحظة: الزَّوايا في الدَّالة ()arc تقاس بالراديان radians وليس بالدّرجات، للتحويل من الدّرجات للراديان يمكنك استخدام السكربت:

radians = (Math.PI/180)*degrees

سأقوم بعرض مثال يشرح طريقة رسم مجموعة من 12 قوسًا بزوايا مختلفة، حيث ستُعرض الأقواس على شكل صفوف (أسطر وأعمدة) في كل صف ثلاث أقواس.

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

لكل مسار قوس سيتم استدعاء ()beginPath في السكربت لإنشاء مسار جديد في كل مرّة لرّسم شكل جديد.

قمت بتخزين كل معامل للدالة arc ضمن متغير لتسهيل استخدامها عند استدعاء الدَّالة.

القطر radius وزاوية البداية startAngle هما ثوابت. أمّا زاوية النّهاية endAngle تبدأ من 180 درجة (نصف دائرة) في العمود الأول. وتزداد بمقدار 90 درجة لتصل إلى دائرة كاملة في العمود الأخير.

في السّطر الأوّل والثّالث سيتم رسم الأقواس باتجاه عقارب الساعة، وفي السّطر الثّاني والرّابع سيتم رسم الأقواس عكس اتجاه عقارب الساعة.

أخيرًا الشرط if سيقوم بعرض أقواس مُفرغة Stroke في الجزء العلوي من الصّفوف وأقواس ذات مساحة لونية solid في الجزء السُّفلي من الصّفوف. سنكون بحاجة إلى canvas بقياسات 150*200.

function draw() { 
  var canvas = document.getElementById(‘canvas’); 
  if (canvas.getContext) { 
    var ctx = canvas.getContext(‘2d’);
    for (var i = 0; i < 4; i++) { 
      for (var j = 0; j < 3; j++) { 
        ctx.beginPath(); 
        var x = 25 + j * 50; 
        var y = 25 + i * 50; 
        var radius = 20; 
        var startAngle = 0; 
        var endAngle = Math.PI + (Math.PI * j) / 2; 
        var anticlockwise = i % 2 == 0 ? false : true; 
        ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise); 

        if (i > 1) { ctx.fill(); } else { ctx.stroke(); } 
      } 
    } 
  } 
}

565c4f2d8adfe__6.thumb.png.203eda53b6f6e

المنحنيات التكعيبية والتربيعية Cubic and Quadratic Curves

تخيّل أنّك تريد إنشاء قوس مائل أو قوس بقمة حادة أو أي نوع من الأقواس المعقدة، توفّر لك الدَّالة ()quadraticCurveTo إمكانيّة رسم أقواس ومنحنيات تربيعية Quadratic Curves والدَّالة ()bezierCurveTo تقوم برسم أقواس أو منحنيات تكعيبية cubic أو ما يعرف بـBézier Curves منحنيات بيزيير. 

7_(1).thumb.jpg.56325aab1e1bf74ad4b20a15

1- رسم منحني تربيعي

تقوم الدالة (quadraticCurveTo(cp1x, cp1y, x, y برسم منحني بيزيير Bézier تربيعي من النُّقطة الحاليّة إلى نقطة معيّنة محددة بـ (x, y) باستخدام نقطة تحكم control point المحدّدة بـ cp1x و cp1y.

تقوم الدَّالة (bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y برسم منحني بيزيير Bézier تكعيبي cubic باستخدام النّقاط المتحكّمة عن طريق (cp1x, cp1y) و (cp2x, cp2y).

الصورة التّالية توضّح الفرق بين المنحنيين Quadratic و cubic:

565c4f2f906dd__8.thumb.png.4630b6d399ea2

لاحظ أن منحني Bézier من الدّرجة الرّابعة لديه نقطة بداية ونقطة نهاية (النقاط الزرقاء) ونقطة تحكم واحدة (النُّقطة الحمراء) في حين أن منحني Bézier من الدرجة الثالثة لديه نقطتي تحكم. 

المعاملين x و y في كلا الدّالتين هما إحداثيات نقطة النّهاية. المعاملين cp1x و cp1y هما إحداثيات نقطة التحكم الأولى، المعاملين cp2x و cp2y هما إحداثيات نقطة التحكم الثانية. 

لنأخذ مثالًا عن كيفية إنشاء منحني Bézier تربيعي سيكون المثال مسلي بعض الشيء إذ أنني سأقوم بإنشاء بالون محادثة speech balloon:

function draw() { 
  var canvas = document.getElementById('canvas'); 
  if (canvas.getContext) { 
    var ctx = canvas.getContext('2d'); 
    ctx.beginPath(); 
    ctx.moveTo(75, 25); 
    ctx.quadraticCurveTo(25, 25, 25, 62.5); 
    ctx.quadraticCurveTo(25, 100, 50, 100); 
    ctx.quadraticCurveTo(50, 120, 30, 125); 
    ctx.quadraticCurveTo(60, 120, 65, 100); 
    ctx.quadraticCurveTo(125, 100, 125, 62.5); 
    ctx.quadraticCurveTo(125, 25, 75, 25); 
    ctx.stroke(); 
  } 
}

تقوم الدَّالة ()quadraticCurveTo برسم منحني بالإحداثيات ونقطة التحكم المناسبة عند استدعائها بالقيم وبالترتيب أعلاه: 

565c4f5132a76__curve_1.thumb.png.cbb1127

565c4f527fd60__curve_2.thumb.png.62620b2

565c4f5357e5b__curve_3.thumb.png.1e65c5c

565c4f54212d5__curve_4.thumb.png.2dd95e7

565c4f55e1c33__curve_5.thumb.png.ed8ae5c

565c4f5a1584e__curve_6.thumb.png.9aa4c2f

2- رسم منحي تكعيبي

في المثال التالي شرح استخدام الدَّالة (bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y لرسم منحنيات من الدرجة الثالثة. 

تفيدنا هذه الدَّالة في رسم أشكال أكثر تعقيدًا لنأخذ مثلًا شكل القلب:

function draw() { 
  var canvas = document.getElementById(‘canvas’); 
  if (canvas.getContext) { 
    var ctx = canvas.getContext(‘2d’);
    ctx.beginPath(); 
    ctx.fillStyle = "rgb(200,0,0)"; 
    ctx.moveTo(75, 40); 
    ctx.bezierCurveTo(75, 37, 70, 25, 50, 25); 
    ctx.bezierCurveTo(20, 25, 20, 62.5, 20, 62.5); 
    ctx.bezierCurveTo(20, 80, 40, 102, 75, 120); 
    ctx.bezierCurveTo(110, 102, 130, 80, 130, 62.5); 
    ctx.bezierCurveTo(130, 62.5, 130, 25, 100, 25); 
    ctx.bezierCurveTo(85, 25, 75, 37, 75, 40); 
    ctx.fill(); 
  } 
}

ينتج عن استدعاء الدَّالة (bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y بالقيم وبالترتيب أعلاه الشكل:

565c4f38cfac9__cubic_1.thumb.png.ec850cb

565c4f3a30864__cubic_2.thumb.png.1807259

565c4f3b44307__cubic_3.thumb.png.90a40e1

565c4f3bec5be__cubic_4.thumb.png.529c778

565c4f3ce19b4__cubic_5.thumb.png.f83663d

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

سنقوم في الدرس المقبل بـإنشاء شكل رسومي أكثر احترافية باستخدام كل تلك الأدوات معًا.

المصادر


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

أفضل التعليقات



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...