معلوماتي الأساسية
تعرّفتُ على <canvas>
في عام 2006 عند إصدار الإصدار 2.0 من Firefox. ألهمتني مقالة على Ajaxian، التي تصف مصفوفة التحويل، لإنشاء أول تطبيق <canvas>
ويب لي، وهو Color Sphere (2007). وقد شغّل هذا البرنامج شغفي بالألوان والأشكال الأساسية للرسومات، ما ألهمني بإنشاء Sketchpad (من 2007 إلى 2008) في محاولة لإنشاء تطبيق "أفضل من Paint" في المتصفّح.
أدّت هذه التجارب في النهاية إلى إنشاء شركة Mugtug الناشئة مع
صديقي القديم تشارلز بريتشارد. نعمل على تطوير Darkroom في HTML5 <canvas>
. Darkroom
هو تطبيق لمشاركة الصور بدون إتلافها، ويجمع بين إمكانات الفلاتر المستندة إلى البكسل
والطباعة والرسم المستندَين إلى المتجهات.
مقدمة

<canvas>
يوفّر لمبرمجي JavaScript إمكانية التحكّم الكامل في الألوان
المتجهات والوحدات البكسلية على شاشاتهم، أي العناصر المرئية للشاشة.
تتناول الأمثلة التالية مجالًا واحدًا في <canvas>
لم يتم تسليط الضوء عليه
كثيرًا، وهو إنشاء تأثيرات نصية. إنّ مجموعة تأثيرات النص
التي يمكن إنشاؤها في <canvas>
واسعة جدًا، وتغطي هذه العروض التوضيحية
قسمًا فرعيًا من التأثيرات الممكنة. على الرغم من أنّنا نتناول
"النص" في هذه المقالة، يمكن تطبيق الأساليب على أيّ عناصر متّجه،
لإنشاء مرئيات مثيرة في الألعاب والتطبيقات الأخرى:
- ظلال النصوص في
<canvas>
- تأثيرات نصية مشابهة لتأثيرات CSS في
<canvas>
لإنشاء أقنعة اقتصاص والعثور على المقاييس في<canvas>
واستخدام خاصية الظل - تأثيرات متسلسلة مثل قوس قزح النيون وانعكاس الحمار الوحشي
- تأثيرات نصية مثل تلك المتوفّرة في Photoshop في
<canvas>
مثال على استخدام globalCompositeOperation وcreateLinearGradient وcreatePattern - الظلال الداخلية والخارجية في
<canvas>
- تقديم ميزة غير معروفة كثيرًا: استخدام لفّة باتجاه عقارب الساعة أو عكسها لإنشاء تأثير معاكس لظلّ خلفي (الظلّ الداخلي).
- Spaceage - تأثير توليدي تأثير نص
- توليدي في
<canvas>
باستخدام دورة ألوان hsl() وwindow.requestAnimationFrame
لخلق شعور بالحركة
ظلال النصوص في Canvas
إنّ أحد الإضافات المفضّلة لديّ إلى مواصفات CSS3 (إلى جانب border-radius وweb-gradients وغيرها)
هي إمكانية إنشاء الظلال. من المهم معرفة الاختلافات
بين CSS وظلّ <canvas>
، على وجه التحديد:
يستخدم CSS طريقتَين: box-shadow لعناصر المربّعات، مثل div وspan وما إلى ذلك، وtext-shadow لمحتوى النص.
يحتوي <canvas>
على نوع واحد من الظلال، ويتم استخدامه لجميع رسومات
الأشكال الهندسية، مثل ctx.moveTo وctx.lineTo وctx.bezierCurveTo وctx.quadradicCurveTo وctx.arc وctx.rect وctx.fillText وctx.strokeText وما إلى ذلك.
لإنشاء ظلّ في <canvas>
، استخدِم السمات الأربع التالية:
- ctx.shadowColor = "red" // string
- لون الظل: تكون قيم RGB وRGBA وHSL وHEX وغيرها من قيم الإدخال صالحة.
- ctx.shadowOffsetX = 0; // عدد صحيح
- المسافة الأفقية للظل بالنسبة إلى النص
- ctx.shadowOffsetY = 0; // عدد صحيح
- المسافة العمودية للظل بالنسبة إلى النص
- ctx.shadowBlur = 10; // عدد صحيح
- تأثير التمويه على الظل، وكلما زادت القيمة، زاد التمويه.
للبدء، لنطّلِع على كيفية تقليد <canvas>
لتأثيرات CSS.
أدى البحث في "صور Google" عن "css text-shadow" إلى العثور على بعض نماذج العروض التقديمية الرائعة التي يمكننا تقليدها، مثل Line25،
وStereoscopic، و
Shadow 3D.

يُعدّ التأثير الثلاثي الأبعاد الاستيريو (اطّلِع على صورة الإسقاط المجسم لمزيد من المعلومات) مثالاً على سطر رمز بسيط يتم استخدامه بفعالية. باستخدام السطر التالي من CSS، يمكننا إنشاء وهم العمق عند المشاهدة باستخدام نظارات ثلاثية الأبعاد باللون الأحمر/الأزرق السماوي (النوع الذي يُعطى لك في الأفلام الثلاثية الأبعاد):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
هناك نقطتان يجب ملاحظتهما عند تحويل هذه السلسلة إلى <canvas>
:
- لا تتوفّر قيمة shadow-blur (القيمة الثالثة)، لذا ما مِن سبب لتشغيل الظلّ، لأنّ fillText سيؤدي إلى تحقيق النتائج نفسها:
var text = "Hello world!"
ctx.fillStyle = "#000"
ctx.fillText(text, -7, 0);
ctx.fillStyle = "red"
ctx.fillText(text, 0, 0);
ctx.fillStyle = "cyan"
ctx.fillText(text, 7, 0);</pre>
- لا تتوفّر ميزة "الإعلانات المتجاوبة على شبكة البحث" في
<canvas>
، لذا يجب تحويلها إلى إعلانات PX. يمكننا العثور على نسبة التحويل للتحويل بين PT وPC وEM وEX وPX وما إلى ذلك من خلال إنشاء عنصر يتضمن سمات الخط نفسها في DOM، وضبط العرض على التنسيق المطلوب قياسه. على سبيل المثال، لتسجيل عملية التحويل من EM إلى PX، سنقيس عنصر DOM باستخدام "height: 1em"، وسيكون offsetHeight الناتج هو عدد وحدات PX في كل EM.
var font = "20px sans-serif"
var d = document.createElement("span");
d.style.cssText = "font: " + font + " height: 1em; display: block"
// the value to multiply PX 's by to convert to EM 's
var EM2PX = 1 / d.offsetHeight;</pre>
منع الضرب الألفابي
في مثال أكثر تعقيدًا، مثل تأثير النيون المتوفّر في السطر 25، يجب استخدام سمة
shadowBlur لمحاكاة التأثير بشكل صحيح. بما أنّ تأثير
النيون يعتمد على ظلال متعدّدة، نواجه مشكلة، وهي أنّه في <canvas>
لا يمكن أن يتضمّن كل عنصر متّجه سوى ظلّ واحد. لذلك، لرسم عدة
ظلال، عليك رسم نُسخ متعددة من النص فوق نفسه.
ويؤدي ذلك إلى ضرب شفافية العنصر، وفي النهاية إلى ظهور حواف خشنة.

حاولت تشغيل ctx.fillStyle = "rgba(0,0,0,0)"
أو "transparent"
لإخفاء النص مع عرض الظل، ولكن تعذّر تنفيذ هذه المحاولة،
لأنّ الظل هو حاصل ضرب القيمة ألفا لfillStyle، وبالتالي لا يمكن أن يكون الظل
أكثر عتامة من fillStyle.
لحسن الحظ، هناك طريقة لحلّ هذه المشكلة، وهي أن نرسم الظلّ بعيدًا عن النص، مع إبقائهما منفصلَين (كي لا يتداخلا)، وبالتالي إخفاء النص من جانب الشاشة:
var text = "Hello world!"
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = "top"
ctx.shadowColor = "#000"
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);
اقتصاص المحتوى حول مقطع نصي
لتنظيف هذا المشهد قليلاً، يمكننا منع رسم fillText في
البداية (مع السماح برسم الظل) عن طريق إضافة مسار اقتصاص.
لإنشاء مسار اقتصاص يحيط بالنص، نحتاج إلى معرفة
ارتفاع النص (يُعرف باسم "ارتفاع em"، وهو ارتفاع
الحرف "م" على آلة الطباعة) وعرض النص.
يمكننا الحصول على العرض باستخدام ctx.measureText().width
، ولكن
ctx.measureText().height
غير متوفّر.
لحسن الحظ، من خلال خدعة CSS (راجِع مقاييس الطباعة
للاطّلاع على مزيد من الطرق لإصلاح عمليات التنفيذ القديمة من <canvas>
باستخدام قياسات CSS)،
يمكننا العثور على ارتفاع النص من خلال قياس offsetHeight
من <span>
باستخدام سمات الخط نفسها:
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
من هناك، يمكننا إنشاء مستطيل لاستخدامه كمسار اقتصاص، مع تضمين "الظل" أثناء إزالة الشكل الوهمي.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
ربط كل ذلك معًا وتحسينه أثناء التنفيذ: إذا لم يكن للظلام تمويه، يمكن استخدام fillText للحصول على التأثير نفسه، ما يجنبنا إعداد قناع القطع:
var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
var shadow = shadows[n];
var totalWidth = width + shadow.blur * 2;
ctx.save();
ctx.beginPath();
ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
ctx.clip();
if (shadow.blur) { // just run shadow (clip text)
ctx.shadowColor = shadow.color;
ctx.shadowOffsetX = shadow.x + totalWidth;
ctx.shadowOffsetY = shadow.y;
ctx.shadowBlur = shadow.blur;
ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
} else { // just run pseudo-shadow
ctx.fillStyle = shadow.color;
ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
}
ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
ctx.fillStyle = style.color;
ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);
بما أنّك لن تريد إدخال كل أوامر <canvas>
هذه يدويًا، لقد أدرجنا في مصدر العرض التوضيحي برنامجًا بسيطًا لتحليل النصّ المظلّل،
وبالتالي يمكنك إرسال أوامر CSS إليه وإنشاء أوامر <canvas>
.
تتضمّن الآن عناصر <canvas>
مجموعة كاملة من الأنماط التي يمكن دمجها فيها.
يمكن استخدام تأثيرات الظلال نفسها على أي عنصر رسومات متجهّة، بدءًا من WebFonts وحتى
الأشكال المعقدة التي تم استيرادها من ملفات SVG، والأشكال المتّجهّة التوليدية، وما إلى ذلك.

فاصل (لمحة عن زيادة عدد الوحدات البكسيلية)
أثناء كتابة هذا القسم من المقالة، أثارت مثال الصور المجسمة فضولي. ما مدى صعوبة إنشاء تأثير شاشة فيلم ثلاثي الأبعاد باستخدام <canvas>
وصورتَين تم التقاطهما من منظورَين مختلفَين قليلاً؟ يبدو أنّه ليس صعبًا جدًا. تجمع النواة التالية بين القناة الحمراء للصورة الأولى (data) والقناة السماوية للصورة الثانية (data2):
data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;
الآن، ما على المستخدم سوى لصق هاتفَي iPhone على جبهته والنقر على "تسجيل فيديو" في الوقت نفسه، وسنتمكن من إنشاء أفلامنا الثلاثية الأبعاد الخاصة بنا باستخدام HTML5. هل هناك أي متطوّعين؟

تأثيرات متسلسلة مثل قوس قزح النيون وانعكاس الحمار الوحشي
يمكن أن يكون تسلسل تأثيرات متعددة في <canvas>
بسيطًا، ولكن يجب أن يكون لديك معرفة dasar
بـ globalCompositeOperation (GCO). لمقارنة
العمليات بخدمة GIMP (أو Photoshop): هناك 12 عملية GCO في <canvas>
darker، ويمكن اعتبار lighter كوضعَي دمج للطبقات،
وتُطبَّق العمليات العشر الأخرى على الطبقات كأقنعة ألفا (تزيل طبقة واحدة
وحدات البكسل في الطبقة الأخرى). تربط globalCompositeOperation "الطبقات"
(أو في حالتنا، سلاسل الرموز البرمجية) معًا، وتدمجها بطرق جديدة ومثيرة:

يعرض الرسم البياني globalCompositeOperation أوضاع GCO أثناء العمل. يستخدم هذا الرسم البياني جزءًا كبيرًا من طيف الألوان ومستويات متعددة من شفافية ألفا من أجل الاطّلاع بالتفصيل على النتائج المتوقّعة. أنصحك بالاطّلاع على مرجع globalCompositeOperation في Mozilla للحصول على أوصاف نصية. لمزيد من البحث، يمكنك الاطّلاع على كيفية عمل العملية في مقالة Compositing Digital Images التي كتبها "بورتر دوف".
وضعي المفضّل هو globalCompositeOperation="lighter". يمزج الضوء الملوّن
الوحدات المرفقة بالطريقة نفسها التي يمزج بها الضوء. عندما يكون الضوء الأحمر والأخضر والأبيض عند
الشدة الكاملة، نرى الضوء الأبيض. هذه ميزة مثيرة للاهتمام يمكن التلاعب بها، خاصةً عند ضبط <canvas>
على قيمة منخفضة لخيار globalAlpha، ما يتيح التحكّم بشكل أدق وحدود أكثر سلاسة. تم استخدام Lighter في العديد من التطبيقات، ومن أمثلة ذلك أداة
إنشاء خلفيات سطح المكتب بتنسيق HTML5 المتوفّرة على الرابط http://weavesilk.com/.
أحد العروض التوضيحية التي أقدّمها، وهو Breathing Galaxies (JS1k)،
يستخدم أيضًا الوضع الأخفّ وزنًا. من خلال رسم أنماط من هذين المثالَين، ستبدأ في
التعرّف على التأثير الذي يحقّقه هذا الوضع.
معالجة المتصفّح لـ globalCompositeOperation
تأثير وميض بألوان النيون والقوس قزح
في العرض التوضيحي التالي، سنطبّق تأثير يشبه تأثيرات Photoshop
يشبه ضوء النيون الملوّن مع مخطط متقطع، وذلك من خلال ربط التأثيرات معًا باستخدام
globalCompositeOperation (source-in وlighter وdarker).
هذا العرض التوضيحي هو خطوة متقدمة من عرض "ظلال النصوص في <canvas>
"، ويستخدم الاستراتيجية نفسها في فصل الظل عن النص (راجِع القسم السابق):

function neonLightEffect() {
var text = "alert('"+String.fromCharCode(0x2665)+"')";
var font = "120px Futura, Helvetica, sans-serif";
var jitter = 25; // the distance of the maximum jitter
var offsetX = 30;
var offsetY = 70;
var blur = getBlurValue(100);
// save state
ctx.save();
ctx.font = font;
// calculate width + height of text-block
var metrics = getMetrics(text, font);
// create clipping mask around text-effect
ctx.rect(offsetX - blur/2, offsetY - blur/2,
offsetX + metrics.width + blur, metrics.height + blur);
ctx.clip();
// create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
ctx.save();
ctx.fillStyle = "#fff";
ctx.shadowColor = "rgba(0,0,0,1)";
ctx.shadowOffsetX = metrics.width + blur;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
ctx.restore();
// create the rainbow linear-gradient
var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
// change composite so source is applied within the shadow-blur
ctx.globalCompositeOperation = "source-atop";
// apply gradient to shadow-blur
ctx.fillStyle = gradient;
ctx.fillRect(offsetX - jitter/2, offsetY,
metrics.width + offsetX, metrics.height + offsetY);
// change composite to mix as light
ctx.globalCompositeOperation = "lighter";
// multiply the layer
ctx.globalAlpha = 0.7
ctx.drawImage(ctx.canvas, 0, 0);
ctx.drawImage(ctx.canvas, 0, 0);
ctx.globalAlpha = 1
// draw white-text ontop of glow
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.fillText(text, offsetX, offsetY + metrics.top);
// created jittered stroke
ctx.lineWidth = 0.80;
ctx.strokeStyle = "rgba(255,255,255,0.25)";
var i = 10; while(i--) {
var left = jitter / 2 - Math.random() * jitter;
var top = jitter / 2 - Math.random() * jitter;
ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
}
ctx.strokeStyle = "rgba(0,0,0,0.20)";
ctx.strokeText(text, offsetX, offsetY + metrics.top);
ctx.restore();
};
تأثير انعكاس الحمار الوحشي
استُوحي تأثير "انعكاس الحمار الوحشي" من مرجع WebDesignerWall الممتاز حول كيفية تحسين صفحتك باستخدام CSS. ينقل هذا الإجراء الفكرة إلى مستوى أعلى قليلاً، من خلال إنشاء "انعكاس" للنص، مثل ما قد يظهر أمامك في iTunes. يجمع التأثير بين fillColor (أبيض) وcreatePattern (zebra.png) وlinearGradient (لمعان)، ما يوضّح إمكانية تطبيق عدة أنواع للملء على كل عنصر متجه:

function sleekZebraEffect() {
// inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
var text = "Sleek Zebra...";
var font = "100px Futura, Helvetica, sans-serif";
// save state
ctx.save();
ctx.font = font;
// getMetrics calculates:
// width + height of text-block
// top + middle + bottom baseline
var metrics = getMetrics(text, font);
var offsetRefectionY = -20;
var offsetY = 70;
var offsetX = 60;
// throwing a linear-gradient in to shine up the text
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.1, '#000');
gradient.addColorStop(0.35, '#fff');
gradient.addColorStop(0.65, '#fff');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient
ctx.fillText(text, offsetX, offsetY + metrics.top);
// draw reflected text
ctx.save();
ctx.globalCompositeOperation = "source-over";
ctx.translate(0, metrics.height + offsetRefectionY)
ctx.scale(1, -1);
ctx.font = font;
ctx.fillStyle = "#fff";
ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
ctx.scale(1, -1);
// cut the gradient out of the reflected text
ctx.globalCompositeOperation = "destination-out";
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient;
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);
// restore back to original transform state
ctx.restore();
// using source-atop to allow the transparent .png to show through to the gradient
ctx.globalCompositeOperation = "source-atop";
// creating pattern from <image> sourced.
ctx.fillStyle = ctx.createPattern(image, 'repeat');
// fill the height of two em-boxes, to encompass both normal and reflected state
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
ctx.restore();
};
الظلال الداخلية/الخارجية في "لوحة الرسم"
لا تتناول مواصفات <canvas>
موضوع الظلال "الداخلية" مقارنةً بالظلال "الخارجية". في الواقع، قد تعتقد في البداية أنّه لا يمكن استخدام الظل "الداخلي". هذا ليس صحيحًا.
إنّ تفعيل هذه الميزة أصعب قليلاً. وكما هو مقترَح في مشاركة حديثة
من F1LT3R،
يمكنك إنشاء ظلال داخلية باستخدام السمات الفريدة لقواعد الالتفاف باتجاه عقارب الساعة أو عكسها. لإجراء ذلك، يمكنك إنشاء "ظل داخلي" من خلال رسم مستطيل
الحاوية، ثم رسم شكل مقطوع باستخدام قواعد الالتفاف المعاكس، ما يؤدي إلى
إنشاء الشكل المعاكس.
يسمح المثال التالي بتصميم inner-shadow وfillStyle باستخدام اللون والتدرّج والنمط في الوقت نفسه. يمكنك تحديد دوران النمط بشكل فردي، ويُرجى ملاحظة أنّ خطوط الحمار الوحشي أصبحت الآن متعامدة مع بعضها. يتم استخدام قناع اقتصاص بحجم مربّع الحدود، ما يزيل الحاجة إلى استخدام حاوية فائقة الحجم لإحاطة شكل القطع، ما يؤدي إلى تحسين السرعة من خلال منع معالجة الأجزاء غير الضرورية من الظل.

function innerShadow() {
function drawShape() { // draw anti-clockwise
ctx.arc(0, 0, 100, 0, Math.PI * 2, true); // Outer circle
ctx.moveTo(70, 0);
ctx.arc(0, 0, 70, 0, Math.PI, false); // Mouth
ctx.moveTo(-20, -20);
ctx.arc(30, -30, 10, 0, Math.PI * 2, false); // Left eye
ctx.moveTo(140, 70);
ctx.arc(-20, -30, 10, 0, Math.PI * 2, false); // Right eye
};
var width = 200;
var offset = width + 50;
var innerColor = "rgba(0,0,0,1)";
var outerColor = "rgba(0,0,0,1)";
ctx.translate(150, 170);
// apply inner-shadow
ctx.save();
ctx.fillStyle = "#000";
ctx.shadowColor = innerColor;
ctx.shadowBlur = getBlurValue(120);
ctx.shadowOffsetX = -15;
ctx.shadowOffsetY = 15;
// create clipping path (around blur + shape, preventing outer-rect blurring)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
ctx.clip();
// apply inner-shadow (w/ clockwise vs. anti-clockwise cutout)
ctx.beginPath();
ctx.rect(-offset/2, -offset/2, offset, offset);
drawShape();
ctx.fill();
ctx.restore();
// cutout temporary rectangle used to create inner-shadow
ctx.globalCompositeOperation = "destination-out";
ctx.fill();
// prepare vector paths
ctx.beginPath();
drawShape();
// apply fill-gradient to inner-shadow
ctx.save();
ctx.globalCompositeOperation = "source-in";
var gradient = ctx.createLinearGradient(-offset/2, 0, offset/2, 0);
gradient.addColorStop(0.3, '#ff0');
gradient.addColorStop(0.7, '#f00');
ctx.fillStyle = gradient;
ctx.fill();
// apply fill-pattern to inner-shadow
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 1;
ctx.rotate(0.9);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply fill-gradient
ctx.save();
ctx.globalCompositeOperation = "destination-over";
var gradient = ctx.createLinearGradient(-offset/2, -offset/2, offset/2, offset/2);
gradient.addColorStop(0.1, '#f00');
gradient.addColorStop(0.5, 'rgba(255,255,0,1)');
gradient.addColorStop(1.0, '#00f');
ctx.fillStyle = gradient
ctx.fill();
// apply fill-pattern
ctx.globalCompositeOperation = "source-atop";
ctx.globalAlpha = 0.2;
ctx.rotate(-0.4);
ctx.fillStyle = ctx.createPattern(image, 'repeat');
ctx.fill();
ctx.restore();
// apply outer-shadow (color-only without temporary layer)
ctx.globalCompositeOperation = "destination-over";
ctx.shadowColor = outerColor;
ctx.shadowBlur = 40;
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 10;
ctx.fillStyle = "#fff";
ctx.fill();
};
من خلال هذه الأمثلة، يمكنك الاطّلاع على أنّه باستخدام globalCompositeOperation، يمكننا تسلسل التأثيرات معًا، ما يؤدي إلى إنشاء تأثيرات أكثر تفصيلاً (باستخدام التمويه والمزج). الشاشة هي كل ما تحتاجه ;)
Spaceage: تأثيرات إبداعية
في <canvas>
، بدءًا من حرف Unicode 0x2708:

…إلى هذا المثال المظلّل:

…يمكن تحقيق ذلك من خلال عمليات استدعاء متعددة لـ ctx.strokeText()
باستخدام سمة lineWidth رفيعة (0.25)،
مع خفض x-offset وalpha ببطء، ما يمنح عناصرنا المتّجهية إحساسًا بالحركة.
من خلال ربط موضع XY للعناصر بموجة جيبية/كوسينية، وتغيير الألوان باستخدام السمة HSL، يمكننا إنشاء تأثيرات أكثر إثارة للاهتمام، مثل مثال "الخطر البيولوجي" هذا:

HSL: Hue, Saturation, Lightness (1978)
إنّ HSL هو تنسيق جديد متوافق مع مواصفات CSS3. على عكس تنسيق HEX الذي تم تصميمه للكمبيوترات، تم تصميم تنسيق HSL ليكون سهل القراءة.
توضيح سهولة استخدام نموذج HSL: للتنقل في طيف الألوان، ما عليك سوى زيادة "الصبغة" من 360 درجة، ويتم ربط الصبغة بالطيف بطريقة أسطوانية. تتحكّم درجة السطوع في مدى قتامة أو سطوع اللون، ويشير الرقم 0% إلى بكسل أسود، في حين يشير الرقم 100% إلى بكسل أبيض. تتحكّم درجة التشبّع في مدى سطوع اللون أو حيويته، ويتم إنشاء الألوان الرمادية بدرجة تشبّع %0، ويتم إنشاء الألوان الزاهية باستخدام قيمة %100.

بما أنّ HSL هو معيار حديث، قد تحتاج إلى مواصلة إتاحة استخدام المتصفّحات القديمة، وهو ما يمكن من خلال تحويل مساحة الألوان. يقبل الرمز التالي عنصر HSL { H: 360, S: 100, L: 100} ويُخرج عنصر RGB { R: 255, G: 255, B: 255 }. من هنا، يمكنك استخدام هذه القيم لإنشاء سلسلة rgb أو rgba. لمزيد من المعلومات المفصّلة، يمكنك الاطّلاع على مقالة ويكيبيديا المفصّلة حول HSL.
// HSL (1978) = H: Hue / S: Saturation / L: Lightness
HSL_RGB = function (o) { // { H: 0-360, S: 0-100, L: 0-100 }
var H = o.H / 360,
S = o.S / 100,
L = o.L / 100,
R, G, B, _1, _2;
function Hue_2_RGB(v1, v2, vH) {
if (vH < 0) vH += 1;
if (vH > 1) vH -= 1;
if ((6 * vH) < 1) return v1 + (v2 - v1) * 6 * vH;
if ((2 * vH) < 1) return v2;
if ((3 * vH) < 2) return v1 + (v2 - v1) * ((2 / 3) - vH) * 6;
return v1;
}
if (S == 0) { // HSL from 0 to 1
R = L * 255;
G = L * 255;
B = L * 255;
} else {
if (L < 0.5) {
_2 = L * (1 + S);
} else {
_2 = (L + S) - (S * L);
}
_1 = 2 * L - _2;
R = 255 * Hue_2_RGB(_1, _2, H + (1 / 3));
G = 255 * Hue_2_RGB(_1, _2, H);
B = 255 * Hue_2_RGB(_1, _2, H - (1 / 3));
}
return {
R: R,
G: G,
B: B
};
};
إنشاء صور متحركة باستخدام requestAnimationFrame
في السابق، كان هناك خياران لإنشاء صور متحركة في JavaScript، وهما setTimeout
وsetInterval
.
window.requestAnimationFrame
، هو المعيار الجديد هنا لاستبدال
كلاهما، ما يساهم في توفير الكهرباء في العالم (وتوفير بعض الوقت على جهاز الكمبيوتر) من خلال
السماح للمتصفّح بتنظيم الرسوم المتحرّكة استنادًا إلى الموارد المتاحة.
تشمل بعض الميزات المهمة ما يلي:
- عندما يغادر المستخدم الإطار، يمكن أن يبطئ عرض الصورة المتحركة أو يتوقف تمامًا لمنع استخدام موارد غير ضرورية.
- هناك حدّ أقصى لعدد اللقطات في الثانية يبلغ 60 لقطة في الثانية. والسبب في ذلك هو أنّه أعلى بكثير من المستوى الذي يمكن للبشر ملاحظته (يرى معظم البشر أنّ اللقطات "سلسة" عند 30 لقطة في الثانية).
في وقت كتابة هذه المقالة، يجب استخدام بادئات خاصة بالمورّد لاستخدام requestAnimationFrame
.
أنشأ "بول آيرش" طبقة مساعدة متوافقة مع جميع المورّدين، في requestAnimationFrame لأجل
الرسوم المتحركة الذكية:
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
وعلاوةً على ذلك، قد يربط المطوّرون الأكثر طموحًا ذلك مع مكوّن polyfill مثل requestAnimationFrame.js (هناك بعض الميزات التي يجب العمل عليها) التي يمكن أن تتوافق مع المتصفّحات القديمة إلى حدٍّ أكبر، أثناء التبديل إلى هذا المعيار الجديد.
(function animate() {
var i = 50;
while(i--) {
if (n > endpos) return;
n += definition;
ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
if (doColorCycle) {
hue = n + color;
ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
}
var x = cos(n / cosdiv) * n * cosmult; // cosine
var y = sin(n / sindiv) * n * sinmult; // sin
ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
}
timeout = window.requestAnimationFrame(animate, 0);
})();



رمز المصدر
بفضل الدعم من جميع مورّدي المتصفّحات، لا شك في
مستقبل <canvas>
، إذ يمكن نقله إلى ملفات التشغيل القابلة للتنفيذ على أجهزة iPhone/Android/Desktop
باستخدام PhoneGap، أو