هرچند امکانات متن در SVG بیشتر هستند، اما باز هم canvas در نوشتن متن حرفی برای گفتن دارد! گاهی لازم است درون canvas چیزی نوشته شود. برای این موقعیتها متدهایی وجود دارند که میتوانند نیاز ما را برطرف کنند. در این آموزش به نوشتن متن در canvas میپردازیم.
متد fillText
با استفاده از این متد میتوان متن موردنظر را به صورت fill
رسم کرد. این متد سه ورودی اصلی میپذیرد که به ترتیب متن موردنظر، و مختصات رسم هستند. ورودی چهارم که اختیاری است طول متن را مشخص میکند. اگر طول متن بیشتر از این مقدار باشد، متن فشرده (scale) شده تا در این طول جا شود:
ctx.fillText("text", X, Y, max_width);
متد strokeText
این متد نیز درست شبیه به متد قبل سه ورودی اصلی و یک ورودی اختیاری دارد. این متد متن موردنظر را به صورت stroke
رسم میکند و طبعا هر چیزی که به ترسیمات stroke
اثر بگذارد، به متنی که با این متد رسم شود نیز تاثیر میگذارد:
ctx.strokeText("text", X, Y, max_width);
متنهایی که رسم میشوند میتوانند حاشیه، خطچین، سایه، طیف رنگ، الگو، و خلاصه تمام ویژگیهای دیگر ترسیمات در canvas را داشته باشند. مشکل اصلی این است که متن هیچگاه وارد شکل فعلی نمیشود و همچنین تعیین مختصات متن در کنار دیگر ویژگیهای مربوط به متن کمی دردسرساز است.
ویژگی textAlign
این ویژگی رفتاری مشابه ویژگی text-align در CSS دارد و از تکرار مطالب آن خودداری میکنیم. این ویژگی مقدارهای زیر را میپذیرد. مختصات X ورودی محل این ویژگی را تعیین کرده و متن نسبت به این خط رسم میشود:
ctx.textAlign = "start" || "end" || "left" || "right" || "middle";
مختصات متن نسبت به این ویژگی رسم میشود، یعنی مثلا اگر مقدار آن left
باشد، متن در آن مختصات X آغاز میشود، ولی اگر مقدار آن right
باشد، متن در آن مختصات X پایان مییابد. مقدار center
نیز باعث میشود مختصات وسط متن در آن مختصات X قرار بگیرد. برای درک بهتر موضوع کد زیر را اجرا کنید:
cvs.width = 700;
cvs.height = 300;
ctx.moveTo(350, 0);
ctx.lineTo(350, 300);
ctx.strokeStyle = "#F00";
ctx.lineWidth = 3;
ctx.stroke();
ctx.font = "3em consolas";
ctx.textAlign = "left";
ctx.fillText("this is left aligned", 350, 60);
ctx.textAlign = "right";
ctx.fillText("this is right aligned", 350, 160);
ctx.textAlign = "center";
ctx.fillText("this is centered", 350, 260);
ویژگی font
این ویژگی نیز دقیقا مانند ویژگی font در CSS رفتار میکند، با این تفاوت که نیازی به تعیین line-height
ندارد. در کد زیر مقدار کامل این ویژگی نوشته شده است اما همانطور که گفته شد میتوانید از نوشتن line-height
خودداری کنید. در برخی مرورگرها این ویژگی خودبهخود حذف میشود:
ctx.font = "[font-style] [font-variant] [font-weight] [font-size] / [line-height] [font-family]";
متد measureText
این متد یک ورودی از نوع متن میپذیرد و ویژگیهای مختلف آن را اندازهگیری کرده و در یک شئ از نوع TextMetrics
برمیگرداند. این متد میتواند ویژگیهای زیادی از متن را اندازهگیری کند، اما فقط اندازهگیری طول متن بهخوبی پشتیبانی میشود! این متد از ویژگی font
تاثیر میپذیرد. به نمونه کد زیر توجه کنید:
let txt = "Hello World!";
ctx.font = "1em consolas";
ctx.measureText(txt); /* { width: 65.9765625 } */
ctx.font = "2em consolas";
ctx.measureText(txt); /* { width: 131.953125 } */
ctx.font = "2em monospace";
ctx.measureText(txt); /* { width: 107.211914 } */
ctx.font = "3em monospace";
ctx.measureText(txt); /* { width: 160.784729 } */
ctx.font = "3em 'times new roman'";
ctx.measureText(txt); /* { width: 158.378906 } */
ویژگی textBaseline
این ویژگی نیز خط مبنای متن را مشخص میکند که متن نسبت به آن رسم میشود. این ویژگی نیز شبیه به ویژگی vertical-align در CSS است اما برخی موارد آن نیاز به توضیح دارند. این ویژگی مقادیر زیر را میپذیرد:
ctx.textBaseline = "top" || "hanging" || "middle" ||
"alphabetical" || "ideographic" || "bottom";
محل این خطوط در تصویر زیر مشخص شده اما رفتار هر خط شاید واضح نباشد. مختصات این خط نسبت به متن مشخص نمیشود، بلکه مختصات متن نسبت به این خط مشخص میشود! و مختصات این خط نیز توسط ورودی متد رسم متن مشخص میشود. برای نمونه اگر مختصات ورودی (500,300)
باشد، مختصات Y این خط نیز 300 خواهد بود، سپس با توجه به ویژگی textBaseline
، موقعیت متن نسبت به این خط مشخص شده و رسم میشود. مثلا اگر این ویژگی برابر top
باشد، متن زیر آن نوشته میشود! (به تصویر دقت کنید تا بهتر متوجه شوید)
بررسی یک نمونه
با توجه به موارد گفته شده، در این بخش یک نمونه بررسی میشود. خواهشمندیم کدهای زیر را بهخوبی بررسی کرده و یاد بگیرید زیرا این موارد جزو سادهترین کدها در ایجاد انیمیشن یا ترسیمات پیچیده هستند و باید از هماکنون به یادگیری آنها بپردازید تا بعدها به مشکل برنخورید. برای هر بخش از کدها توضیحات موردنیاز نیز آورده شده است:
<canvas id="cvs" style="border: 0.1em solid #111;"></canvas>
let cvs = document.getElementById("cvs"),
ctx = cvs.getContext("2d");
cvs.width = 700;
cvs.height = 300;
/* (1) */
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = "900 160px 'tahoma'";
ctx.lineWidth = 10;
ctx.lineCap = ctx.lineJoin = "round";
ctx.setLineDash([60, 15, 80, 15]);
/* (2) */
let gradient = ctx.createRadialGradient(
cvs.width / 2, cvs.height / 2, 0,
cvs.width / 2, cvs.height / 2, 350
);
gradient.addColorStop(0.0, "#08D");
gradient.addColorStop(0.2, "#08D");
gradient.addColorStop(0.2, "#0B5");
gradient.addColorStop(0.4, "#0B5");
gradient.addColorStop(0.4, "#FF0");
gradient.addColorStop(0.6, "#FF0");
gradient.addColorStop(0.6, "#F30");
gradient.addColorStop(0.8, "#F30");
gradient.addColorStop(0.8, "#C0C");
gradient.addColorStop(1.0, "#C0C");
/* (3) */
let vertices = [
180, -10, 180, 60, 130, 60, 130, 100,
-10, 100, -10, 120, 160, 120, 160, 310,
180, 310, 180, 215, 520, 215, 520, 310,
540, 310, 540, 120, 710, 120, 710, 100,
570, 100, 570, -10, 550, -10, 550, 60,
200, 60, 200, -10
],
/* (4) */
offset_size = ctx.getLineDash().reduce((a, b) => a + b),
dash_offset = 0;
/* (5) */
function draw_text () {
/* (6) */
ctx.clearRect(0, 0, cvs.width, cvs.height);
ctx.lineDashOffset = dash_offset;
/* (7) */
ctx.lineWidth = 13;
ctx.strokeStyle = "#111";
ctx.stroke();
ctx.strokeText("TEXT", cvs.width / 2, cvs.height / 2);
/* (8) */
ctx.lineWidth = 8;
ctx.strokeStyle = gradient;
ctx.stroke();
ctx.strokeText("TEXT", cvs.width / 2, cvs.height / 2);
/* (9) */
dash_offset = ++dash_offset % offset_size;
}
/* (10) */
for (let i = 0, l = vertices.length; i < l; i += 2) {
ctx.lineTo(vertices[i], vertices[i + 1]);
}
/* (11) */
setInterval(draw_text, 16);
بخش 1 تعریف ویژگیهای اولیه
در این بخش ویژگیهای اولیه، از جمله font
، lineJoin
، lineCap
، و خطچین را تعیین میکنیم. از آنجایی که میخواهیم متن در وسط عنصر cvs
باشد، برای راحتی کار ویژگیهای textAlign
و textBaseline
را به ترتیب به center
و middle
تعیین میکنیم.
بخش 2 تعریف طیف رنگ
در این بخش یک طیف رنگ شعاعی تعریف میکنیم که مرکز هر دو دایرهی آن وسط cvs
بوده و شعاع دایرهی بزرگتر نیز 350 است. رنگهای این طیف به گونهای هستند که به آرامی تغییر نمیکنند بلکه دارای لبههای مشخص هستند. روش ساخت این نوع طیف رنگ در این پست بهخوبی توضیح داده شده است.
بخش 3 تعریف مختصات نقاط شکل
در این بخش یک آرایه به نام vertices
تعریف میشود. این آرایه شامل نقاطی است که میخواهیم به آنها خط رسم کنیم. میتوانستیم این کار را به صورت دستی انجام داده و صدها خط کد تکراری بنویسیم اما با این ترفند به سادگی میتوانیم هر شکل بزرگ و پیچیدهای را در چند خط ساده رسم کنیم.
بخش 4 مجموع اعضای آرایهی خطچین
در این بخش با کمک متد reduce که یک متد ویژهی آرایه است، مجموع اعداد درون آرایهی خطچین را به دست آورده و در متغیر offset_size
ذخیره میکنیم. درضمن یک متغیر به نام dash_offset
تعریف میکنیم. قرار است برای خطچین شکل و متن یک انیمیشن ایجاد کنیم و برای این کار به این متغیرها نیاز داریم.
بخش 5 تابع انیمیشن
در این بخش تابع draw_text
را تعریف میکنیم که تابع اصلی ما در اجرای انیمیشن است. خود تابع نیاز به توضیحات خاصی ندارد و به توضیح کد درون آن میپردازیم.
بخش 6 آمادهسازی اولیه
در این بخش لایهی ترسیمات کاملا پاک میشود تا ترسیمات بعدی روی آن رسم شوند. همچنین، ویژگی lineDashOffset
برابر مقدار متغیر dash_offset
قرار میگیرد. این ویژگی تا پایان اجرای یک فریم از انیمیشن بدون تغییر باقی میماند.
بخش 7 رسم لایهی بیرونی
در این بخش رنگ حاشیه به سیاه و اندازهی حاشیه به 13 تعیین میشود و متن و شکل رسم میشوند. توجه کنید که منظور از شکل، شکلی است که از رسم نقاط درون آرایهی vertices
ایجاد شده. این شکل یک بار تعریف شده ولی تا وقتی که بخواهیم درون شکل فعلی باقی میماند.
بخش 8 رسم لایهی اصلی
در این بخش رنگ حاشیه به طیف رنگ gradient
که پیشتر تعریف کردیم و اندازهی حاشیه به 8 تعیین میشود. این بار شکل و متن، با حاشیهی کوچکتر و رنگ متفاوت روی حاشیهای که در بخش قبل رسم شد، رسم میشوند و این توهم را به وجود میآورند که انگار حاشیهی شکل، خود دارای حاشیه شده!
بخش 9 ایجاد انیمیشن
در این بخش یک واحد به متغیر dash_offset
اضافه میشود؛ البته به واسطهی عملگر %
و متغیر offset_width
، اطمینان حاصل میشود که مقدار آن از مجموع اعضای آرایهی خطچین بیشتر نشود. این مورد ضروری نیست اما بنا به خاصیت پلهای خطچین، میتوانیم از بزرگ شدن بیمورد آن جلوگیری کنیم و همان نتیجه را بگیریم. حال که یک واحد به این متغیر اضافه شده، در فریم بعدی، مقدار آن به lineDashOffset
رسیده و باعث ایجاد انیمیشن روی خطچین میشود.
بخش 10 رسم خودکار خطوط
در این بخش از آرایهی vertices
استفاده میکنیم. در این بخش یک حلقه داریم که اعداد درون آرایه را به صورت جفتی انتخاب کرده و در متد lineTo
قرار میدهد. به این ترتیب شکل موردنظر رسم میشود. منظور از انتخاب به صورت جفتی این است که گام حلقه 2 است و در هر بار اجرای حلقه میتوانیم به عضو i
و i+1
ام دسترسی داشته باشیم.
بخش 11 اجرای انیمیشن
در این بخش به کمک تابع setInterval
یک انیمیشن ایجاد میشود که هر 16 میلیثانیه یک بار اجرا میشود. هر 16 میلیثانیه یک بار، تابع draw_text
اجرا میشود؛ یعنی حدود 60 بار در یک ثانیه. با هر بار اجرای این تابع، یک واحد به dash_offset
اضافه میشود و به این ترتیب حاشیهی شکل دارای انیمیشن میشود.
تابع setInterval برای ایجاد انیمیشن مناسب نیست و در اینجا فقط از جهت آشنایی استفاده شده. در بخش «انیمیشن در canvas» مفصل به این موضوع پرداخته و توابع مناسب را بررسی میکنیم.
توضیحات تکمیلی
از جمله مواردی که ممکن است در کد بالا شما را گیج کرده باشند، میتوان به عملگر %
اشاره کرد. این عملگر باقیماندهی تقسیم عدد سمت چپ را نسبت به عدد راست برمیگرداند. از خواص تقسیم این است که باقیماندهی یک تقسیم هیچگاه نمیتواند بزرگتر از مقسومعلیه آن تقسیم (یعنی عدد سمت راست) بشود. ما از این ویژگی استفاده کردیم و باقیماندهی تقسیم dash_offset
بر offset_width
را در خود متغیر dash_offset
ذخیره کردیم. یعنی offset_width
مقسومعلیه یا مخرج کسر ماست بنابراین عدد نهایی که باقیماندهی تقسیم است، هیچگاه از آن بزرگتر نمیشود. این ویژگی تقسیم باعث شده نهتنها در اینجا، بلکه در پروژههای فراوان دیگری نیز از این عملگر برای هدف مشابه استفاده شود. ویژگی این عملگر، بهویژه در کار با آرایهها، بسیار کاربردی است.
مشکلات کار با متن در canvas
متاسفانه canvas و SVG امکانات چندان مناسبی برای کار با متن ندارند. در واقع هدف اصلی این فناوریها کار با متن نبوده و نیست. علاوه بر اینکه نوشتن متن به پردازش زیادی نیاز دارد، حتی متن موردنظر وارد شکل فعلی نمیشود و این موضوع باعث شده که انجام بسیاری از کارها یا خیلی سخت شده و یا غیرممکن باشد!
با اینکه میتوان کارهای جالبی با متن انجام داد، اما در موارد کاربردیتر مانند نمایش یک بند نوشته کار بسیار سختی خواهد بود و بهتر است به دنبال روشهای جایگزین باشیم. در بخش «ترکیب HTML و canvas» به این موضوع خواهیم پرداخت.
نتیجهگیری
در این بخش سعی کردیم با امکانات مربوط به متن آشنا شده و یک نمونهی نسبتا بزرگ نیز بررسی کردیم. سعی کنید با مواردی که در این آموزش یاد گرفتید یک طرح ایجاد کنید تا درک بهتری از روند طراحی و متدهای canvas به دست آورید زیرا آموزشهای آینده سختتر و پیچیدهتر خواهند بود. در آموزش آینده به «تعامل شکل فعلی و لایهی ترسیمات» میپردازیم.
سوال داری؟ برو به پنل پرسش و پاسخ