אפקטים טיפוגרפיים בבד ציור

מיכאל דיל
מיכאל דיל

הרקע שלי

<canvas> גילה את המודעות שלי בשנת 2006, כש-Firefox v2.0 הושק. מאמר על Ajaxian, שמתאר את מטריצת הטרנספורמציה, נתן לי השראה ליצור את אפליקציית האינטרנט הראשונה שלי ב-<canvas> ; Colors (2007). שסחף אותי בעולם הצבעים והפרימיטיבים הגרפיים. השראה שיצרה את Sketchpad (2007-2008) בניסיון ליצור אפליקציה "טוב יותר מציור" בדפדפן. הניסויים האלה הובילו בסופו של דבר ליצירת חברת הסטארט-אפ Mugtug עם חבר שלי הוותיק צ'רלס פריצ'רד. אנחנו מפתחים את Darkroom ב-HTML5 <canvas>. Darkroom היא אפליקציה לא הרסנית לשיתוף תמונות, והיא משלבת את הכוח של פילטרים שמבוססים על פיקסלים עם טיפוגרפיה ושרטוט שמבוססים על וקטורים.

מבוא

גרפיקה של מודעת הבאנר על קנבס.

<canvas> מעניק למתכנתים של JavaScript שליטה מלאה בצבעים, ווקטורים והפיקסלים במסכים שלהם – המבנה החזותי של המסך.

הדוגמאות הבאות עוסקות בתחום אחד ב<canvas> שלא קיבל הרבה תשומת לב, יצירת אפקטים של טקסט. מגוון האפקטים של הטקסט שאפשר ליצור ב-<canvas> הוא עצום שאפשר לדמיין. ההדגמות האלה מכסות חלק משנה של מה שאפשר לעשות. על אף שהנושא של "טקסט" במאמר הזה עוסק, אפשר ליישם את השיטות על כל אובייקט וקטורי, ליצור רכיבים חזותיים מרגשים במשחקים ובאפליקציות אחרות:

צלליות טקסט ב<canvas>.
אפקטים של טקסט כמו CSS ב-<canvas> יצירת מסכות של גזירה, חיפוש מדדים ב-<canvas> ושימוש במאפיין הצללית.
קשת נאון, השתקפות של זברה – אפקטים של שרשרת.
אפקטי טקסט דמויי Photoshop ב-<canvas> דוגמאות לשימוש ב-globalCompositeOperation, createMDMGradient, createPattern.
צלליות פנימיות וחיצוניות ב-<canvas>
חשיפת תכונה ידועה קטנה; שימוש בלולאת כיוון השעון לעומת לולאות נגד כיוון השעון כדי ליצור את ההיפוך של הטלת צללית (הצל הפנימי).
חלל – אפקט גנרטיבי.
אפקט טקסט מבוסס גנרטיבי ב-<canvas> באמצעות hsl() של מחזור צבעים ו-window.requestAnimationFrame ליצירת תחושת תנועה.

צלליות טקסט בלוח הציור

אחת התוספות האהובות עליי למפרטים של CSS3 (יחד עם רדיוס גבול, שינויים הדרגתיים באינטרנט ועוד) היא היכולת ליצור צלליות. חשוב לשים לב להבדלים בין הצלליות ב-CSS לבין <canvas>, באופן ספציפי:

ב-CSS יש שתי שיטות: box-shadow לאלמנטים של תיבה, כמו div, span וכו', ו-text-shadow לתוכן טקסט.

ל- <canvas> יש סוג אחד של צל; הוא משמש לכל אובייקטי הווקטור; {/7}.moveTo, hl.com.lineTo, {/7}.bezierCurveTo, {/7}.quadradicCurveTo, answer.arc, {/7}.rect, answer.fillText, {/7}.StrikeText, וכן הלאה. כדי ליצור צל ב-<canvas>, מקישים על ארבעת המאפיינים הבאים:

ctx.shadowColor = "red" // מחרוזת
צבע הצללית; RGB, RGBA, HSL, HEX וקלט אחר חוקי.
ctx.shadowOffsetX = 0; // integer
מרחק אופקי של הצללית ביחס לטקסט.
ctx.shadowOffsetY = 0; // מספר שלם
מרחק אנכי של הצללית ביחס לטקסט.
ctx.shadowBlur = 10; // מספר שלם
טשטוש של הצללית, ככל שהערך גדול יותר, כך הטשטוש גדול יותר.

כדי להתחיל, נראה איך <canvas> יכול לדמות אפקטים של CSS. החיפוש אחר "css text-shadow" בתמונות Google הוביל לכמה גרסאות הדגמה נהדרת שבעזרתן יכולנו לחקות; Line25, סטריאוסקופי ו-Shadow 3D.

גרפיקה תלת-ממדית של CSS

האפקט הסטריאוסקופי תלת-ממדי (למידע נוסף, ראו תמונת אנגליף) הוא דוגמה לשורה פשוטה של קוד, שימושית מאוד. בעזרת שורת ה-CSS הבאה, אפשר ליצור אשליה של עומק כשצופים במשקפי תלת-ממד בצבעי אדום/ציאן (מה שהם מספקים בסרטים בתלת-ממד):

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

יש שני דברים שכדאי לשים לב כשממירים את המחרוזת הזו ל-<canvas>:

  1. אין טשטוש צל (הערך השלישי), ולכן אין סיבה להפעיל צל בפועל, כי המילוי של הטקסט ייצור את אותן התוצאות:
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. כדי למצוא את יחס ההמרה בין PT, PC, EM, EX, PX ותחילה, ניתן ליצור רכיב עם אותם מאפייני גופן ב-DOM, ולהגדיר את הרוחב לפורמט למדידה. או מכונה, כדי לתעד את המרת EM -> PX, נמדוד את רכיב ה-DOM עם היסט DOM: 1em.
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, צריך להשתמש במאפיין הצללית של טשטוש הרקע כדי לדמות את האפקט כמו שצריך. אפקט ה-Neon מסתמך על כמה צלליות, ולכן אנחנו נתקלים בבעיה. ב-<canvas> לכל אובייקט וקטורי יכול להיות רק צל אחד. לכן, כדי לצייר כמה צלליות, צריך לשרטט מספר גרסאות של הטקסט על גבי עצמו. התוצאה היא כפל של אלפא, ובסופו של דבר קצוות משוננים.

גרפיקת ניאון

ניסיתי להריץ ctx.fillStyle = "rgba(0,0,0,0)" או "transparent" כדי להסתיר את הטקסט תוך הצגת הצל... עם זאת, הניסיון הזה לא היה מוצלח. מאחר שהצל הוא הכפלה של האלפא fullStyle, הצללית לעולם לא תהיה אטומה יותר מה-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 לא קיים.

למרבה המזל, בזכות הפריצה ל-CSS (ראו מדדים טיפוגרפיים לדרכים נוספות לתיקון הטמעות ישנות של <canvas> באמצעות מדידות CSS), אנחנו יכולים לזהות את גובה הטקסט על ידי מדידת offsetHeight של <span> עם אותם מאפייני גופן:

var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;

מכאן נוכל ליצור מלבן שישמש כנתיב חיתוך. תוחם את ה "צל" תוך הסרת צורת הדמה.

ctx.rect(0, 0, width, emHeight);
ctx.clip();

אנו מחברים את כל המידע ומבצעים אופטימיזציה תוך כדי - אם צל אינו מטושטש, ניתן להשתמש ב-fillText לאותו אפקט ולחסוך לנו את הגדרת המסכה הגורמת:

var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
var shadow = shadows[n];
var totalWidth = width + shadow.blur * 2;
ctx.save();
ctx.beginPath();
ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
ctx.clip();
if (shadow.blur) { // just run shadow (clip text)
    ctx.shadowColor = shadow.color;
    ctx.shadowOffsetX = shadow.x + totalWidth;
    ctx.shadowOffsetY = shadow.y;
    ctx.shadowBlur = shadow.blur;
    ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
} else { // just run pseudo-shadow
    ctx.fillStyle = shadow.color;
    ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
}
ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
ctx.fillStyle = style.color;
ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);

מכיוון שלא תרצו להזין את כל פקודות <canvas> האלה באופן ידני, כללתי במקור ההדגמה כלי פשוט של צללית טקסט, כך שניתן להזין בו פקודות CSS וליצור פקודות <canvas>. עכשיו, לרכיבי <canvas> יש מגוון רחב של סגנונות שאפשר להשתלב בהם. ניתן להשתמש באותם אפקטים של צלליות בכל אובייקט וקטורי, מ-WebFonts ועד לצורות מורכבות שיובאו מ-SVG, לצורות וקטור גנרטיביות וכו'.

צללית טקסט באפקטים של קנבס

מרווח (טנגנס בדחיפת פיקסלים)

בכתבה את החלק הזה של המאמר, הדוגמה הסטריאוסקופית עוררה אותי סקרנות. כמה קשה ליצור אפקט של סרט בתלת-ממד באמצעות <canvas> ושתי תמונות שצולמו בנקודות מבט שונות? נראה שזה לא קשה מדי. הליבה הבאה משלבת את הערוץ האדום של התמונה הראשונה (נתונים) עם ערוץ הציאן של התמונה השנייה (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> כהה יותר, ואפשר להתייחס למצב בהיר יותר כמצבי מיזוג שכבות. 10 הפעולות האחרות מוחלות על השכבות כמסכות אלפא (שכבה אחת מסירה את הפיקסלים של השכבה השנייה). ה-globalCompositeOperation מחבר בין "שכבות" (או במקרה שלנו, מחרוזות קוד) ומשלב אותן בדרכים חדשות ומלהיבות:

גרפיקת אפקטים של שרשור

בתרשים GlobalCompositeOperation מוצגים מצבי GCO בפעולה. התרשים משתמש בחלק גדול ממגוון הצבעים ובכמה רמות של שקיפות ברמת האלפא כדי לראות בצורה מפורטת למה לצפות. מומלץ לקרוא את חומר העזר בנושא GlobalCompositeOperation של Mozilla לגבי תיאורים בטקסט. למחקר נוסף, ניתן לקרוא איך הפעולה עובדת במאמר Compositing Digital Images של פורטר Duff.

המצב המועדף עליי הוא globalCompositeOperation="lighter". 'בהיר' יותר משלב את הפיקסלים המצורפים בדומה לאופן שבו האור מתערבב. כשאור אדום, ירוק ולבן נמצא בעוצמה מלאה, אנחנו רואים אור לבן. זו תכונה מרתקת שאפשר לשחק איתה, במיוחד כאשר <canvas> מוגדר לרמת אלפא גלובלית נמוכה. היא מאפשרת שליטה עדינה יותר ושוליים חלקים יותר. קליל כבר עשה שימושים רבים, והמועדף האחרון שלי היה יצירת רקעים ב-HTML5 לשולחן העבודה שנמצא בכתובת http://weavesilk.com/. אחת מההדגמות שלי, גלקסיות נשימה (JS1k), משתמשת גם במצב בהיר יותר – משרטטים דפוסים משתי הדוגמאות האלה, ורואים איך המצב הזה משפיע.

טיפול בדפדפןglobalCompositeOperation.

אפקט של רעידות בצבעי ניאון בצבעי הקשת

בהדגמה הבאה ניתן ליצור אפקט של זוהר בצבעי הקשת Photoshop עם קווי מתאר רעידים, על ידי מחרוזת האפקטים באמצעות ה-globalCompositeOperation (source-in, בהיר יותר וכהה יותר). הדגמה זו היא התקדמות של ההדגמה 'צלליות טקסט ב-<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) ו-לינאריGradient (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, תוכלו ליצור צלליות פנימיות באמצעות המאפיינים הייחודיים של כללי קיפולים בכיוון השעון לעומת כללי עיקול נגד כיוון השעון. לשם כך יוצרים 'צל פנימי' על ידי שרטוט מלבן מיכל, ולאחר מכן משרטטים צורת גזרה הפוכה של הצורה ההפוך.

הדוגמה הבאה מאפשרת לעצב את הצללית הפנימית ואת ה-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>, מעבר מתו ה-Unicode 0x2708:

Unicode gfaphic

...לדוגמה המוצללת הזו:

דוגמה מוצללת

...ניתן להשיג באמצעות קריאות מרובות ל-ctx.strokeText() עם linewidth דק (0.25), תוך הפחתה איטית של ההיסט ה-x והאלפא, וכך לתת לאלמנטים הווקטוריים שלנו תחושת תנועה.

מיפוי המיקום של הרכיבים XY לגל סינוס/קוסינוס ומעבר בין צבעים באמצעות מאפיין HSL מאפשר לנו ליצור אפקטים מעניינים יותר, כמו הדוגמה הבאה של "biohazard":

אפקט רכיבה על אופניים של HSL

HSL: גוון, רוויה, בהירות (1978)

HSL הוא פורמט נתמך חדש במפרטים של CSS3. כאשר HEX תוכנן עבור מחשבים, HSL תוכנן להיות קריא לאנשים.

נמחיש את הקלות של HSL. כדי לעבור בין רצף הצבעים צריך פשוט להגדיל את ה"גוון" מ-360; הגוון ממופה לספקטרום בצורה גלילית. בהירות קובעת עד כמה הצבע כהה או בהיר; 0% מציין פיקסל שחור ו-100% מציין פיקסל לבן. הרוויה קובעת עד כמה צבע בהיר או ססגוני. צבעים אפורים נוצרים עם רוויה של 0%, וצבעים עזים נוצרים עם ערך של 100%.

פריט גרפי 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

בעבר, כדי ליצור אנימציות ב-JavaScript, היו שתי אפשרויות: setTimeout ו-setInterval.

window.requestAnimationFrame, הוא התקן החדש שמחליף את שניהם; חיסכון בחשמל בעולם (וגם במחשב בכמה פעימות לב) על ידי מתן אפשרות לדפדפן לווסת אנימציות בהתאם למשאבים הזמינים. כמה מהתכונות החשובות כוללות:

  • כשמשתמש קיים את הפריים, האנימציה עלולה להאט או להפסיק לגמרי, כדי למנוע שימוש במשאבים לא נחוצים.
  • יש מכסת הגבלה לקצב פריימים של 60FPS. הסיבה לכך היא שהאנימציה הזו גבוהה בהרבה מהרמה שבני אדם יכולים לראות (רוב האנשים, בהפרש של 30FPS, רואים שהאנימציה "נוזלת").

בזמן הכתיבה, יש לספק קידומות ספציפיות כדי להשתמש ב-requestAnimationFrame. פול אירלנד יצר שכבת 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);
        };
})();

אם נרחיב קצת יותר את הנושא, יכול להיות שהשאפתנות יותר תקושר בין כל הנתונים האלה בעזרת מילוי של Poly כמו requestAnimationFrame.js (יש עוד כמה תכונות שצריך לעבוד עליהן) שיתמכו בדפדפנים ישנים יותר בזמן המעבר לתקן החדש.

(function animate() {
var i = 50;
while(i--) {
    if (n > endpos) return;

    n += definition;
    ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
    if (doColorCycle) {
        hue = n + color;
        ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
    }
    var x = cos(n / cosdiv) * n * cosmult; // cosine
    var y = sin(n / sindiv) * n * sinmult; // sin
    ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
}
timeout = window.requestAnimationFrame(animate, 0);
})();
גרפיקה של טשטוש הערות
פריט גרפי מונפש
פריט גרפי של מטריצה

קוד מקור

עם תמיכה מסביבת הספק של הדפדפנים, אין ספק לגבי העתיד של <canvas>, ואפשר להעביר אותו לקובצי הפעלה ב-iPhone/Android/Desktop באמצעות PhoneGap, או

טיטניום.