הרקע שלי
התוודעתי ל-<canvas> בשנת 2006, כששוחררה גרסה 2.0 של Firefox. מאמר ב-Ajaxian, שמתאר את מטריצת הטרנספורמציה, נתן לי השראה ליצור את אפליקציית האינטרנט הראשונה שלי ב-<canvas>, Color Sphere (2007). העבודה הזו הכניסה אותי לעולם של צבעים ופרימיטיבים גרפיים, והיוותה השראה ליצירת Sketchpad (2007-2008) במאמץ ליצור אפליקציה "טובה יותר מ-Paint" בדפדפן.
הניסויים האלה הובילו בסופו של דבר ליצירת הסטארט-אפ Mugtug עם חברי הוותיק צ'ארלס פריצ'רד. אנחנו מפתחים את Darkroom ב-HTML5 <canvas>. Darkroom היא אפליקציה לשיתוף תמונות שלא פוגעת באיכות שלהן, ומשלבת את היכולות של פילטרים מבוססי-פיקסלים עם טיפוגרפיה וציור מבוססי-וקטורים.
מבוא
<canvas> מאפשר למתכנתי Javascript שליטה מלאה בצבעים, בוקטורים ובפיקסלים במסכים שלהם – המבנה החזותי של המסך.
הדוגמאות הבאות עוסקות בתחום אחד ב-<canvas> שלא זכה לתשומת לב רבה: יצירת אפקטים של טקסט. מגוון אפקטי הטקסט שאפשר ליצור ב-<canvas> הוא עצום – ההדגמות האלה מכסות רק חלק קטן מהאפשרויות. למרות שבמאמר הזה אנחנו עוסקים ב"טקסט", אפשר ליישם את השיטות על כל אובייקט וקטורי, וליצור רכיבים חזותיים מעניינים במשחקים ובאפליקציות אחרות:
- צלליות טקסט ב-
<canvas>. - אפקטים של טקסט דמוי CSS ב-
<canvas>, יצירת מסכות חיתוך, חיפוש מדדים ב-<canvas>ושימוש במאפיין הצל. - קשת בענן ניאון, השתקפות זברה – אפקטים משורשרים.
- אפקטים של טקסט כמו בפוטושופ ב-
<canvas>דוגמאות לשימוש ב-globalCompositeOperation, createLinearGradient, createPattern. - צללים פנימיים וחיצוניים ב-
<canvas> - חשיפת תכונה פחות מוכרת: שימוש בליפוף בכיוון השעון לעומת ליפוף נגד כיוון השעון כדי ליצור את ההיפוך של צללית (צללית פנימית).
- עידן החלל – אפקט גנרטיבי.
- אפקט טקסט מבוסס-AI גנרטיבי ב-
<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 הבאה, אנחנו יכולים ליצור אשליה של עומק כשמסתכלים על התמונה עם משקפי תלת-ממד בצבעי אדום/ציאן (כמו אלה שמקבלים בסרטי תלת-ממד):
text-shadow: -0.06em 0 0 red, 0.06em 0 0 cyan;
יש שני דברים שצריך לשים לב אליהם כשממירים את המחרוזת הזו ל-<canvas>:
- אין טשטוש צל (הערך השלישי), אז אין סיבה להפעיל צל, כי 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 שיתקבל יהיה מספר ה-PX בכל 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>
מניעת הכפלת אלפא
בדוגמה מורכבת יותר, כמו אפקט הניאון ב-Line25, צריך להשתמש במאפיין shadowBlur כדי לדמות את האפקט בצורה נכונה. מכיוון שאפקט הניאון מסתמך על כמה צללים, אנחנו נתקלים בבעיה: ב-<canvas> לכל אובייקט וקטורי יכול להיות רק צל אחד. לכן, כדי לצייר כמה צללים, צריך לצייר כמה גרסאות של הטקסט אחת על גבי השנייה.
התוצאה היא הכפלת אלפא, ובסופו של דבר קצוות משוננים.
ניסיתי להריץ את ctx.fillStyle = "rgba(0,0,0,0)" או את "transparent" כדי להסתיר את הטקסט ולהציג את הצל, אבל הניסיון הזה לא הצליח. מכיוון שהצל הוא מכפלה של אלפא של fillStyle, הצל אף פעם לא יכול להיות אטום יותר מ-fillStyle.
למרבה המזל, יש דרך לעקוף את זה. אפשר להגדיר שהצל יופיע במרחק מהטקסט, כך שהם לא יחפפו, וכך להסתיר את הטקסט בצד המסך:
var text = "Hello world!"
var blur = 10;
var width = ctx.measureText(text).width + blur * 2;
ctx.textBaseline = "top"
ctx.shadowColor = "#000"
ctx.shadowOffsetX = width;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -width, 0);
חיתוך מסביב לגוש טקסט
כדי לנקות את זה קצת, אנחנו יכולים למנוע את הציור של fillText מלכתחילה (ולאפשר את הציור של הצל) על ידי הוספת נתיב חיתוך.
כדי ליצור נתיב חיתוך שמקיף את הטקסט, אנחנו צריכים לדעת את גובה הטקסט (שנקרא "em-height", בעבר הגובה של האות "M" במכונת דפוס) ואת רוחב הטקסט.
אנחנו יכולים לקבל את הרוחב באמצעות ctx.measureText().width, אבל ctx.measureText().height לא קיים.
למרבה המזל, באמצעות פריצה ל-CSS (במאמר Typographic Metrics
יש דרכים נוספות לתקן הטמעות ישנות יותר של <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> שלנו יש מגוון שלם של סגנונות שאפשר לקשר אליהם.
אפשר להשתמש באותם אפקטים של צל על כל אובייקט וקטורי, החל מגופני אינטרנט ועד לצורות מורכבות שיובאו מ-SVG, צורות וקטוריות גנרטיביות וכו'.
הפסקה (הערה על pixel-pushing)
כשכתבתי את החלק הזה במאמר, הדוגמה הסטריאוסקופית עוררה את הסקרנות שלי. עד כמה קשה ליצור אפקט של מסך קולנוע תלת-ממדי באמצעות <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;
עכשיו, כל מה שצריך זה להדביק שני מכשירי אייפון למצח, ללחוץ על "הקלטת סרטון" בו-זמנית, ונוכל ליצור סרטי תלת-ממד משלנו ב-HTML5. יש מתנדבים?
קשת בענן, השתקפות זברה – שרשור אפקטים
אפשר לשרשר כמה אפקטים ב-<canvas> בקלות, אבל צריך ידע בסיסי ב-globalCompositeOperation (GCO). כדי להשוות את הפעולות ל-GIMP (או ל-Photoshop): יש 12 GCO ב-<canvas>, וניתן לחשוב על darker ו-lighter כמצבי מיזוג שכבות. 10 הפעולות האחרות מוחלות על השכבות כמסכות אלפא (שכבה אחת מסירה את הפיקסלים של השכבה השנייה). ה-globalCompositeOperation מקשר בין "שכבות" (או במקרה שלנו, מחרוזות קוד) ומשלב אותן בדרכים חדשות ומעניינות:
התרשים globalCompositeOperation מציג מצבי GCO בפעולה. התרשים הזה משתמש בחלק גדול מספקטרום הצבעים ובכמה רמות של שקיפות אלפא כדי לראות בפירוט למה לצפות. מומלץ לעיין בחומר העזר של Mozilla בנושא globalCompositeOperation כדי לקרוא תיאורים טקסטואליים. כדי להבין טוב יותר איך הפעולה עובדת, אתם יכולים לקרוא את המאמר Compositing Digital Images של פורטר דאף.
המצב האהוב עליי הוא globalCompositeOperation="lighter". האפשרות Lighter מערבבת את הפיקסלים שנוספו בדומה לאופן שבו אור מתערבב. כשאור אדום, ירוק ולבן נמצאים בעוצמה מלאה, אנחנו רואים אור לבן. זו תכונה מעניינת שאפשר להתנסות בה, במיוחד כש-<canvas> מוגדר לערך globalAlpha נמוך, שמאפשר שליטה מדויקת יותר וקצוות חלקים יותר. השתמשו ב-Lighter למטרות רבות, והשימוש האהוב עליי לאחרונה הוא יוצר רקע לשולחן העבודה ב-HTML5 שנמצא בכתובת http://weavesilk.com/.
אחד מהדמואים שלי, Breathing Galaxies (JS1k), משתמש גם במצב הבהיר יותר – כשמציירים דפוסים משתי הדוגמאות האלה, מתחילים לראות את האפקט שהמצב הזה מניב.
הטיפול של הדפדפן ב-globalCompositeOperation.
אפקט של קשת ניאון רוטטת
בהדגמה הבאה, נשיג זוהר של קשת בענן ניאון בסגנון פוטושופ עם קו מתאר מרוטט, על ידי שרשור אפקטים יחד באמצעות globalCompositeOperation (מקור-פנימה, בהיר יותר וכהה יותר).
ההדגמה הזו היא המשך של ההדגמה "צלליות טקסט ב-<canvas>",
באמצעות אותה אסטרטגיה של הפרדת הצל מהטקסט (ראו את הסעיף הקודם):
function neonLightEffect() {
var text = "alert('"+String.fromCharCode(0x2665)+"')";
var font = "120px Futura, Helvetica, sans-serif";
var jitter = 25; // the distance of the maximum jitter
var offsetX = 30;
var offsetY = 70;
var blur = getBlurValue(100);
// save state
ctx.save();
ctx.font = font;
// calculate width + height of text-block
var metrics = getMetrics(text, font);
// create clipping mask around text-effect
ctx.rect(offsetX - blur/2, offsetY - blur/2,
offsetX + metrics.width + blur, metrics.height + blur);
ctx.clip();
// create shadow-blur to mask rainbow onto (since shadowColor doesn't accept gradients)
ctx.save();
ctx.fillStyle = "#fff";
ctx.shadowColor = "rgba(0,0,0,1)";
ctx.shadowOffsetX = metrics.width + blur;
ctx.shadowOffsetY = 0;
ctx.shadowBlur = blur;
ctx.fillText(text, -metrics.width + offsetX - blur, offsetY + metrics.top);
ctx.restore();
// create the rainbow linear-gradient
var gradient = ctx.createLinearGradient(0, 0, metrics.width, 0);
gradient.addColorStop(0, "rgba(255, 0, 0, 1)");
gradient.addColorStop(0.15, "rgba(255, 255, 0, 1)");
gradient.addColorStop(0.3, "rgba(0, 255, 0, 1)");
gradient.addColorStop(0.5, "rgba(0, 255, 255, 1)");
gradient.addColorStop(0.65, "rgba(0, 0, 255, 1)");
gradient.addColorStop(0.8, "rgba(255, 0, 255, 1)");
gradient.addColorStop(1, "rgba(255, 0, 0, 1)");
// change composite so source is applied within the shadow-blur
ctx.globalCompositeOperation = "source-atop";
// apply gradient to shadow-blur
ctx.fillStyle = gradient;
ctx.fillRect(offsetX - jitter/2, offsetY,
metrics.width + offsetX, metrics.height + offsetY);
// change composite to mix as light
ctx.globalCompositeOperation = "lighter";
// multiply the layer
ctx.globalAlpha = 0.7
ctx.drawImage(ctx.canvas, 0, 0);
ctx.drawImage(ctx.canvas, 0, 0);
ctx.globalAlpha = 1
// draw white-text ontop of glow
ctx.fillStyle = "rgba(255,255,255,0.95)";
ctx.fillText(text, offsetX, offsetY + metrics.top);
// created jittered stroke
ctx.lineWidth = 0.80;
ctx.strokeStyle = "rgba(255,255,255,0.25)";
var i = 10; while(i--) {
var left = jitter / 2 - Math.random() * jitter;
var top = jitter / 2 - Math.random() * jitter;
ctx.strokeText(text, left + offsetX, top + offsetY + metrics.top);
}
ctx.strokeStyle = "rgba(0,0,0,0.20)";
ctx.strokeText(text, offsetX, offsetY + metrics.top);
ctx.restore();
};
אפקט השתקפות זברה
האפקט "השתקפות זברה" נוצר בהשראת המאמר המצוין של WebDesignerWall על הוספת עניין לדף באמצעות CSS. כאן אנחנו לוקחים את הרעיון צעד אחד קדימה ויוצרים "השתקפות" של הטקסט, כמו שאפשר לראות ב-iTunes. האפקט משלב את fillColor (לבן), createPattern (zebra.png) ו-linearGradient (ברק). זה ממחיש את היכולת להחיל כמה סוגי מילוי על כל אובייקט וקטורי:
function sleekZebraEffect() {
// inspired by - http://www.webdesignerwall.com/demo/css-gradient-text/
var text = "Sleek Zebra...";
var font = "100px Futura, Helvetica, sans-serif";
// save state
ctx.save();
ctx.font = font;
// getMetrics calculates:
// width + height of text-block
// top + middle + bottom baseline
var metrics = getMetrics(text, font);
var offsetRefectionY = -20;
var offsetY = 70;
var offsetX = 60;
// throwing a linear-gradient in to shine up the text
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.1, '#000');
gradient.addColorStop(0.35, '#fff');
gradient.addColorStop(0.65, '#fff');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient
ctx.fillText(text, offsetX, offsetY + metrics.top);
// draw reflected text
ctx.save();
ctx.globalCompositeOperation = "source-over";
ctx.translate(0, metrics.height + offsetRefectionY)
ctx.scale(1, -1);
ctx.font = font;
ctx.fillStyle = "#fff";
ctx.fillText(text, offsetX, -metrics.height - offsetY + metrics.top);
ctx.scale(1, -1);
// cut the gradient out of the reflected text
ctx.globalCompositeOperation = "destination-out";
var gradient = ctx.createLinearGradient(0, offsetY, 0, metrics.height + offsetY);
gradient.addColorStop(0.0, 'rgba(0,0,0,0.65)');
gradient.addColorStop(1.0, '#000');
ctx.fillStyle = gradient;
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height);
// restore back to original transform state
ctx.restore();
// using source-atop to allow the transparent .png to show through to the gradient
ctx.globalCompositeOperation = "source-atop";
// creating pattern from <image> sourced.
ctx.fillStyle = ctx.createPattern(image, 'repeat');
// fill the height of two em-boxes, to encompass both normal and reflected state
ctx.fillRect(offsetX, offsetY, metrics.width, metrics.height * 2);
ctx.restore();
};
צללים פנימיים/חיצוניים בקנבס
המפרט של <canvas> לא מתייחס לנושא של צללים "פנימיים" לעומת צללים "חיצוניים". למעשה, במבט ראשון, יכול להיות שתחשבו שאין תמיכה בצל "פנימי". זה לא המצב.
רק קצת יותר מסובך להפעיל אותה ;) כמו שהוצע בפוסט האחרון של F1LT3R, אפשר ליצור צללים פנימיים באמצעות המאפיינים הייחודיים של כללי הליפוף בכיוון השעון לעומת נגד כיוון השעון. כדי לעשות את זה, יוצרים "צל פנימי" על ידי ציור מלבן של מאגר, ואז, באמצעות כללי פיתול הפוכים, מציירים צורת חיתוך – ויוצרים את הצורה ההפוכה.
בדוגמה הבאה אפשר לעצב את inner-shadow ואת fillStyle באמצעות צבע, שיפוע ותבנית בו-זמנית. אפשר לציין את סיבוב התבנית בנפרד. שימו לב שהפסים של הזברה עכשיו מאונכים זה לזה. מסכת חיתוך בגודל של התיבה התוחמת (bounding box) משמשת להסרת הצורך במאגר גדול במיוחד כדי להקיף את הצורה החתוכה – מהירות העיבוד משתפרת כי החלקים המיותרים של הצל לא מעובדים.
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 והאלפא; כך שהאלמנטים הווקטוריים שלנו יקבלו תחושה של תנועה.
אם ממפים את מיקום ה-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.
// 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. הסיבה לכך היא שהערך הזה גבוה בהרבה מהרמה שבני אדם יכולים להבחין בה (רוב בני האדם רואים אנימציה "זורמת" ב-30FPS).
נכון לזמן כתיבת המאמר, נדרשות קידומות ספציפיות לספק כדי להשתמש ב-requestAnimationFrame.
פול אייריש יצר שכבת shim עם תמיכה בספקים שונים, במאמר requestAnimationFrame for smart animating:
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback, /* DOMElement */ element){
window.setTimeout(callback, 1000 / 60);
};
})();
אם רוצים להרחיב את זה עוד יותר, אפשר לקשר את זה ל-polyfill כמו 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>. אפשר להעביר אותו לקובצי הפעלה של אייפון, Android או מחשב באמצעות PhoneGap, או