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

הדוגמא המלאה כאן:
http://www.netcraft.co.il/playground/canvaspicture/index.html
ולהלן ההסברים שלב שלב, איך הגעתי לתוצאה.
הקנבס
ל – canvas יש רק שתי תכונות (attributes) הכרחיות – רוחב וגובה. אם לא מגדירים אותן אז הרוחב הדיפולטיבי יהיה 300 פיקסלים והגובה 150 פיקסל. את התכונות מגדירים בתג ולא דרך CSS, אחרת היחס של אלמנטים בתוך ה- canvas יהיה מעוות. כל הפעולות מתבצעות בקונטקסט דו מימדי (2D Rendering Context), שנמצא בכל תג <canvas>, ובעצם קונטקסט דו מימדי זו מערכת קואורדינטות מלבנית.

השלב הראשון הוא להגדיר בקובץ ה – HTML את ה – canvas שלנו (השטח שבו הולכים לצייר), עם הרוחב והגובה שלו:
<canvas id="canvas" width="1100" height="650"></canvas>
ועכשיו ל Javascript.
תשתית לציור
קודם כל נדאג שהקוד שלנו ירוץ לאחר שה canvas מאותחל ב DOM. זה יכול לקרות אחרי שכל העמוד נטען (onload), אך אפשרות יותר טובה היא פשוט לשים את הסקריפט בסוף העמוד (אחרי ה <canvas>), כך הוא ירוץ יותר מהר, עוד לפני שהתמונות יטענו.
כמו כן נצטרך פונקציה שממירה את המעלות לרדיאנים, כי יותר ברור ונוח לנו לעבוד עם מעלות. נשתמש בפונקציה הזאת בפעולת הזזה של קרנות שמש. הגדרנו אותה כהרחבה של טיפוס מספר בסיסי מטעמי נוחות בהמשך.
// fuction of converting grades to radians
Number.prototype.degree = function () {
return this * Math.PI / 180;
};
בדיקה האם הדפדפן תומך ב – canvas מתבצעת ע"י בדיקת המצאוּת getContext ואם כן – מקבלים גישה לקונטקסט ציור שהוא 2D (דו-ממדי):
var canvas = document.getElementById('canvas');
if (canvas.getContext){
// use getContext to use the canvas for drawing
var ctx = canvas.getContext('2d');
}
מגדירים משתנים בשביל הרוחב והגובה של ה – canvas, שנשתמש בהם בעתיד:
// get the canvas sizes
var canvasWidth = canvas.getAttribute("width"),
canvasHeight = canvas.getAttribute("height");
מציירים שמש

מעגל השמש
קודם נצייר מעגל של שמש בשלושה שלבים:
1. מגדירים גרדיאנט מעוגל של המעגל ע"י שיטת
createRadialGradient(x1,y1,r1,x2,y2,r2)
שבה :
x1,y1 – קואורדינאטות מרכז מעגל פנימי (קטן)
r1 – רדיוס מעגל פנימי (קטן)
x2,y2 – קואורדינאטות מרכז מעגל חיצוני( גדול)
r2 – רדיוס מעגל חיצוני (גדול)
// create gradient of sun circle var radgrad = ctx.createRadialGradient(120,140,10,120,130,80);
2. מגדירים נקודות מפתח של גרדיאנט וצבעים ע"י שורות:
radgrad.addColorStop(0, '#F4F201'); radgrad.addColorStop(0.9, '#E4C700'); radgrad.addColorStop(1, 'rgba(228,199,0,0)');
זה נותן 3 נקודות:
- ראשונה, הנמצאת בנקודת התחלה של מרוח בין המעגלים (על מעגל פנימי) ומציינת צבע #F4F201
- שנייה, הנמצאת בנקודת מרווח של 90% מכל המרווח בין המעגלים מנקודת ההתחלה שלו ומציינת צבע #E4C700
- שלישית, הנמצאת בנקודה סופית של המרווח בין המעגלים (ז.א. על המעגל החיצוני) ומציינת צבע
rgba(228,199,0,0)
פונקצית ה rgba דומה לפונקצית rgb, רק בנוסף קיים בה פרמטר שמגדיר שקיפות. במקרה שלנו אנו משתמשים בשקיפות מלאה בשביל להסתיר את הריבוע שבו יהיה מצויר המעגל (השלב הבא).

3. מגדירים שטח (ריבוע) שבו יהיה מצויר הגרדיאנט המעוגל וממלאים את הריבוע בגרדיאנט (כמו בצבע).
// fill the defined rectangle by sun circle gradient ctx.fillStyle = radgrad; ctx.fillRect(40,50,160,160);
קרנות השמש

1. קרנות השמש נצייר בצורת משולשים המסתובבים 360 מעלות סביב נקודה אחת, והפינה הכי חדה של כל משולש פונה למרכז (נקודת מפגש). חוץ מזה אנחנו רוצים שמעגל השמש יסתיר את נקודת המפגש של הקרנות.
במקרה כזה ניתן לשנות את צורת החפיפה של האלמנטים. צורת החפיפה הדיפולטיבית היא כזאת שכל אלמנט חדש מסתיר
את הישן ואנחנו צריכים הפוך: שהקרנות "יכנסו מתחת" למעגל השמש. אז נשנה את צורת החפיפה
globalCompositeOperation מהדיפולטיבית להפוכה (ישן יסתיר חדש): destination-over.
ואחרי זה נגדיר צבע מילוי של משולשי קרנות.
// set composite property for insert rays behind sun circle
ctx.globalCompositeOperation = "destination-over";
// set color of rays
ctx.fillStyle = "#edda58";
2. עכשיו נצייר 12 קרנות:
קודם כל מציירים קרן שמש אחת (העליונה שיוצאת קצת מחוץ לשטח הקנבס), ע"י שורות קוד שמציירות 3 קוים וממלאים את התוכן בצבע מילוי שהוגדר לפני כן.
ctx.beginPath();
ctx.moveTo(120, 130);
ctx.lineTo(100, -50);
ctx.lineTo(130, -50);
ctx.lineTo(120, 130);
ctx.fill();
ע"י מתודה translate אנחנו מזיזים את כל הפיקסלים של הקנבס לערכים x ו-y המוגדרים בה. וע"י מתודה rotate אנחנו מסובבים את המשולש ל30 מעלות בכיוון השעון.
עושים סיבוב מלא בעזרת לולאת for:
// draw 12 triangles of rays: each triangle rotated to 30 grad.
for (var i = 0; i < 12; i++) {
ctx.translate(85, -40);
ctx.rotate((30).degree());
ctx.beginPath();
ctx.moveTo(120, 130);
ctx.lineTo(100, -50);
ctx.lineTo(130, -50);
ctx.lineTo(120, 130);
ctx.fill();
}
(בגלל שאנחנו עושים סיבוב מלא (30×12=360) ה-canvas חוזר לאותה צורה כמו לפני שהשתמשנו במתודה translate). אם לא היינו עושים סיבוב מלא, צריך היה לבצע שמירה של מצב ה canvas ע"י שורת ()ctx.save לפני שימוש ב-translate והחזר מצב שמור ע"י שורת ()ctx.restore אחרי סיום שימוש ב- translate.
ענני Bezier

- לפני שנצייר ענן, נגדיר לו צבע מילוי:
ctx.fillStyle = "#bed2fb"; - הענן בנוי מ-5 עקומות המצוירות ע"י מתודה bezierCurveTo.נצייר את העקומות בעזרת bezierCurveTo בשביל שהציור יהיה דומה יותר לציור ידני (שהעקומות לא יהיו בצורה נכונה). בשביל לצייר עקומת בזיה צריך לציין קואורדינאטות של 3 נקודות: (נקודת התחלה של העקומה – היא הנקודה האחרונה שעצרנו בה או הנקודה שהגדרנו, במקרה שלנו, במתודה ctx.moveTo(410,80);). נקודת ביקורת ראשונה, נקודת ביקורת שניה ונקודת סיום של העקומה. בתמונה – נקודות אדומות הן נקודות ביקורת ונקודות כחולות – נקודות התחלה והסוף.
חשוב שנקודת התחלה של הציור תהיה גם נקודה סופית של העקומה אחרונה.
// draw one cloudlet
ctx.moveTo( 410, 80);
ctx.bezierCurveTo( 410, 20, 448, 0, 510, 30);
ctx.bezierCurveTo( 565, 10, 595, 8, 630, 60);
ctx.bezierCurveTo( 700, 65, 700, 160, 610, 160);
ctx.bezierCurveTo( 580, 190, 540, 200, 490, 160);
ctx.bezierCurveTo( 400, 190, 370, 110, 410, 80);
ctx.fill();
- אנחנו רוצים לצייר את העננים עד סוף הקנבס ברוחב, אז נעטוף את הקוד הזה בלולאת while עד x פחות מרוחב הקנבס. ובקוד שלנו לכל הקואורדינאטות נוסיף משתנים x וy- בהתאם. כל ענן יצויר אחרי 400 פיקסלים מרווח ברוחב. בנוסף לכך אנחנו רוצים שהעננים יופיעו אחד למטה, אחד למעלה. בשביל זה נשנה את הקואורדינאטה y בהתאם למספר הסידורי של הענן. זוגי יצויר בגובה y – 80 פיקסלים, ואי-זוגי – בגובה y של 40 פיקסלים.
וזה הקוד הסופי של ציור העננים:
function drawCloudlets() {
// set cloudlets color fill
ctx.fillStyle = "#bed2fb";
// drawing cloudlet and duplication its until canvas with
var y = 0,
x = 0,
i = 0;
while ( x < canvasWidth ) {
ctx.moveTo(x + 410, y + 80);
ctx.bezierCurveTo(x + 410, y + 20, x + 448, y + 0, x + 510, y + 30);
ctx.bezierCurveTo(x + 565, y + 10, x + 595, y + 8, x + 630, y + 60);
ctx.bezierCurveTo(x + 700, y + 65, x + 700, y + 160, x + 610, y + 160);
ctx.bezierCurveTo(x + 580, y + 190, x + 540, y + 200, x + 490, y + 160);
ctx.bezierCurveTo(x + 400, y + 190, x + 370, y + 110, x + 410, y + 80);
ctx.fill();
x = x + 400;
// for even cloudlet - draw upward
if (i % 2 == 0) {
y = y - 40;
}
// for odd wave - draw downward
else y = y + 40;
i++;
}
}
גלים ריבועיים
![]()
לפני שנצייר גלים, נגדיר את צורת החפיפה של האלמנטים כ destination-over (בשביל השהגלים יסתירו את הסירה שנצייר אחר כך) ונגדיר צבעים של גבול ומילוי.
// set composite property for drawing skiff behind waves ctx.globalCompositeOperation = "destination-over"; // set color of waves line ctx.strokeStyle = "#416cf8"; // set color of waves fill ctx.fillStyle = "#416cf8";
1. בשתי השורות הבאות נגדיר שאנחנו מתחילים לצייר צורה (אזור הגלים הוא בעצם צורה שמלמעלה – גבול גלי ובצדדים ולמטה – גבולות ישרים) ונקודת התחלה:
// begining draw waves shape (all blue area) ctx.beginPath(); // set start point to left side of canvas ctx.moveTo(0, 500);
2. עכשיו נצייר גלים. כאן אנחנו נצייר עקומות בצורה "נכונה", ונשתמש בשביל זה במתודה quadraticCurveTo לציור של כל גל. המתודה מקבלת קואורדינאטות של 2 נקודות: נקודת ביקורת ונקודה סופית. בתמונה – נקודה אדומה היא נקודות ביקורת ונקודה כחולה – נקודת סוף.

גל אחד שלנו יצויר בקוד:
ctx.quadraticCurveTo(50, 480, 110,500);
אם נשנה קואורדינאטה y של נקודת ביקורת ל 40 פיקסלים יותר – הגל יצויר עם בטן למטה, ואם בכל גל הבא נתחיל בנקודה סופית של הגל הקודם וגם נזיז קואורדינאטה x של נקודת ביקורת – נקבל אותו גל, רק בצורה הפוכה.
אנחנו רוצים להריץ את הגלים עד רוחב הקנבס, אז נעטוף את הפעולה בלולאת while וגם נעשה בדיקה האם הגל זוגי או אי-זוגי לפי מספר סידורי (משתנה i) בשביל לדעת איך לצייר את הגל, עם בטן למעלה או למטה:
// draw waves until canvas width
x = y = 0;
var i = 1;
while ( x < canvasWidth ) {
// for even wave - draw upward
if ( i % 2 == 0 ) {
y = 40;
}
// for odd wave - draw downward
else y = 0;
// drawe wave as quadraticCurve
ctx.quadraticCurveTo(x + 50, y + 480, x + 110, 500);
ctx.stroke();
x = x + 120;
i++;
}
3. אחרי ציור של כל הגלים עצרנו בנקודה סופית של קנבס בגובה 500 פיקסל. עכשיו צריך למתוח קוים בשביל לסגור את הצורה כולה ולמלא את הצורה בצבע שהגדרנו לפני תחילת ציור של גלים:
// close shape of waves for fill ctx.lineTo(canvasWidth, canvasHeight); ctx.lineTo(0, canvasHeight); ctx.closePath(); // fill shape in selected by fillStyle color ctx.fill();
סירה על פני המים

ציור של סירה מתבצע ע"י ציור קוים פשוטים ע"י מתודה lineTo שמציינים בה רק קואורדינאטות של נקודה סופית של הקו.
1. לפני זה נגדיר צבעים של קוים ומילוי:
//set lines color ctx.strokeStyle = "#000000"; // set color fill ctx.fillStyle = "#d99d4e";
2. נצייר את צורה של גוף הסירה ונמלא אותה בצבע מילוי (לפני זה נעביר את נקודת התחלה של הצורה לנקודה הרצויה):
// draw skiff body ctx.beginPath(); ctx.moveTo(380, 510); ctx.lineTo(320, 450); ctx.lineTo(610, 450); ctx.lineTo(550, 510); ctx.closePath(); ctx.stroke(); ctx.fill();
3. נגדיר צבע מילוי של מפרש , נצייר אותו בצורת משולש ונמלא אותו בצבע:
// set sail color fill ctx.fillStyle = "#ff6662"; //draw skiff sail ctx.beginPath(); ctx.moveTo(435, 450); ctx.lineTo(435, 250); ctx.lineTo(530, 355); ctx.lineTo(435, 410); ctx.closePath(); ctx.stroke(); ctx.fill();
חוף מבטחים

צורה של אי – היא צורה דומה לחלק מהמעגל, אז נצייר עקומה ע"י מתודה quadraticCurveTo ונסגור את הצורה עם קוים שחוזרים על גבולות הקנבס בפינה ימנית תחתונה.
לפני שנצייר אי, נחזיר את צורת החפיפה של האלמנטים למצב דיפולטיבי שהוא source-over (בשביל שהאי יעלה מעל הצורה של הגלים) ונגדיר צבע מילוי:
// set composite property for drawing island in front of waves
ctx.globalCompositeOperation = "source-over";
// set composite property for drawing island in front of waves
ctx.globalCompositeOperation = "source-over";
//set island color fill
ctx.fillStyle = "#925112";
לאחר מכן נצייר את הצורה עם ציור עקומה וקוים ומילוי בצבע:
// draw island ctx.beginPath(); ctx.moveTo(canvasWidth, 470); ctx.quadraticCurveTo((canvasWidth - canvasWidth / 6), 470, (canvasWidth - canvasWidth / 2.2), canvasHeight); ctx.lineTo(canvasWidth, canvasHeight); ctx.closePath(); ctx.fill();
הדשא של (האי) השכן

ציור של דשא מורכב מהשלבים הבאים:
1. הגדרת צבע ורוחב קוים:
//set grass lines color ctx.strokeStyle = "#61e676"; //set with of lines ctx.lineWidth = 2;
2. ציור קבוצה של 3 קוים בעזרת bezierCurveTo, שכל קו מתחיל באותה נקודה וציור 4 קבוצות של דשא ע"י לולאת for שכל קבוצה נמצאת למטה או למעלה לפי סדר רץ:
//draw 4 grass groups (3 blades in each group)
for ( var i = 0; i < 4; i++ ) {
ctx.moveTo(x + 750, y + 600);
ctx.bezierCurveTo(x + 752, y + 550, x + 770, y + 550, x + 785, y + 545);
ctx.moveTo(x + 750, y + 600);
ctx.bezierCurveTo(x + 740, y + 555, x + 750, y + 555, x + 760, y + 540);
ctx.moveTo(x + 750, y + 600);
ctx.bezierCurveTo(x + 725, y + 550, x + 735, y + 560, x + 710, y + 555);
ctx.moveTo(x + 750, y + 600);
x = x + 100;
// for grass group - draw upward
if ( i % 2 != 0 ) {
y = y + 45;
}
// for odd grass group - draw downward
else y = y - 45;
}
3. סוגרים מסלול ונותנים פקודה לצייר קוים (בצבע strokeStyle שהגדרנו אחרון):
// closing path and drawing lines ctx.closePath(); ctx.stroke();
ברור שזו רק דוגמה קטנה (או לא ממש קטנה), רק בשביל להכיר את היכולת המדהימה הזו של HTML5 ואם פעם נצטרך איזושהי יצירת אמנות באתר – יש לנו את הכלי וקצת ידע גם.

אינה, מדליק ביותר!
נהניתי לקרוא יותר מזה, נהניתי פשוט להסתכל.
איזו פשטות בציור וכמה מחשבה מאחוריה :) וזה נכון, לפעמים גם אנחנו הופכים ילדים ביחס לילדים שלנו, ולומדים מהם.
אהבתי את הרזומה שלך גם כן.
מצפה לבאות!
אני רק שאלה.
באיזה דפדפנים יש תמיכה בcanvas ?
לא כדאי, עד שתהיה תמיכה בכל הדפדפנים בשוק, להשתמש בRaphael או ב
?jquery svg
הי רן,
זה נכון שלא כל הדפדפנים תומכים בcanvas עכשיו
( FF, Chrome, Safari, Opera, IE9 – כן תומכים ).
כאן ניתן לראות בצורה מגניבה את התמיכה ב HTML5+CSS3 בכל הדפדפנים:
http://html5readiness.com/.
אבל אם רוצים תמיכה בIE – זה גם אפשרי ע"י הוספת סקריפט שעוזר בזה:
http://code.google.com/p/explorercanvas/ .
ולגבי שימוש בRaphael או ב jquery svg- זה תלוי במה שאתה צריך. המטרה שלי הייתה ללמוד לצייר בcanvas והתוצאה לפניך :)
פשוט מקסים!
הייתי מציעה בשלב הבא להסתכל על ספריות ציור ב-canvas. אני מכירה את cakejs, שמאפשרת להפוך את תהליך הציור ליעיל יותר על ידי כך שהם בנו אובייקטים מוכנים לצורות בסיסיות. הספרייה הזאת מאפשר גם ליצור תתי מוחלקות שמורכבות מאוסף של אובייקטים, ולהתייחס אליהם כאובייקט אחד! ואז אולי לשכפל אותו, לשנות לו פרמטרים.
אני בעצמי עובדת כרגע על פרוייקט גראפי ב-canvas.
אין ספק שידע מעמיק במתמטיקה הוא הכרחי כדי לצייר בחופשיות כמו שעשית. ישר כח!
אחלה כתבה!
כל הכבוד על הפוסט המושקע, תיעוד ושיתוף הדברים!
תודה.
אני מצטער אבל מאוד קשה לי עם ההתלהבות הזאת מהקנבס אם אין ממשק ויזואלי שמממיר את זה לקוד.
ברור שאפשר ליצור דברים יפים בקוד. אבל רוב הציירים הגדולים לא יצליחו להגיע לביצועים דרך כתיבת קוד. ציור היא תהליך אינטואטיבי שהרבה פעמים דורשת התבוננות ובחירה עיצובית תוך כדי עבודה. עד שלא תהיה תוכנה שתאשר את המרחב פעולה הזה באופן קלאסי ע"י פריימים ותזוזה על ציר הזמן לא נראה יצירות מופת. יש בפיתוח תוכנות (אדובי אדג', ואנימייט) שכנראה יעשו את העבודה. אבל עד שהן יגיעו לרמות ביצוע טובות אנחנו עוד רחוקים.