CanvasRenderingContext2D.fillText(text, x, y [, maxWidth]);text
text是需要繪製的文本。
x
x是文本繪製的水平參考點坐標。隨著CanvasRenderingContext2D.textAlign的設置不同,x的坐標位置也不同。可以表示這段文字內容左側坐標,或水平中心坐標,或右側坐標。
y
y是文本繪製的垂直參考點坐標。隨著CanvasRenderingContext2D.textBaseline的設置不同,y的坐標位置也不同。支持多種基線類型(CSS中也有對應概念),MDN上有一張圖可以很好地表示文本基線和文本垂直位置的關係。
maxWidth
maxWidth表示文本內容佔據的最大寬度。這裡的maxWidth概念和CSS中的max-width差別很大,其最終的文本表現是:當文本佔據寬度超過maxWidth的後,所有的文本自動變窄以適應這個最大寬度限制。表現類似這樣:
您可以狠狠地點擊這裡:maxWidth參數讓文字變窄demo
相關測試代碼如下:
var canvas = document.querySelector('canvas');var context = canvas.getContext('2d');context.font = '32px sans-serif';context.fillText('我是一段被maxWidth限制的文本', 0, 50, 200如果沒有maxWidth限制,則文本會一行走到底,直到超出畫布尺寸,有點類似CSS中設置容器white-space:nowrap + overflow:hidden的表現。二、如何讓canvas支持自動換行?首先有一點可以肯定,就是到目前為止,canvas中並沒有任何可以讓文本自動換行的現成的API。因此註定這個看上去簡單的事情實踐起來並沒有那麼容易。1. canvas計算與逐行繪製實現原理的核心是CanvasRenderingContext2D.measureText(text)這個API,可以返回一個TextMetrics對象,其中包含了當前上下文環境下text double精度的佔據寬度,於是我們就可以通過每個字符寬度的不斷累加,精確計算哪個位置應該可以換行。CanvasRenderingContext2D.prototype.wrapText = function (text, x, y, maxWidth, lineHeight) { if (typeof text != 'string' || typeof x != 'number' || typeof y != 'number') { return; } var context = this; var canvas = context.canvas; if (typeof maxWidth == 'undefined') { maxWidth = (canvas && canvas.width) || 300; } if (typeof lineHeight == 'undefined') { lineHeight = (canvas && parseInt(window.getComputedStyle(canvas).lineHeight)) || parseInt(window.getComputedStyle(document.body).lineHeight); } var arrText = text.split(''); var line = ''; for (var n = 0; n < arrText.length; n++) { var testLine = line + arrText[n]; var metrics = context.measureText(testLine); var testWidth = metrics.width; if (testWidth > maxWidth && n > 0) { context.fillText(line, x, y); line = arrText[n]; y += lineHeight; } else { line = testLine; } } context.fillText(line, x, y);};CanvasRenderingContext2D.wrapText(text, x, y, maxWidth, lineHeight)其中text, x, y 3個參數和fillText()方法中的這3個參數含義是一樣的,不贅述。
而maxWidth表示的含義可就不一樣了,表示最大需要換行的寬度,此參數可預設,默認會使用canvas畫布的width寬度作為maxWidth;lineHeight表示行高,同樣可預設,默認會使用<canvas>元素在DOM中繼承的line-height作為行高。var canvas = document.querySelector('canvas');var context = canvas.getContext('2d');context.font = '16px sans-serif';context.textBaseline = 'top';context.wrapText('我是一段會換行的文字啦啦啦', 0, 0);用法很簡單,使用wrapText代替原生的fillText即可!您可以狠狠的點擊這裡:自動換行擴展API wrapText演示demo下面截圖就是demo頁面繪製效果(截自IE9瀏覽器):可以看到上方繪製的文字在核實位置自動換行了,您可以修改<textarea>中的文字內容,點擊「繪製」按鈕體驗下其他文本內容的自動換行繪製效果。2. 藉助SVG <foreignObject>直接把CSS效果繪製上去關於SVG <foreignObject>讓HTML轉換成canvas圖片的原理和細節可以參見我之前寫的「SVG <foreignObject<簡介與截圖等應用」這篇文章。基本上,本文後面會介紹到的字符間距,文字豎排等實現都可以使用這個方法實現,因此,為了避免不必要的囉嗦,僅本效果會具體演示代碼細節,後面效果大家自行拷貝改改就好了。我們先看實例,您可以狠狠地點擊這裡:canvas藉助SVG foreignObject實現文本自動換行demovar canvas = document.querySelector('canvas');var context = canvas.getContext('2d');context.font = '16px sans-serif';var width = canvas.width;var height = canvas.height;
var tempImg = new Image();tempImg.width = width;tempImg.height = height;tempImg.onload = function () { context.drawImage(this, 0, 0, width, height);};tempImg.src = 'data:image/svg+xml;charset=utf-8,<svg xmlns="http://www.w3.org/2000/svg"><foreignObject width="'+ width +'" height="'+ height +'"><body xmlns="http://www.w3.org/1999/xhtml" style="margin:0;font:'+ context.font +';">我是一段需要換行的文字啦啦啦</body></foreignObject></svg>';此方法優點在於足夠簡單,只要一段帶style樣式的HTML代碼即可!唯一不足在於兼容性,IE瀏覽器不支持<foreignObject>,最新的Firefox瀏覽器雖然支持<foreignObject>,但是只能以<img<形式呈現,無法繪製到canvas畫布上(若誰知道原因歡迎不吝賜教)。不過好的是移動端Safari瀏覽器以及微信瀏覽器都是支持的,因此,此方法理論上是可以在移動端使用的。例如我手機Safari的效果截圖:三、如何讓canvas支持字符間距?1. 如果只需要兼容Chrome,直接letter-spacing控制對於Chrome瀏覽器,無論是字符間距還是單詞間距,都可以自動繼承於<canvas>元素,這個特性讓人非常感動。canvas { letter-pacing: 5px; }如此欣喜的特性有必要親眼見證一下,您可以狠狠地點擊這裡:canavs文本間距使用CSS letter-spacing實現demovar canvas = document.querySelector('canvas');var context = canvas.getContext('2d');var range = document.querySelector('input[type=range]');// 繪製方法var draw = function () { context.clearRect(0, 0, canvas.width, canvas.height); canvas.style.letterSpacing = range.value + 'px'; context.font = '32px sans-serif'; context.fillText('我是一段文本', 0, 50);};// 改變字符間距後重繪range.addEventListener('change', draw);// 一進來根據默認值繪製draw();根據我的觀察,貌似Chrome瀏覽器在設置font屬性值的時候,把letter-spacing等信息一起算作上下文中了。所以,雖然看上去context.font = '32px sans-serif'一直都沒變,但卻不能放在draw()方法之外,否則,還是按照老的letter-spacing渲染而看不到字符間距變化。此方法最簡單最容易理解,只可惜,根據我的測試,目前僅Chrome瀏覽器支持。Firefox以及Safari全都不行。2. canvas計算與逐字繪製原理為,每一個字符單獨作為一個繪製單元,然後根據字符寬度+letterSpacing間距動態繪製,同樣,離不開使用CanvasRenderingContext2D.measureText(text)這個API。以下就是自己直接在原型上擴展的字符間距繪製方法letterSpacingText,大家可以直接拷貝過去使用,MIT協議,保留原出處即可。/*** @author zhangxinxu(.com)* @licence MIT* @description http://www.zhangxinxu.com/wordpress/?p=7362*/CanvasRenderingContext2D.prototype.letterSpacingText = function (text, x, y, letterSpacing) { var context = this; var canvas = context.canvas; if (!letterSpacing && canvas) { letterSpacing = parseFloat(window.getComputedStyle(canvas).letterSpacing); } if (!letterSpacing) { return this.fillText(text, x, y); } var arrText = text.split(''); var align = context.textAlign || 'left'; var originWidth = context.measureText(text).width; var actualWidth = originWidth + letterSpacing * (arrText.length - 1); if (align == 'center') { x = x - actualWidth / 2; } else if (align == 'right') { x = x - actualWidth; } context.textAlign = 'left'; arrText.forEach(function (letter) { var letterWidth = context.measureText(letter).width; context.fillText(letter, x, y); x = x + letterWidth + letterSpacing; }); context.textAlign = align;};CanvasRenderingContext2D.letterSpacingText(text, x, y, letterSpacing);其中text, x, y 3個參數和fillText()方法中的這3個參數含義是一樣的,不贅述。letterSpacing表示字符間距大小,數值。可預設,默認會拿<canvas>元素在DOM環境下的letter-spacing大小作為計算值。var canvas = document.querySelector('canvas');var context = canvas.getContext('2d');context.font = '32px sans-serif';context.textAlign = 'center';// 字符間隙5pxcontext.letterSpacingText('我是一段文本', canvas.width / 2, 50, 5);您可以狠狠地點擊這裡:canavs字符間距JS逐字計算demo效果如下GIF示意(居中對齊,截自IE Edge):此方法兼容性非常好,IE9+瀏覽器都支持,PC和Mobile通吃。3. 藉助SVG <foreignObject>直接把CSS效果繪製上去四、如何讓canvas支持豎直排列?文字豎直排列,對於玩英文的老外,可以使用context.rotate()旋轉90deg實現,但是對於中文等中亞文字,卻是完全不適合的。因為兩種語言的豎直排版規則是不一樣的。中文等東亞文字上,例如一些古詩詞文字還是正的,僅僅是閱讀方向是從上往下,但是,英文(以及阿拉伯數字)由於本身的字符特性,直接就是旋轉排列的。在CSS中,我們可以使用writing-mode改變文檔流的方向,從而實現文字豎排,相關文章可以參見我之前的文章:「改變CSS世界縱橫規則的writing-mode屬性」,或者購買我的《CSS世界》,其中有詳細介紹。1. JS混合計算逐字排列混合計算規則如下:全形字符豎排,英文數字等半角字符旋轉排列。下面是我擴展的豎排方法,同樣MIT協議,可隨意使用,保留上面一段作者和出處說明即可。CanvasRenderingContext2D.prototype.fillTextVertical = function (text, x, y) { var context = this; var canvas = context.canvas; var arrText = text.split(''); var arrWidth = arrText.map(function (letter) { return context.measureText(letter).width; }); var align = context.textAlign; var baseline = context.textBaseline; if (align == 'left') { x = x + Math.max.apply(null, arrWidth) / 2; } else if (align == 'right') { x = x - Math.max.apply(null, arrWidth) / 2; } if (baseline == 'bottom' || baseline == 'alphabetic' || baseline == 'ideographic') { y = y - arrWidth[0] / 2; } else if (baseline == 'top' || baseline == 'hanging') { y = y + arrWidth[0] / 2; } context.textAlign = 'center'; context.textBaseline = 'middle'; API名稱是fillTextVertical,語法如下:CanvasRenderingContext2D.fillTextVertical(text, x, y)其中text, x, y 3個參數和fillText()方法中的這3個參數含義是一樣的,不贅述。實現的效果是:英文數字等旋轉,中文垂直排列。支持textAlign和textBaseline等基本設置。您可以狠狠地點擊這裡:canavs文本豎排JS逐字計算實現demovar canvas = document.querySelector('canvas');var context = canvas.getContext('2d');context.font = '24px STKaiti, sans-serif';context.textAlign = 'center';context.textBaseline = 'top';context.fillTextVertical('anglebaby和黃曉明', canvas.width / 2, 0);2. 藉助SVG <foreignObject>直接把CSS效果繪製上去五、結束語當年CSS之所以一統天下就是在文本展現文字排版這一塊非常方便。看看SVG的文本展現,在看看canvas的文本呈現,難用的很。全靠友軍襯託啊!問題來了,CSS文本呈現這裡厲害,那還需要canvas幹什麼?因為canvas可以方便把文字轉換成圖片,例如一些廣告工具等等,需要前端合成的,就需要canvas大放異彩了。本文擴展的這些方法並未實際項目大規模驗證,有疏漏之處在所難免,歡迎指正!