הרקע שלי
<canvas>
נכנס לתודעה שלי בשנת 2006, כשגרסה 2.0 של Firefox הושקה. מאמר ב-Ajaxian שבו מתוארת מטריצת הטרנספורמציה, עורר בי השראה ליצור את אפליקציית האינטרנט הראשונה שלי ב-<canvas>
: Color Sphere (2007). המשחק הכניס אותי לעולם הצבעים והפרימיטיביות הגרפיות, וקיבלתי השראה ליצירה של Sketchpad (2007-2008) במאמץ להרכיב את האפליקציה "טוב יותר מצייר" בדפדפן.
הניסויים האלה הובילו בסופו של דבר ליצירת הסטארט-אפ Mugtug עם חברי הטוב Charles Pritchard. אנחנו מפתחים את Darkroom ב-HTML5 <canvas>
. Darkroom היא אפליקציית שיתוף תמונות ללא השחתה, שמשלבת את עוצמת הפילטרים שמבוססים על פיקסלים עם טיפוגרפיה ורישום מבוססי-וקטור.
מבוא
<canvas>
מאפשר למתכנתים של JavaScript שליטה מלאה בצבעים, בווקטורים ובפיקסלים של המסכים שלהם – המרכיב החזותי של המסך.
הדוגמאות הבאות עוסקות בתחום אחד ב-<canvas>
שלא קיבל הרבה תשומת לב: יצירת אפקטים של טקסט. מגוון האפקטים לטקסט שאפשר ליצור ב-<canvas>
הוא עצום – הדגמות האלה מכסות רק חלק קטן ממה שאפשר לעשות. אמנם המאמר עוסק ב"טקסט" במאמר הזה, אבל אפשר להשתמש בשיטות האלה בכל אובייקט וקטורי. כך תוכלו ליצור רכיבים חזותיים מלהיבים במשחקים ובאפליקציות אחרות:
- צללי טקסט ב-
<canvas>
. - אפקטים של טקסט ב-
<canvas>
שנראים כמו CSS, יצירת מסכות חיתוך, חיפוש מדדים ב-<canvas>
ושימוש במאפיין הצללית. - קשירת אפקטים: קשת ניאון, השתקפות של זברה.
- אפקטי טקסט כמו Photoshop ב-
<canvas>
דוגמאות לשימוש ב-globalCompositeOperation, createLinearGradient, createTemplate. - צללים פנימיים וחיצוניים ב-
<canvas>
- חשיפת תכונה ידועה במידה מועטה; שימוש בכיפוף בכיוון השעון לעומת ליפוף נגד כיוון השעון כדי ליצור את ההיפוך של הטלת הצללית (הצללית).
- Spaceage – אפקט גנרטיבי.
- אפקט טקסט גנרטיבי מבוסס-AI ב-
<canvas>
באמצעות מחזור צבעים של hsl() ו-window.requestAnimationFrame
כדי ליצור תחושה של תנועה.
צלליות טקסט באזור העריכה
אחת מהתוספות האהובות עלי למפרטי CSS3 (לצד border-radius, web-gradients ועוד) היא היכולת ליצור צללים. חשוב להבין את ההבדלים בין CSS לבין צללים של <canvas>
, במיוחד:
ב-CSS נעשה שימוש בשתי שיטות: box-shadow לרכיבי תיבה, כמו div, span וכו', ו-text-shadow לתוכן טקסט.
ב-<canvas>
יש סוג אחד של צללית: הוא משמש לכל האובייקטים הווקטורים; ctx.moveTo, ד.lineTo, קנו
כדי ליצור צל ב-<canvas>
, מקישים על ארבעת המאפיינים הבאים:
- ctx.shadowColor = "red" // string
- הצבע של הצל. אפשר להזין ערכים של RGB, RGBA, HSL, HEX וערכים אחרים.
- ctx.shadowOffsetX = 0; // integer
- המרחק האופקי של הצללית ביחס לטקסט.
- ctx.shadowOffsetY = 0; // integer
- המרחק האנכי של ההצללה ביחס לטקסט.
- ctx.shadowBlur = 10; // integer
- אפקט טשטוש לצל, ככל שהערך גדול יותר, כך הטשטוש גדול יותר.
כדי להתחיל, נראה איך אפשר להשתמש ב-<canvas>
כדי לחקות אפקטים של CSS.
חיפוש של "css text-shadow" (צלל טקסט ב-CSS) בתמונות Google הוביל לכמה
הדגמות נהדרות שניסינו לאמולציה: Line25,
ו-Stereoscopic, ו-Shadow 3D.
אפקט התלת-ממד הסטריאוסקופי (למידע נוסף, ראו תמונת אנליף) הוא דוגמה לשורת קוד פשוטה שניתן להיעזר בה. באמצעות שורת ה-CSS הבאה, אפשר ליצור אשליה של עומק כשצופים באמצעות משקפי 3D אדומים/כחולים (הסוג שמקבלים בסרטים תלת-ממדיים):
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 יהיה מספר הפיקסלים בכל 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"
כדי להסתיר את הטקסט, ובמקביל להציג את הצללית. עם זאת, ניסיון זה לא הצליח; מכיוון שהצל הוא הכפלה של גרסה אלפא של 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>
האלה באופן ידני, לכן הוספתי למקור הדמו מפַתח פשוט של 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. התרשים הזה משתמש בחלק גדול מתחום הצבעים ובמספר רמות של שקיפות אלפא כדי להראות בפירוט את התוצאות הצפויות. מומלץ לעיין בחומר העזר של GlobalCompositeOperationשל Mozilla כדי למצוא תיאורים טקסטואליים. למידע נוסף, תוכלו לקרוא על הפעולה הזו במאמר Compositing Digital תמונות של Porter Duff.
המצב האהוב עליי הוא globalCompositeOperation="lighter". התכונה Lighter משלבת את הפיקסלים שנוספו באופן דומה לאופן שבו אור מעורבב. כשהאור האדום, הירוק והלבן נמצאים בעוצמה מלאה, אנחנו רואים אור לבן. זו תכונה מעניינת שאפשר להתנסות בה, במיוחד כשהערך של <canvas>
מוגדר ל-globalAlpha נמוך. כך אפשר לשלוט בצורה מדויקת יותר ולקבל קצוות חלקים יותר. ל-Lighter כבר נעשה שימושים רבים, ואני הכי אוהב לאחרונה ליצור רקעים ב-HTML5 בשולחן העבודה שנמצא בכתובת http://weavesilk.com/.
אחת מההדגמות שלי, Breathing Galaxies (JS1k), משתמשת גם במצב הבהיר יותר – שרטוט דפוסים משתי הדוגמאות האלה כדי להתחיל לראות איזה השפעה מצב זה מניב.
טיפול בדפדפן של globalCompositeOperation.
אפקט של ניאון-קשת עם רעידות
בדמו הבא נראה איך ליצור אפקט של פוטושופ של זוהר ניאון בצבעי הקשת עם קו מתאר רעוע, על ידי שילוב אפקטים באמצעות 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 באמצעות color+gradient+pattern בו-זמנית. אפשר לציין את סיבוב התבנית בנפרד. שימו לב שפסיי הזברה עכשיו בכיוון בניצב זה לזה. משתמשים במסכת חיתוך בגודל של תיבת הגבול, כך שאין צורך בקונטיינר גדול במיוחד כדי להכיל את צורת החיתוך. כך אפשר לשפר את המהירות על ידי מניעת העיבוד של החלקים המיותרים בצל.
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 Ireland יצר שכבת ספריית Shim עם תמיכה ב-Cross-vender, באמצעות 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, או