خلفيتي
تعرّفت على <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> - سأكشف لكم عن ميزة غير معروفة، وهي استخدام اللف في اتجاه عقارب الساعة مقابل اللف في عكس اتجاه عقارب الساعة لإنشاء عكس تظليل القطرات (الظل الداخلي).
- تأثير مستند إلى الذكاء الاصطناعي التوليدي يحاكي عصر الفضاء
- تأثير نصي قائم على الذكاء الاصطناعي التوليدي في
<canvas>باستخدام تدرّج الألوان hsl() وwindow.requestAnimationFrameلإضفاء شعور بالحركة
تظليلات النص في لوحة الرسم
من الإضافات المفضلة لديّ في مواصفات 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; // integer
- المسافة الأفقية للظل بالنسبة إلى النص
- ctx.shadowOffsetY = 0; // integer
- المسافة العمودية للظل بالنسبة إلى النص
- ctx.shadowBlur = 10; // integer
- تأثير التمويه على الظل، كلما زادت القيمة، زاد التمويه.
للبدء، لنتعرّف على كيفية محاكاة <canvas> لتأثيرات CSS.
أدى البحث في "صور بحث Google" عن "css text-shadow" إلى العثور على بعض العروض التوضيحية الرائعة التي يمكننا محاكاتها، مثل Line25 وStereoscopic وShadow 3D.
إنّ التأثير المجسّم الثلاثي الأبعاد (يُرجى الاطّلاع على صورة النقش البارز لمزيد من المعلومات) هو مثال على سطر بسيط من الرموز البرمجية تم استخدامه بشكل رائع. باستخدام سطر CSS التالي، يمكننا إنشاء وهم العمق عند عرضه باستخدام نظارات ثلاثية الأبعاد باللون الأحمر/السماوي (النوع الذي يقدمونه لك في الأفلام ثلاثية الأبعاد):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
هناك أمران يجب ملاحظتهما عند تحويل هذه السلسلة إلى <canvas>:
- لا يوجد تمويه للظل (القيمة الثالثة)، لذلك لا يوجد سبب لتشغيل الظل فعليًا، لأن 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>
- لا تتوفّر وحدات EM في
<canvas>، لذا يجب تحويلها إلى وحدات PX. يمكننا العثور على نسبة التحويل للتحويل بين PT وكمبيوتر شخصي وEM وEX وبكسل وما إلى ذلك من خلال إنشاء عنصر له خصائص الخط نفسها في DOM، وضبط العرض على التنسيق المطلوب قياسه، على سبيل المثال، لالتقاط تحويل EM -> بكسل، سنقيس عنصر DOM باستخدام "height: 1em"، وسيكون offsetHeight الناتج هو عدد وحدات بكسل في كل 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>
منع مضاعفة ألفا
في مثال أكثر تعقيدًا، مثل تأثير النيون الموجود في Line25، يجب استخدام خاصية 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"، وهو تاريخيًا ارتفاع
الحرف "M" في المطبعة) وعرض النص.
يمكننا الحصول على العرض باستخدام 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> على مجموعة كاملة من الأنماط التي يمكن ربطها بها.
يمكن استخدام تأثيرات الظل نفسها على أي عنصر متجه، من خطوط الويب إلى
الأشكال المعقدة المستوردة من ملفات 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> أمرًا بسيطًا، ولكن يجب الإلمام ببعض المعلومات الأساسية حول globalCompositeOperation (GCO). للمقارنة بين
العمليات في GIMP (أو Photoshop): هناك 12 عملية GCO في <canvas>
darker، ويمكن اعتبار lighter كأوضاع دمج الطبقات،
ويتم تطبيق العمليات العشر الأخرى على الطبقات كأقنعة ألفا (تزيل طبقة واحدة
وحدات البكسل للطبقة الأخرى). يربط globalCompositeOperation "الطبقات"
(أو في حالتنا، سلاسل الرموز البرمجية) معًا، ويجمعها بطرق جديدة ومثيرة:
يعرض الرسم البياني globalCompositeOperation أوضاع GCO قيد العمل؛ يستخدم هذا الرسم البياني جزءًا كبيرًا من طيف الألوان ومستويات متعددة من شفافية ألفا لمعرفة ما يمكن توقعه بالتفصيل. أنصحك بالاطّلاع على مرجع globalCompositeOperation من Mozilla للحصول على أوصاف نصية. لإجراء المزيد من البحث، يمكنك التعرّف على كيفية عمل العملية في Compositing Digital Images من Porter Duff.
الوضع المفضّل لديّ هو globalCompositeOperation="lighter". يمزج وضع "أفتح" (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();
};
تأثير انعكاس الحمار الوحشي
تم استلهام تأثير Zebra Reflection من المرجع الممتاز الذي يقدّمه موقع WebDesignerWall حول كيفية تحسين مظهر صفحتك باستخدام CSS. يأخذ هذا الفكرة إلى أبعد من ذلك قليلاً، مما يؤدي إلى إنشاء "انعكاس" للنص - مثل ما قد تراه في iTunes. يجمع التأثير بين fillColor (أبيض) وcreatePattern (zebra.png) وlinearGradient (shine)، ويوضح هذا القدرة على تطبيق أنواع تعبئة متعددة على كل كائن متجه:
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، يمكننا ربط التأثيرات معًا، مما ينتج عنه تأثيرات أكثر تفصيلاً (باستخدام الإخفاء والمزج). الشاشة هي ملعبك ;)
عصر الفضاء: مؤثرات مستندة إلى الذكاء الاصطناعي التوليدي
في <canvas>، عند الانتقال من حرف يونيكود 0x2708:
…إلى هذا المثال المظلّل:
…يمكن تحقيق ذلك من خلال استدعاءات متعددة لـ ctx.strokeText() باستخدام lineWidth رفيع (0.25)،
مع تقليل x-offset وalpha ببطء، ما يمنح عناصر المتجه شعورًا بالحركة.
من خلال ربط موضع العنصر XY بموجة جيبية/جيب تمام، والتنقّل بين الألوان باستخدام السمة HSL، يمكننا إنشاء تأثيرات أكثر إثارة للاهتمام، مثل مثال "الخطر البيولوجي" هذا:
HSL: درجة اللون والتشبّع والإضاءة (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. لمزيد من المعلومات التفصيلية، يمكنك الاطّلاع على مقالة Wikipedia المفيدة حول 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 for
smart animating:
// 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);
};
})();
ويمكن للمطوّرين الأكثر طموحًا ربط ذلك مع poly-fill مثل 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 وأجهزة الكمبيوتر المكتبي باستخدام PhoneGap أو