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

माइकल डील
माइकल डील

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

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

शुरुआती जानकारी

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

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

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

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

कैनवस में टेक्स्ट-परछाइयां

CSS3 के विनिर्देशों (बॉर्डर-दायरा, वेब-ग्रेडिएंट वगैरह के साथ) में मेरी पसंदीदा चीज़ों में से एक है शैडो बनाना. सीएसएस और <canvas> शैडो के बीच का फ़र्क़ समझना ज़रूरी है. खास तौर पर:

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

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

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

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

CSS 3D ग्राफ़िक

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

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

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

  1. कोई शैडो-ब्लर (तीसरा वैल्यू) नहीं है, इसलिए असल में शैडो चलाने की कोई वजह नहीं है, क्योंकि clearText से भी वही नतीजे मिलेंगे:
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. EM, <canvas> में काम नहीं करते. इसलिए, उन्हें PX में बदलना होगा. DOM में एक जैसे फ़ॉन्ट-प्रॉपर्टी वाला एलिमेंट बनाकर और चौड़ाई को मेज़र किए जाने वाले फ़ॉर्मैट में सेट करके, PT, PC, EM, EX, 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 पर मौजूद नियॉन इफ़ेक्ट, शैडोब्लर प्रॉपर्टी को इफ़ेक्ट को एम्युलेट करने के लिए इस्तेमाल किया जाना चाहिए. नियॉन इफ़ेक्ट में एक से ज़्यादा शैडो का इस्तेमाल किया जाता है, इसलिए हमें समस्या आ रही है; <canvas> में, हर वेक्टर ऑब्जेक्ट में सिर्फ़ एक शैडो हो सकती है. इसलिए, एक से ज़्यादा शैडो बनाने के लिए, आपको टेक्स्ट के कई वर्शन बनाने होंगे. इससे ऐल्फ़ा का गुणा और आखिर में दांतेदार किनारे हो जाते हैं.

नियॉन ग्राफ़िक

शैडो दिखाते समय मैंने टेक्स्ट छिपाने के लिए ctx.fillStyle = "rgba(0,0,0,0)" या "transparent" चलाने की कोशिश की... हालांकि, यह कोशिश व्यावहारिक थी; शैडो, फ़िलस्टाइल ऐल्फ़ा का गुणा है, इसलिए शैडो कभी भी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" अक्षर की ऊंचाई कहा जाता है). 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> एलिमेंट में अलग-अलग स्टाइल की कई स्टाइल हैं, जिन्हें साथ में जोड़ा जा सकता है. इन शैडो इफ़ेक्ट का इस्तेमाल किसी भी वेक्टर ऑब्जेक्ट पर किया जा सकता है, जैसे कि WebFonts से लेकर SVGs से इंपोर्ट किए गए मुश्किल शेप, जनरेटिव वेक्टर वगैरह!

कैनवस इफ़ेक्ट में टेक्स्ट की परछाई

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

लेख का यह सेक्शन लिखते समय, स्टीरियोस्कोपिक उदाहरण ने मुझे जानने के लिए प्रेरित किया. <canvas> और दो अलग-अलग नज़रिए से ली गई इमेज का इस्तेमाल करके 3D-फ़िल्म-स्क्रीन इफ़ेक्ट बनाना कितना मुश्किल होगा? ज़ाहिर है, यह बहुत मुश्किल नहीं है. नीचे दिया गया कर्नेल, दूसरी इमेज (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 में अपनी 3D मूवी बना सकते हैं. क्या कोई वॉलंटियर है?

3D ग्लास

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

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

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

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

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

globalCompositeOperation ब्राउज़र की हैंडलिंग होती है.

नियॉन-रेनबो जिटर इफ़ेक्ट

नीचे दिए गए डेमो में, हम ग्लोबलकंपोज़िटऑपरेशन (सोर्स-इन, लाइटर, और गहरा) का इस्तेमाल करके इफ़ेक्ट को एक साथ जोड़कर, फ़ोटोशॉप की तरह की नियॉन-रेनबो-की-अलग-अलग चमक बनाने वाले हैं. यह डेमो "<canvas> में Text-Shadows" डेमो की प्रोग्रेस को हाइलाइट करता है. इसके लिए, जिस रणनीति का इस्तेमाल करके शैडो को टेक्स्ट से अलग किया जाता है (पिछला सेक्शन देखें):

रेनबो जिटर
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();
};

ज़ेब्रा रिफ़्लेक्शन इफ़ेक्ट

Gebra Reflection इफ़ेक्ट कोWebDesignerWall के प्रॉडक्ट से प्रेरित किया गया है, ताकि पेज को सीएसएस के साथ ज़्यादा आकर्षक बनाया जा सके. यह आइडिया को थोड़ा और आगे ले जाता है, ताकि टेक्स्ट में एक "रिफ़्लेक्शन" बन सके. जैसे, iTunes में क्या दिखेगा. इस इफ़ेक्ट में,fillColor (सफ़ेद), createPattern (zebra.png), और लीनियर ग्रैडिएंट (शाइन) जैसे एलिमेंट शामिल होते हैं. इससे, हर वेक्टर ऑब्जेक्ट पर एक से ज़्यादा फ़िल टाइप लागू करने की सुविधा के बारे में पता चलता है:

ज़ीब्रा इफ़ेक्ट
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 की हाल ही में की गई पोस्ट में बताया गया था, आप घड़ी की दिशा में और उलटी दिशा में घुमाव करने के नियमों की खास प्रॉपर्टी का इस्तेमाल करके इनर-शैडो बना सकते हैं. ऐसा करने के लिए, कंटेनर का रेक्टैंगल बनाकर "इनर-शैडो" बनाएं. इसके बाद, घुमावदार नियमों का इस्तेमाल करके, एक कटआउट शेप बनाएं - ऐसा करके, शेप का इनर शैडो बनाएं.

इस उदाहरण में, इनर-शैडो और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 से:

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

...इस शेड किए गए उदाहरण में:

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

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

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

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

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

CSS3 के स्पेसिफ़िकेशन में, एचएसएल अब काम करने वाला फ़ॉर्मैट है. जहां HEX को कंप्यूटर के लिए डिज़ाइन किया गया था, वहीं एचएसएल को इस तरह डिज़ाइन किया गया है कि लोग आसानी से पढ़ सकें.

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

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

एचएसएल एक नया स्टैंडर्ड है. इसलिए, पुराने ब्राउज़र पर काम करना जारी रखा जा सकता है. ऐसा कलर-स्पेस कन्वर्ज़न से किया जा सकता है. नीचे दिया गया कोड एक एचएसएल ऑब्जेक्ट { H: 360, S: 100, L: 100} इस्तेमाल करता है और एक आरजीबी ऑब्जेक्ट { R: 255, G: 255, B: 255 } दिखाता है. इसके बाद, उन वैल्यू का इस्तेमाल अपनी आरजीबी या आरजीबी स्ट्रिंग बनाने के लिए करें. ज़्यादा जानकारी के लिए, एचएसएल पर 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 FPS (फ़्रेम प्रति सेकंड) पर फ़्रेम-दर की सीमा तय है. इसकी वजह यह है कि यह उस लेवल से काफ़ी ऊपर है जिसे इंसान देख सकते हैं (30 FPS (फ़्रेम प्रति सेकंड) तक ज़्यादातर इंसानों को ऐनिमेशन "फ़्लूइड" दिखता है).

लिखते समय, 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);
        };
})();

इसे थोड़ा और बेहतर करने पर, ज़्यादा महत्वाकांक्षी चीज़ों को 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> के आने वाले समय में इसे PhoneGap का इस्तेमाल करके iPhone/Android/Desktop एक्ज़ीक्यूटेबल में पोर्ट किया जा सकेगा या

टाइटेनियम.