कैनवस पर टाइपिंग इफ़ेक्ट

मेरा बैकग्राउंड

मुझे <canvas> के बारे में 2006 में पता चला, जब Firefox का वर्शन 2.0 रिलीज़ हुआ था. मैंने Ajaxian पर ट्रांसफ़ॉर्मेशन मैट्रिक्स के बारे में एक लेख पढ़ा. इससे मुझे अपना पहला <canvas> वेब-ऐप्लिकेशन, Color Sphere (2007) बनाने की प्रेरणा मिली. इसने मुझे रंगों और ग्राफ़िक प्रिमिटिव की दुनिया से रूबरू कराया. इससे मुझे Sketchpad (2007-2008) बनाने की प्रेरणा मिली. यह ब्राउज़र में "Paint से बेहतर" ऐप्लिकेशन बनाने की एक कोशिश थी. इन प्रयोगों की वजह से, मैंने अपने पुराने दोस्त चार्ल्स प्रिचर्ड के साथ मिलकर, Mugtug स्टार्टअप शुरू किया. हम HTML5 <canvas> में Darkroom डेवलप कर रहे हैं. Darkroom, फ़ोटो शेयर करने वाला एक ऐसा ऐप्लिकेशन है जो ओरिजनल फ़ोटो में बदलाव नहीं करता. इसमें पिक्सल पर आधारित फ़िल्टर के साथ-साथ, वेक्टर पर आधारित टाइपोग्राफ़ी और ड्रॉइंग की सुविधा भी मिलती है.

परिचय

Canvas बैनर ग्राफ़िक.

<canvas>, Javascript प्रोग्रामर को उनकी स्क्रीन पर मौजूद रंगों, वेक्टर, और पिक्सल का पूरा कंट्रोल देता है. इससे मॉनिटर पर विज़ुअल मेकअप किया जा सकता है.

यहां दिए गए उदाहरण, <canvas> के एक ऐसे सेक्शन के बारे में हैं जिस पर ज़्यादा ध्यान नहीं दिया गया है. यह सेक्शन है, टेक्स्ट-इफ़ेक्ट बनाना. <canvas> में कई तरह के टेक्स्ट-इफ़ेक्ट बनाए जा सकते हैं. ये इफ़ेक्ट आपकी कल्पना से भी ज़्यादा हो सकते हैं. इन डेमो में, इस बात की जानकारी दी गई है कि किस तरह के टेक्स्ट-इफ़ेक्ट बनाए जा सकते हैं. हालांकि, इस लेख में हम "टेक्स्ट" के बारे में बात कर रहे हैं, लेकिन इन तरीकों को किसी भी वेक्टर ऑब्जेक्ट पर लागू किया जा सकता है. इससे गेम और अन्य ऐप्लिकेशन में शानदार विज़ुअल बनाए जा सकते हैं:

<canvas> में टेक्स्ट-शैडो.
सीएसएस जैसे टेक्स्ट इफ़ेक्ट, <canvas> में क्लिपिंग मास्क बनाना, <canvas> में मेट्रिक ढूंढना, और शैडो प्रॉपर्टी का इस्तेमाल करना.
नियॉन-रेनबो, ज़ेबरा-रिफ़्लेक्शन - चेनिंग इफ़ेक्ट.
Photoshop जैसे टेक्स्ट-इफ़ेक्ट, <canvas> globalCompositeOperation, createLinearGradient, createPattern के इस्तेमाल के उदाहरण.
<canvas> में इनर और आउटर शैडो
एक ऐसी सुविधा के बारे में बताया गया है जिसके बारे में कम ही लोग जानते हैं. इसमें घड़ी की सुई की दिशा में और घड़ी की सुई की उल्टी दिशा में वाइंडिंग का इस्तेमाल करके, ड्रॉप-शैडो (इनर-शैडो) का इन्वर्स बनाया गया है.
स्पेसएज - जनरेटिव इफ़ेक्ट.
जनरेटिव एआई पर आधारित टेक्स्ट-इफ़ेक्ट, जो <canvas> में hsl() कलर-साइकलिंग और window.requestAnimationFrame का इस्तेमाल करके बनाया गया है. इससे मोशन का एहसास होता है.

कैनवस में टेक्स्ट-शैडो

CSS3 की खास बातों में, मेरी पसंदीदा चीज़ों में से एक (border-radius, web-gradients, और अन्य चीज़ों के साथ) शैडो बनाने की सुविधा है. सीएसएस और <canvas> शैडो के बीच के अंतर को समझना ज़रूरी है. खास तौर पर:

सीएसएस दो तरीकों का इस्तेमाल करती है; div, span जैसे बॉक्स-एलिमेंट के लिए box-shadow और टेक्स्ट कॉन्टेंट के लिए text-shadow.

<canvas> में एक तरह की शैडो होती है. इसका इस्तेमाल सभी वेक्टर ऑब्जेक्ट के लिए किया जाता है. जैसे, ctx.moveTo, ctx.lineTo, ctx.bezierCurveTo, ctx.quadradicCurveTo, ctx.arc, ctx.rect, ctx.fillText, ctx.strokeText वगैरह. <canvas> में शैडो बनाने के लिए, इन चार प्रॉपर्टी पर टैप करें:

ctx.shadowColor = "red" // string
शैडो का रंग; आरजीबी, आरजीबीए, एचएसएल, हेक्स, और अन्य इनपुट मान्य हैं.
ctx.shadowOffsetX = 0; // integer
टेक्स्ट के हिसाब से, शैडो की हॉरिज़ॉन्टल दूरी.
ctx.shadowOffsetY = 0; // integer
टेक्स्ट के हिसाब से, शैडो की वर्टिकल दूरी.
ctx.shadowBlur = 10; // integer
शैडो को धुंधला करने वाला इफ़ेक्ट. वैल्यू जितनी ज़्यादा होगी, शैडो उतना ही धुंधला होगा.

चलिए, सबसे पहले यह देखते हैं कि <canvas>, सीएसएस इफ़ेक्ट को कैसे एम्युलेट कर सकता है. Google इमेज पर "css text-shadow" खोजने पर, हमें कुछ बेहतरीन डेमो मिले, जिन्हें हम कॉपी कर सकते हैं; Line25, Stereoscopic, और Shadow 3D.

सीएसएस 3D ग्राफ़िक

स्टीरियोस्कोपिक 3D इफ़ेक्ट, कोड की एक आसान लाइन का उदाहरण है. इसका इस्तेमाल करके, बेहतरीन इफ़ेक्ट बनाए जा सकते हैं. ज़्यादा जानकारी के लिए, ऐनाग्लिफ़ इमेज देखें. CSS की इस लाइन की मदद से, हम 3D लाल/स्यान चश्मे (जो आपको 3D फ़िल्मों में दिए जाते हैं) से देखने पर गहराई का भ्रम पैदा कर सकते हैं:

text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;

इस स्ट्रिंग को <canvas> में बदलते समय, दो बातों का ध्यान रखना ज़रूरी है:

  1. इसमें कोई शैडो-ब्लर (तीसरी वैल्यू) नहीं है. इसलिए, शैडो को चलाने की कोई वजह नहीं है, क्योंकि 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>
  1. <canvas> में EM का इस्तेमाल नहीं किया जा सकता. इसलिए, उन्हें PX में बदलना होगा. हम DOM में एक जैसे फ़ॉन्ट-प्रॉपर्टी वाला एलिमेंट बनाकर और चौड़ाई को मेज़र किए जाने वाले फ़ॉर्मैट पर सेट करके, PT, PC, EM, EX, PX वगैरह के बीच कन्वर्ज़न रेशियो का पता लगा सकते हैं. उदाहरण के लिए, EM -> PX कन्वर्ज़न को कैप्चर करने के लिए, हम DOM एलिमेंट को "height: 1em" के साथ मेज़र करेंगे. इससे मिलने वाला offsetHeight यह बताएगा कि हर EM में कितने PX हैं.
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-height" कहा जाता है. पहले, प्रिंटिंग प्रेस में "M" अक्षर की ऊंचाई को em-height कहा जाता था) और टेक्स्ट की चौड़ाई पता होनी चाहिए. हम ctx.measureText().width का इस्तेमाल करके चौड़ाई का पता लगा सकते हैं. हालांकि, ctx.measureText().height मौजूद नहीं है.

खुशकिस्मती से, सीएसएस हैक-आर्ड्री (सीएसएस मेज़रमेंट का इस्तेमाल करके, <canvas> के पुराने इंप्लिमेंटेशन को ठीक करने के ज़्यादा तरीकों के लिए, टाइपोग्राफ़िक मेट्रिक देखें) के ज़रिए, हम एक ही फ़ॉन्ट-प्रॉपर्टी वाले <span> के offsetHeight को मेज़र करके, टेक्स्ट की ऊंचाई का पता लगा सकते हैं:

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> कमांड को मैन्युअल तरीके से नहीं डालना चाहेंगे, इसलिए मैंने डेमो सोर्स में एक आसान टेक्स्ट-शैडो पार्सर शामिल किया है; इस तरह आप इसे सीएसएस कमांड दे सकते हैं और इससे <canvas> कमांड जनरेट कर सकते हैं. अब हमारे <canvas> एलिमेंट में कई तरह की स्टाइल हैं, जिन्हें इसमें शामिल किया जा सकता है. इन शैडो-इफ़ेक्ट का इस्तेमाल किसी भी वेक्टर ऑब्जेक्ट पर किया जा सकता है. जैसे, वेबफ़ॉन्ट, एसवीजी से इंपोर्ट किए गए जटिल आकार, जनरेटिव वेक्टर आकार वगैरह!

कैनवस इफ़ेक्ट में टेक्स्ट शैडो

इंटरमिशन (पिक्सेल-पुशिंग पर टेंजेंट)

लेख के इस सेक्शन को लिखते समय, स्टीरियोस्कोपिक के उदाहरण ने मुझे उत्सुक कर दिया. <canvas> और अलग-अलग ऐंगल से ली गई दो इमेज का इस्तेमाल करके, 3D-मूवी-स्क्रीन इफ़ेक्ट बनाना कितना मुश्किल होगा? ऐसा लगता है कि यह ज़्यादा मुश्किल नहीं है. यहां दिया गया कर्नल, पहली इमेज (डेटा) के लाल चैनल को दूसरी इमेज (डेटा2) के स्यान चैनल के साथ जोड़ता है:

data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;

अब किसी को बस अपने माथे पर दो iPhone को डक्ट-टेप से चिपकाना होगा और एक ही समय पर "वीडियो रिकॉर्ड करें" पर क्लिक करना होगा. इसके बाद, हम HTML5 में अपनी 3D फ़िल्में बना सकते हैं. कौन आगे आना चाहता है?

3D ग्लासेज़

नियॉन-रेनबो, ज़ेबरा-रिफ़्लेक्शन—चेनिंग इफ़ेक्ट

<canvas> में एक से ज़्यादा इफ़ेक्ट को चेन करना आसान हो सकता है. हालांकि, इसके लिए globalCompositeOperation (GCO) की बुनियादी जानकारी होना ज़रूरी है. GIMP (या Photoshop) के साथ ऑपरेशन की तुलना करने के लिए: <canvas> में 12 GCO हैं गहरे और हल्के को लेयर ब्लेंड-मोड के रूप में माना जा सकता है; अन्य 10 ऑपरेशन लेयर पर अल्फा मास्क के रूप में लागू किए जाते हैं (एक लेयर दूसरी लेयर के पिक्सल हटाती है). globalCompositeOperation, "लेयर" (या हमारे मामले में, कोड की स्ट्रिंग) को एक साथ जोड़ता है. साथ ही, उन्हें नए और दिलचस्प तरीकों से जोड़ता है:

चेनिंग इफ़ेक्ट के ग्राफ़िक

globalCompositeOperation चार्ट, काम करने वाले GCO मोड दिखाता है. इस चार्ट में, कलर-स्पेक्ट्रम के बड़े हिस्से और ऐल्फ़ा ट्रांसपेरेंसी के कई लेवल का इस्तेमाल किया जाता है, ताकि यह देखा जा सके कि क्या उम्मीद की जा सकती है. हमारा सुझाव है कि टेक्स्ट के ब्यौरे के लिए, Mozilla का globalCompositeOperation रेफ़रंस देखें. ज़्यादा जानकारी के लिए, पोर्टर डफ़ के कंपोज़िटिंग डिजिटल इमेज में, ऑपरेशन के काम करने का तरीका जानें.

मेरा पसंदीदा मोड globalCompositeOperation="lighter" है. लाइटर, जोड़े गए पिक्सल को उसी तरह मिक्स करता है जिस तरह रोशनी मिक्स होती है. जब लाल, हरे, और सफ़ेद रंग की रोशनी पूरी तीव्रता पर होती है, तो हमें सफ़ेद रोशनी दिखती है. यह एक शानदार सुविधा है, खास तौर पर तब, जब <canvas> को कम globalAlpha पर सेट किया गया हो. इससे बेहतर कंट्रोल और स्मूद किनारे मिलते हैं. Lighter का इस्तेमाल कई कामों के लिए किया गया है. हाल ही में, मुझे http://weavesilk.com/ पर मौजूद, HTML5 डेस्कटॉप बैकग्राउंड क्रिएटर का इस्तेमाल करना सबसे ज़्यादा पसंद आया. मेरे एक डेमो, Breathing Galaxies (JS1k) में भी लाइटर मोड का इस्तेमाल किया गया है. इन दोनों उदाहरणों के पैटर्न से, आपको यह समझने में मदद मिलेगी कि इस मोड का क्या असर होता है.

पर जाएं

globalCompositeOperation browser handling.

नियॉन-इंद्रधनुष के रंगों में जिटर इफ़ेक्ट

इस डेमो में, हम globalCompositeOperation (source-in, lighter, और darker) का इस्तेमाल करके, इफ़ेक्ट को एक साथ जोड़ेंगे. इससे हमें Photoshop की तरह, नियॉन-रेनबो-ग्लो के साथ-साथ, झिलमिलाती हुई आउटलाइन मिलेगी. यह डेमो, "<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 के बेहतरीन रिसॉर्स से प्रेरित है. इसमें बताया गया है कि सीएसएस की मदद से अपने पेज को कैसे आकर्षक बनाया जाए. यह इस आइडिया को थोड़ा और आगे ले जाता है, जिससे टेक्स्ट के लिए "रिफ़्लेक्शन" बनता है - जैसे कि 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> में, यूनिकोड वर्ण 0x2708 से:

यूनिकोड ग्राफ़िक

…इस शेडेड उदाहरण में:

शेड वाला उदाहरण

…इसे ctx.strokeText() को कई बार कॉल करके हासिल किया जा सकता है. इसके लिए, lineWidth को कम (0.25) रखना होगा. साथ ही, x-ऑफ़सेट और ऐल्फ़ा को धीरे-धीरे कम करना होगा. इससे हमारे वेक्टर एलिमेंट में मोशन का एहसास होगा.

एलिमेंट की XY पोज़िशन को साइन/कोसाइन वेव पर मैप करके और एचएसएल प्रॉपर्टी का इस्तेमाल करके रंगों को साइकल करके, हम ज़्यादा दिलचस्प इफ़ेक्ट बना सकते हैं. जैसे, यह "बायोहैज़र्ड" उदाहरण:

एचएसएल साइकलिंग इफ़ेक्ट

एचएसएल: ह्यू, सैचुरेशन, लाइटनेस (1978)

एचएसएल, सीएसएस3 की खास जानकारी में नया फ़ॉर्मैट है. एचईएक्स को कंप्यूटर के लिए डिज़ाइन किया गया था, जबकि एचएसएल को इंसानों के लिए डिज़ाइन किया गया है.

एचएसएल का इस्तेमाल करना कितना आसान है, यह दिखाने के लिए हम कलर-स्पेक्ट्रम में साइकल करेंगे. इसके लिए, हम "ह्यू" को 360 से बढ़ाएंगे. ह्यू को स्पेक्ट्रम में सिलिंडर के आकार में मैप किया जाता है. लाइटनेस से यह कंट्रोल किया जाता है कि रंग कितना गहरा/हल्का है. 0% का मतलब है कि पिक्सल काले रंग का है, जबकि 100% का मतलब है कि पिक्सल सफ़ेद रंग का है. 'कलर को गहरा या फीका करें' से, यह तय किया जाता है कि कलर कितना चमकदार या चटक दिखे. 0% पर सेट करने से स्लेटी कलर बनता है और 100% पर सेट करने से चटक कलर बनता है.

एचएसएल ग्राफ़िक

एचएसएल एक नया स्टैंडर्ड है. इसलिए, हो सकता है कि आपको पुराने ब्राउज़र के साथ काम करना जारी रखना हो. ऐसा कलर-स्पेस कन्वर्ज़न की मदद से किया जा सकता है. यहां दिया गया कोड, एचएसएल ऑब्जेक्ट { H: 360, S: 100, L: 100} को स्वीकार करता है और आरजीबी ऑब्जेक्ट { R: 255, G: 255, B: 255 } को आउटपुट करता है. इसके बाद, उन वैल्यू का इस्तेमाल करके आरजीबी या आरजीबीए स्ट्रिंग बनाई जा सकती है. ज़्यादा जानकारी के लिए, HSL पर Wikipedia का अहम लेख पढ़ें.

// 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);
        };
})();

इस नए स्टैंडर्ड पर स्विच करते समय, पुराने ब्राउज़र को ज़्यादा से ज़्यादा सपोर्ट करने के लिए, इसे 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);
})();
Notes Blur Graphic
ऐनिमेशन ग्राफ़िक
मैट्रिक्स ग्राफ़िक

सोर्स-कोड

ब्राउज़र वेंडर-स्फ़ीयर से मिलने वाली सहायता की वजह से, <canvas> के भविष्य को लेकर कोई सवाल नहीं है. इसे PhoneGap का इस्तेमाल करके, iPhone/Android/डेस्कटॉप पर एक्ज़ीक्यूटेबल फ़ाइलों में पोर्ट किया जा सकता है या

टाइटेनियम.