הרקע שלי
<canvas>
נכנס לתודעה שלי בשנת 2006, כשגרסה 2.0 של Firefox שוחררה. מאמר ב-Ajaxian שתיאר את מטריצת הטרנספורמציה השפיע עלי ליצור את אפליקציית האינטרנט הראשונה שלי ב-<canvas>
, Color Sphere (2007). כתוצאה מכך, התחלתי להתעניין בעולם הצבעים והפרימיטיבים הגרפיים, והשראה ליצור את Sketchpad (2007-2008) כדי ליצור אפליקציה "טובה יותר מ-Paint" בדפדפן.
הניסויים האלה הובילו בסופו של דבר ליצירת הסטארט-אפ Mugtug עם חברי הטוב Charles Pritchard. אנחנו מפתחים את Darkroom ב-HTML5 <canvas>
. Darkroom היא אפליקציית שיתוף תמונות ללא השחתה, שמשלבת את עוצמת הפילטרים שמבוססים על פיקסלים עם טיפוגרפיה ורישום מבוססי-וקטור.
מבוא

<canvas>
מעניק למתכנתים של Javascript שליטה מלאה על הצבעים, הווקטורים והפיקסלים במסכים שלהם – המראה החזותי של המסך.
הדוגמאות הבאות עוסקות בתחום אחד ב-<canvas>
שלא קיבל הרבה תשומת לב: יצירת אפקטים של טקסט. מגוון האפקטים לטקסט שאפשר ליצור ב-<canvas>
הוא עצום – הדגמות האלה מכסות רק חלק קטן ממה שאפשר לעשות. במאמר הזה אנחנו עוסקים ב'טקסט', אבל אפשר להחיל את השיטות האלה על כל אובייקט וקטורי, כדי ליצור רכיבים חזותיים מעניינים במשחקים ובאפליקציות אחרות:
- צללי טקסט ב-
<canvas>
. - אפקטים של טקסט ב-
<canvas>
שנראים כמו CSS, יצירת מסכות חיתוך, חיפוש מדדים ב-<canvas>
ושימוש במאפיין 'צללית'. - קשירת אפקטים: קשת ניאון, השתקפות של זברה.
- אפקטים של טקסט בסגנון Photoshop ב
<canvas>
דוגמאות לשימוש ב-globalCompositeOperation, createLinearGradient, createPattern. - צללים פנימיים וחיצוניים ב-
<canvas>
- הצגת תכונה לא מוכרת: שימוש בכיוון השעון לעומת נגד כיוון השעון כדי ליצור את ההפך של צללית (inner-shadow).
- Spaceage – אפקט גנרטיבי. אפקט טקסט
- גנרטיבי ב-
<canvas>
באמצעות מחזור צבעים של hsl() ו-window.requestAnimationFrame
כדי ליצור תחושה של תנועה.
צלליות טקסט ב-Canvas
אחת מהתוספות האהובות עלי למפרטי 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.

אפקט ה-3D הסטראוסקופי (מידע נוסף זמין במאמר תמונה אנאלוגית) הוא דוגמה לשורת קוד פשוטה שאפשר להשתמש בה בצורה יעילה. באמצעות שורת ה-CSS הבאה, אפשר ליצור אשליה של עומק כשצופים באמצעות משקפי 3D אדומים/כחולים (הסוג שמקבלים בסרטים תלת-ממדיים):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
יש שני דברים שחשוב לשים לב אליהם כשממירים את המחרוזת הזו ל-<canvas>
:
- אין ערך של shadow-blur (הערך השלישי), ולכן אין סיבה להריץ בפועל את הפונקציה shadow, כי הפונקציה fillText תיצור את אותן תוצאות:
var text = "Hello world!"
ctx.fillStyle = "#000"
ctx.fillText(text, -7, 0);
ctx.fillStyle = "red"
ctx.fillText(text, 0, 0);
ctx.fillStyle = "cyan"
ctx.fillText(text, 7, 0);</pre>
- לא ניתן להשתמש ב-EM ב-
<canvas>
, ולכן צריך להמיר אותם ל-PX. כדי למצוא את יחס ההמרה בין PT, PC, EM, EX, PX וכו', יוצרים רכיב עם אותם מאפייני גופן ב-DOM ומגדירים את הרוחב לפי הפורמט שרוצים למדוד. לדוגמה, כדי לתעד את ההמרה מ-EM ל-PX, מודדים את רכיב ה-DOM עם 'height: 1em'. הערך שיתקבל ב-offsetHeight יהיה מספר הפיקסלים בכל EM.
var font = "20px sans-serif"
var d = document.createElement("span");
d.style.cssText = "font: " + font + " height: 1em; display: block"
// the value to multiply PX 's by to convert to EM 's
var EM2PX = 1 / d.offsetHeight;</pre>
מניעת כפל אלפא
בדוגמה מורכבת יותר, כמו אפקט הניאון שמופיע בשורה 25, צריך להשתמש במאפיין shadowBlur כדי לדמות את האפקט בצורה נכונה. מכיוון שאפקט הניאון מסתמך על כמה צללים, נתקלים בבעיה: ב-<canvas>
לכל אובייקט וקטור יכול להיות רק צל אחד. לכן, כדי לצייר כמה צללים, צריך לצייר כמה גרסאות של הטקסט מעל עצמו.
התוצאה היא כפל אלפא, ובסופו של דבר קצוות משוננים.

ניסיתי להריץ את ctx.fillStyle = "rgba(0,0,0,0)"
או "transparent"
כדי להסתיר את הטקסט ולהציג את הצללית… אבל הניסיון הזה היה לשווא. מכיוון שהצללית היא מכפלה של אלפא של fillStyle, הצללית לעולם לא יכולה להיות אטומה יותר מ-fillStyle.
למרבה המזל, יש דרך לעקוף את הבעיה הזו. אפשר לצייר את ההזזה של הצללים מהטקסט, כך שהם יישארו מופרדים (כדי שהם לא 'יחפפו') וכך להסתיר את הטקסט בצד המסך:
var text = "Hello world!"
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = "top"
ctx.shadowColor = "#000"
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);
חיתוך סביב קטע טקסט
כדי לפתור את הבעיה הזו, אפשר להוסיף נתיב חיתוך כדי למנוע את הציור של fillText מלכתחילה (תוך שמירה על האפשרות לצייר את הצל).
כדי ליצור נתיב חיתוך שמקיף את הטקסט, אנחנו צריכים לדעת את גובה הטקסט (נקרא 'גובה em', ובעבר היה זה הגובה של האות 'M' במכונת דפוס) ואת רוחב הטקסט.
אפשר לקבל את הרוחב באמצעות ctx.measureText().width
, אבל הערך ctx.measureText().height
לא קיים.
למרבה המזל, באמצעות טריקים ב-CSS (במדדים הטיפוגרפיים מפורטות דרכים נוספות לתקן הטמעות ישנות יותר של <canvas>
באמצעות מדידות CSS), אפשר למצוא את גובה הטקסט על ידי מדידת offsetHeight
של <span>
עם אותם מאפייני גופן:
var d = document.createElement("span");
d.font = "20px arial"
d.textContent = "Hello world!"
var emHeight = d.offsetHeight;
לאחר מכן, אפשר ליצור מלבן שישמש כנתיב חיתוך, שיכלול את ה'צללית' ויגרום להסרת הצורה המדומה.
ctx.rect(0, 0, width, emHeight);
ctx.clip();
משלבים את כל החלקים ומבצעים אופטימיזציה תוך כדי תנועה – אם לצללית אין טשטוש, אפשר להשתמש ב-fillText כדי להשיג את אותו אפקט, וכך לחסוך את ההגדרה של מסכת החיתוך:
var width = ctx.measureText(text).width;
var style = shadowStyles[text];
// add a background to the current effect
ctx.fillStyle = style.background;
ctx.fillRect(0, offsetY, ctx.canvas.width, textHeight - 1)
// parse text-shadows from css
var shadows = parseShadow(style.shadow);
// loop through the shadow collection
var n = shadows.length; while(n--) {
var shadow = shadows[n];
var totalWidth = width + shadow.blur * 2;
ctx.save();
ctx.beginPath();
ctx.rect(offsetX - shadow.blur, offsetY, offsetX + totalWidth, textHeight);
ctx.clip();
if (shadow.blur) { // just run shadow (clip text)
ctx.shadowColor = shadow.color;
ctx.shadowOffsetX = shadow.x + totalWidth;
ctx.shadowOffsetY = shadow.y;
ctx.shadowBlur = shadow.blur;
ctx.fillText(text, -totalWidth + offsetX, offsetY + metrics.top);
} else { // just run pseudo-shadow
ctx.fillStyle = shadow.color;
ctx.fillText(text, offsetX + (shadow.x||0), offsetY - (shadow.y||0) + metrics.top);
}
ctx.restore();
}
// drawing the text in the foreground
if (style.color) {
ctx.fillStyle = style.color;
ctx.fillText(text, offsetX, offsetY + metrics.top);
}
// jump to next em-line
ctx.translate(0, textHeight);
לא תרצו להזין את כל פקודות ה-<canvas>
האלה באופן ידני, לכן הוספתי למקור הדמו מפַתח פשוט של text-shadow. כך תוכלו להזין לו פקודות CSS ולגרום לו ליצור פקודות <canvas>
.
עכשיו, לאלמנטים שלנו מסוג <canvas>
יש מגוון רחב של סגנונות שאפשר לקשר אליהם.
אפשר להשתמש באותם אפקטים של צללים בכל אובייקט וקטורי, החל מ-WebFonts ועד לצורות מורכבות שיובאו מקובצי SVG, לצורות וקטוריות גנרטיביות וכו'.

הפסקה (הסבר על דחיפת פיקסלים)
כשכתבתי את הקטע הזה במאמר, הדוגמה לתמונה תלת-ממדית עוררה בי סקרנות. כמה קשה יהיה ליצור אפקט של מסך קולנוע תלת-ממדי באמצעות <canvas>
ושתי תמונות שצולמו מנקודות מבט שונות במקצת? נראה שזה לא קשה מדי. הליבה הבאה משלבת את הערוץ האדום של התמונה הראשונה (data) עם הערוץ הכחול-ירוק של התמונה השנייה (data2):
data[i] = data[i] * 255 / 0xFF;
data[i+1] = 255 * data2[i+1] / 0xFF;
data[i+2] = 255 * data2[i+2] / 0xFF;
עכשיו, כל מה שצריך הוא להדביק שני מכשירי iPhone לקדמת הראש באמצעות נייר דבק, ללחוץ על 'צילום סרטון' בו-זמנית, ואנחנו יכולים ליצור סרטים משולבי 3D ב-HTML5. יש מתנדבים?

קשירת אפקטים – קשת ניאון, השתקפות של זברה
שרשור של כמה אפקטים ב-<canvas>
יכול להיות פשוט, אבל נדרש ידע בסיסי ב-globalCompositeOperation (GCO). כדי להשוות את הפעולות ל-GIMP (או ל-Photoshop): יש 12 פעולות GCO ב-<canvas>
darker, ואפשר להתייחס ל-lighter כאל שיטות למיזוג שכבות. 10 הפעולות האחרות חלות על השכבות כמסכות אלפא (שכבה אחת מסירה את הפיקסלים של השכבה השנייה). הפונקציה globalCompositeOperation מקשרת בין "שכבות" (או במקרה שלנו, מחרוזות קוד) ומחברת אותן בדרכים חדשות ומעניינות:

בתרשים globalCompositeOperation מוצגים מצבי GCO. התרשים הזה משתמש בחלק גדול מתחום הצבעים ובמספר רמות של שקיפות אלפא כדי להראות בפירוט את התוצאות הצפויות. מומלץ לעיין בחומר העזר של Mozilla בנושא globalCompositeOperation כדי לקבל תיאורים טקסטואליים. למידע נוסף, אפשר לקרוא את המאמר של Porter Duff בנושא Compositing Digital Images.
המצב האהוב עליי הוא globalCompositeOperation="lighter". התכונה Lighter משלבת את הפיקסלים שנוספו באופן דומה לאופן שבו אור מעורבב. כשהאור האדום, הירוק והלבן בעוצמה מלאה, אנחנו רואים אור לבן. זו תכונה מעניינת שאפשר להתנסות בה, במיוחד כשה-<canvas>
מוגדר ל-globalAlpha נמוך. כך אפשר לשלוט בצורה מדויקת יותר ולקבל קצוות חלקים יותר. יש הרבה שימושים ל-Lighter, אבל אחד מהם שהכי אהבתי לאחרונה הוא יצירת רקע למחשב בפורמט HTML5 בכתובת http://weavesilk.com/.
גם באחד מהדמואים שלי, Breathing Galaxies (JS1k), נעשה שימוש במצב הקל יותר. כשמשווים בין שתי הדוגמאות האלה, מתחילים להבין איזה אפקט מקבלים במצב הזה.
טיפול בדפדפן של globalCompositeOperation.
אפקט של ניאון-קשת עם רעידות
בדמו הבא נראה איך ליצור אפקט דמוי Photoshop של זוהר ניאון בצבעי הקשת עם קו מתאר רעוע, על ידי שילוב אפקטים באמצעות globalCompositeOperation (source-in, lighter ו-darker).
ההדגמה הזו היא המשך של ההדגמה 'צללי טקסט ב-<canvas>
', ומתבססת על אותה אסטרטגיה להפרדת הצל מהטקסט (ראו הקטע הקודם):

function neonLightEffect() {
var text = "alert('"+String.fromCharCode(0x2665)+"')";
var font = "120px Futura, Helvetica, sans-serif";
var jitter = 25; // the distance of the maximum jitter
var offsetX = 30;
var offsetY = 70;
var blur = getBlurValue(100);
// save state
ctx.save();
ctx.font = font;
// calculate width + height of text-block
var metrics = getMetrics(text, font);
// create clipping mask around text-effect
ctx.rect(offsetX - blur/2, offsetY - blur/2,
offsetX + metrics.width + blur, metrics.height + blur);
ctx.clip();
// create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
ctx.save();
ctx.fillStyle = "#fff";
ctx.shadowColor = "rgba(0,0,0,1)";
ctx.shadowOffsetX = metrics.width + blur;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
ctx.restore();
// create the rainbow linear-gradient
var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
// change composite so source is applied within the shadow-blur
ctx.globalCompositeOperation = "source-atop";
// apply gradient to shadow-blur
ctx.fillStyle = gradient;
ctx.fillRect(offsetX - jitter/2, offsetY,
metrics.width + offsetX, metrics.height + offsetY);
// change composite to mix as light
ctx.globalCompositeOperation = "lighter";
// multiply the layer
ctx.globalAlpha = 0.7
ctx.drawImage(ctx.canvas, 0, 0);
ctx.drawImage(ctx.canvas, 0, 0);
ctx.globalAlpha = 1
// draw white-text ontop of glow
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.fillText(text, offsetX, offsetY + metrics.top);
// created jittered stroke
ctx.lineWidth = 0.80;
ctx.strokeStyle = "rgba(255,255,255,0.25)";
var i = 10; while(i--) {
var left = jitter / 2 - Math.random() * jitter;
var top = jitter / 2 - Math.random() * jitter;
ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
}
ctx.strokeStyle = "rgba(0,0,0,0.20)";
ctx.strokeText(text, offsetX, offsetY + metrics.top);
ctx.restore();
};
אפקט השתקפות של זברה
ההשראה לאפקט Zebra Reflection הגיעה מהמקור המצוין של WebDesignerWall בנושא שדרוג הדף באמצעות CSS. כאן אנחנו ממשיכים את הרעיון ומציגים 'השתקפות' של הטקסט – כמו שאפשר לראות ב-iTunes. האפקט משלב את fillColor (לבן), createPattern (zebra.png) ו-linearGradient (shine). הדוגמה הזו ממחישה את היכולת להחיל כמה סוגים של מילוי על כל אובייקט וקטור:

function sleekZebraEffect() {
// inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
var text = "Sleek Zebra...";
var font = "100px Futura, Helvetica, sans-serif";
// save state
ctx.save();
ctx.font = font;
// getMetrics calculates:
// width + height of text-block
// top + middle + bottom baseline
var metrics = getMetrics(text, font);
var offsetRefectionY = -20;
var offsetY = 70;
var offsetX = 60;
// throwing a linear-gradient in to shine up the text
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.1, '#000');
gradient.addColorStop(0.35, '#fff');
gradient.addColorStop(0.65, '#fff');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient
ctx.fillText(text, offsetX, offsetY + metrics.top);
// draw reflected text
ctx.save();
ctx.globalCompositeOperation = "source-over";
ctx.translate(0, metrics.height + offsetRefectionY)
ctx.scale(1, -1);
ctx.font = font;
ctx.fillStyle = "#fff";
ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
ctx.scale(1, -1);
// cut the gradient out of the reflected text
ctx.globalCompositeOperation = "destination-out";
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient;
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);
// restore back to original transform state
ctx.restore();
// using source-atop to allow the transparent .png to show through to the gradient
ctx.globalCompositeOperation = "source-atop";
// creating pattern from <image> sourced.
ctx.fillStyle = ctx.createPattern(image, 'repeat');
// fill the height of two em-boxes, to encompass both normal and reflected state
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
ctx.restore();
};
צללים פנימיים/חיצוניים ב-Canvas
המפרט של <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>
, עוברים מתווי ה-Unicode 0x2708:

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

…אפשר להשיג באמצעות מספר קריאות ל-ctx.strokeText()
עם lineWidth דק (0.25), תוך הפחתה איטית של x-offset ו-alpha, כדי לתת לאלמנטי הווקטור תחושה של תנועה.
אם נתבצע מיפוי של המיקום XY של הרכיבים לגל סינוס/קוסינוס, ונעבור בין הצבעים באמצעות המאפיין HSL, נוכל ליצור אפקטים מעניינים יותר, כמו הדוגמה הבאה של 'סכנה ביולוגית':

HSL: גוון, רוויה, בהירות (1978)
HSL הוא פורמט חדש שנתמך במפרטי CSS3. בעוד ש-HEX תוכנן למחשבים, הפורמט HSL תוכנן כך שגם בני אדם יוכלו לקרוא אותו.
המחשה של הקלות של HSL: כדי לעבור על ספקטרום הצבעים, פשוט מוסיפים את הערך של 'הגוון' מ-360. הגוון ממופה לספקטרום בצורה צילינדרית. הצבע הבהיר קובע את מידת החושך או הבהירות של הצבע. הערך 0% מציין פיקסל שחור, ואילו הערך 100% מציין פיקסל לבן. הצבעה קובעת את הבהירות או את העוצמה של הצבע. צבעים אפורים נוצרים עם רמת צבעה של 0%, וצבעים עזים נוצרים עם ערך של 100%.

מאחר ש-HSL הוא תקן חדש, יכול להיות שתרצו להמשיך לתמוך בדפדפנים ישנים יותר. אפשר לעשות זאת באמצעות המרה של מרחב צבע. הקוד הבא מקבל אובייקט HSL { H: 360, S: 100, L: 100} ומפיק אובייקט RGB { R: 255, G: 255, B: 255 }. משם, אפשר להשתמש בערכים האלה כדי ליצור מחרוזת rgb או rgba. מידע מפורט יותר זמין במאמר המעניין בנושא 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
, הוא הסטנדרט החדש שהחל להחליף את שניהם. הוא חוסך בחשמל בעולם (ומקצץ כמה פעימות לב במחשב) על ידי מתן אפשרות לדפדפן לשלוט באנימציות על סמך המשאבים הזמינים.
אלה כמה מהתכונות החשובות:
- כשמשתמש יוצא מהפריים, האנימציה יכולה להאט או להפסיק לגמרי כדי למנוע שימוש במשאבים מיותרים.
- קצב הפריימים מוגבל ל-60FPS. הסיבה לכך היא שהיא גבוהה בהרבה מהרמה שאנשים יכולים להבחין בה (רוב האנשים רואים אנימציה 'חלקה' ב-30FPS).
נכון למועד כתיבת המאמר, יש צורך להשתמש ב-requestAnimationFrame
כדי להשתמש בתחילית ספציפית לספק.
Paul Irish יצר שכבת תמיכה עם תמיכה בספקים שונים, בבקשה ל-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-fill כמו requestAnimationFrame.js (יש כמה תכונות שצריך לעבוד עליהן), שיתמוך בדפדפנים ישנים יותר, תוך מעבר לתקן החדש.
(function animate() {
var i = 50;
while(i--) {
if (n > endpos) return;
n += definition;
ctx.globalAlpha = (0.5 - (n + startpos) / endpos) * alpha;
if (doColorCycle) {
hue = n + color;
ctx.strokeStyle = "hsl(" + (hue % 360) + ",99%,50%)"; // iterate hue
}
var x = cos(n / cosdiv) * n * cosmult; // cosine
var y = sin(n / sindiv) * n * sinmult; // sin
ctx.strokeText(text, x + xoffset, y + yoffset); // draw rainbow text
}
timeout = window.requestAnimationFrame(animate, 0);
})();



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