جلوه‌های تایپوگرافی در بوم نقاشی

پس‌زمینه من

در سال ۲۰۰۶ که Firefox نسخه ۲٫۰ منتشر شد، با <canvas> آشنا شدم. مقاله‌ای در Ajaxian که ماتریس تبدیل را توصیف می‌کرد، الهام‌بخش من برای ایجاد اولین برنامه وب <canvas> من شد؛ Color Sphere (۲۰۰۷). این موضوع من را در دنیای رنگ‌ها و اصول گرافیک غوطه‌ور کرد و الهام‌بخش ایجاد Sketchpad (۲۰۰۷-۲۰۰۸) شد تا برنامه‌ای «بهتر از Paint» در مرورگر ایجاد کنم. این آزمایش‌ها درنهایت منجر به ایجاد استارت‌آپ Mugtug با دوست قدیمی‌ام چارلز پریچارد شد. ما درحال توسعه Darkroom در HTML5 <canvas> هستیم. Darkroom برنامه هم‌رسانی عکس غیرمخرب است که قدرت فیلترهای مبتنی بر پیکسل را با حروف‌چینی و طراحی مبتنی بر بردار ترکیب می‌کند.

مقدمه

گرافیک برنمای Canvas.

<canvas> به برنامه‌نویسان جاوا اسکریپت امکان می‌دهد تا کنترل کاملی بر رنگ‌ها، بردارها و پیکسل‌های روی صفحه‌نمایش خود داشته باشند - یعنی ساختار بصری مانیتور.

مثال‌های زیر به یک حوزه در <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

جلوه سه‌بعدی برجسته‌نمایی (برای اطلاعات بیشتر، به تصویر آناگلیف مراجعه کنید) نمونه‌ای از یک خط کد ساده است که کاربرد بسیار خوبی دارد. با خط CSS زیر، می‌توانیم توهم عمق را هنگام مشاهده با عینک‌های سه‌بعدی قرمز/فیروزه‌ای (از نوعی که در فیلم‌های سه‌بعدی به شما می‌دهند) ایجاد کنیم:

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. EM در <canvas> پشتیبانی نمی‌شود، بنابراین باید به PX تبدیل شود. We can find the conversion ratio for converting between PT, PC, EM, EX, PX and so-forth by creating an element with the same font-properties in DOM, and setting the width to the format to be measured; or instance, to capture the EM -> PX conversion, we 'd measure the DOM element with a "height: 1em", the resulting offsetHeight would be how many PX 's are in each 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>

جلوگیری از ضرب آلفا

در نمونه‌ای پیچیده‌تر، مانند جلوه Neon که در Line25 یافت می‌شود، ویژگی shadowBlur باید برای شبیه‌سازی صحیح جلوه استفاده شود. ازآنجایی‌که جلوه «نئون» به چندین سایه متکی است، با مشکلی مواجه می‌شویم؛ در <canvas> هر شیء برداری فقط می‌تواند یک سایه داشته باشد. بنابراین، برای ترسیم چندین سایه، باید چندین نسخه از نوشتار را روی خودش ترسیم کنید. این امر منجر به ضرب آلفا و درنهایت لبه‌های دندانه‌دار می‌شود.

گرافیک نئونی

I tried running ctx.fillStyle = "rgba(0,0,0,0)" or "transparent" to hide the text, while displaying the shadow… however, this attempt was futile; since the shadow is a multiplication of the fillStyle alpha, the shadow can never be more opaque than the 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> ما طیف وسیعی از سبک‌ها را دارند که می‌توانند با آن‌ها مرتبط شوند. از همین جلوه‌های سایه می‌توان در هر شیء برداری استفاده کرد، از WebFonts گرفته تا اشکال پیچیده واردشده از SVG، تا اشکال برداری زایا، و غیره!

سایه نوشتار در جلوه‌های بوم نقاشی

میان‌پرده (مماس بر پیکسل-پوشینگ)

In writing this section of the article, the Stereoscopic example made me curious. ایجاد جلوه صفحه‌نمایش فیلم سه‌بعدی بااستفاده از <canvas> و دو تصویر گرفته‌شده از چشم‌اندازهای کمی متفاوت چقدر سخت است؟ ظاهراً خیلی سخت نیست. هسته زیر کانال قرمز تصویر اول (داده) را با کانال فیروزه‌ای تصویر دوم (داده۲) ترکیب می‌کند:

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): ۱۲ GCO در <canvas> وجود دارد تیره‌تر، و روشن‌تر را می‌توان به‌عنوان حالت‌های ترکیب لایه درنظر گرفت؛ ۱۰ عملیات دیگر به‌عنوان ماسک‌های آلفا روی لایه‌ها اعمال می‌شوند (یک لایه پیکسل‌های لایه دیگر را حذف می‌کند). globalCompositeOperation «لایه‌ها» (یا در مورد ما، رشته‌های کد) را به هم گره می‌زند و آن‌ها را به روش‌های جدید و هیجان‌انگیزی ترکیب می‌کند:

گرافیک جلوه‌های زنجیره‌ای

نمودار globalCompositeOperation حالت‌های GCO را درحال کار نشان می‌دهد؛ این نمودار از بخش بزرگی از طیف رنگ و چندین سطح شفافیت آلفا استفاده می‌کند تا جزئیات آنچه را که باید انتظار داشت نشان دهد. توصیه می‌کنم برای توضیحات متنی، مرجع globalCompositeOperation Mozilla را بررسی کنید. برای تحقیق بیشتر، می‌توانید با نحوه عملکرد این عملیات در Compositing Digital Images اثر پورتر داف آشنا شوید.

حالت موردعلاقه من globalCompositeOperation="lighter" است. «روشن‌تر» پیکسل‌های الحاق‌شده را مشابه نحوه ترکیب نور ترکیب می‌کند؛ وقتی نور قرمز، سبز، و سفید در شدت کامل باشند، نور سفید می‌بینیم. این ویژگی هیجان‌انگیزی برای بازی کردن است، به‌ویژه زمانی که <canvas> روی یک globalAlpha پایین تنظیم شده باشد؛ این کار کنترل دقیق‌تر و لبه‌های نرم‌تری را فراهم می‌کند. Lighter کاربردهای زیادی داشته است، مورد دلخواه اخیر من سازنده پس‌زمینه میزکار HTML5 است که در http://weavesilk.com/ یافت می‌شود. یکی از نسخه‌های نمایشی من، Breathing Galaxies (JS1k)، نیز از حالت روشن‌تر استفاده می‌کند - با کشیدن الگوها از این دو مثال، شروع به دیدن تأثیری که این حالت ایجاد می‌کند می‌کنید.

مراجعه کنید

مدیریت مرورگر globalCompositeOperation.

جلوه لرزش رنگین‌کمان نئونی

در نسخه نمایشی زیر، با زنجیر کردن جلوه‌ها به یکدیگر بااستفاده از 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();
};

جلوه انعکاس گورخر

جلوه 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 می‌توانیم جلوه‌ها را به‌هم زنجیر کنیم و جلوه‌های پیچیده‌تری تولید کنیم (بااستفاده از ماسک‌گذاری و ترکیب). صفحه دراختیار شما است ;)

Spaceage—generative effects

در <canvas>، با رفتن از نویسه یونی‌کد 0x2708:

گرافیک یونی‌کد

…به این نمونه سایه‌دار:

نمونه سایه‌دار

…می‌تواند با چندین فراخوانی به ctx.strokeText() با lineWidth نازک (۰٫۲۵) حاصل شود، درحالی‌که x-offset و alpha به‌آرامی کاهش می‌یابد؛ این کار به عناصر برداری ما حس حرکت می‌دهد.

با نگاشت موقعیت XY عناصر به موج سینوسی/کسینوسی و چرخه رنگ‌ها بااستفاده از ویژگی HSL، می‌توانیم جلوه‌های جالب‌تری ایجاد کنیم، مانند این نمونه «خطر زیستی»:

جلوه چرخه HSL

‫HSL: فام، اشباع، روشنایی (۱۹۷۸)

HSL قالبی است که به‌تازگی در مشخصات CSS3 پشتیبانی می‌شود. درحالی‌که HEX برای رایانه‌ها طراحی شده است، HSL به‌گونه‌ای طراحی شده است که برای انسان قابل‌خواندن باشد.

برای نشان دادن سهولت HSL، برای چرخش در طیف رنگ، به‌سادگی «فام» را از ۳۶۰ افزایش می‌دهیم؛ فام به‌صورت استوانه‌ای به طیف نگاشت می‌شود. روشنایی کنترل می‌کند که رنگ چقدر تیره/روشن باشد؛ ‫۰٪ نشان‌دهنده پیکسل سیاه است، درحالی‌که ۱۰۰٪ نشان‌دهنده پیکسل سفید است. اشباع رنگ میزان روشنایی یا زنده بودن رنگ را کنترل می‌کند؛ رنگ‌های خاکستری با اشباع رنگ ۰٪ ایجاد می‌شوند و رنگ‌های زنده بااستفاده از مقدار ۱۰۰٪ ایجاد می‌شوند.

گرافیک HSL

ازآنجایی‌که 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

در گذشته، برای ایجاد پویانمایی در جاوا اسکریپت، دو انتخاب وجود داشت؛ setTimeout، و setInterval.

window.requestAnimationFrame، استاندارد جدیدی است که جایگزین هر دو می‌شود؛ با اجازه دادن به مرورگر برای تنظیم پویانمایی‌ها براساس منابع دردسترس، در مصرف برق جهان (و چند ضربان قلب رایانه‌تان) صرفه‌جویی می‌کند. برخی‌از ویژگی‌های مهم عبارت‌اند از:

  • وقتی کاربر از قاب خارج می‌شود، پویانمایی می‌تواند کند شود یا کاملاً متوقف شود تا از استفاده از منابع غیرضروری جلوگیری شود.
  • نرخ فریم به ۶۰ فریم در ثانیه محدود شده است. دلیل این امر این است که این مقدار بسیار بالاتر از سطحی است که انسان‌ها می‌توانند متوجه شوند (اکثر انسان‌ها با ۳۰ قاب در ثانیه پویانمایی را «روان» می‌بینند).

در زمان نوشتن، پیشوندهای خاص فروشنده برای استفاده از requestAnimationFrame لازم است. Paul Irish یک لایه shim ایجاد کرد که از پشتیبانی بین فروشنده‌ای برخوردار است، در 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> وجود ندارد که بتوان آن را به فایل‌های اجرایی iPhone/Android/رایانه بااستفاده از PhoneGap منتقل کرد، یا

تیتانیوم.