canvas文本繪製自動換行、字間距、豎排等實現

2021-02-19 web前端開發
作者 | 張鑫旭來源 | http://www.zhangxinxu.com/wordpress/?p=7362一、canvas對文字排版的支持很弱和CSS相比,SVG以及canvas對文字排版的支持很弱。在CSS中天然支持的文本自動換行,其他letter-sapcing字間距,writing-mode豎排等都是一個CSS屬性就可以實現。但是在canvas中,全部都不支持。
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實現文本自動換行demo
var 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實現demo
var 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逐字計算實現demo
var 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大放異彩了。本文擴展的這些方法並未實際項目大規模驗證,有疏漏之處在所難免,歡迎指正!

相關焦點

  • Canvas學習:繪製文本
    textAlign的值為center時候文本的居中是基於你在fillText的時候所給的x的值,也就是說文本一半在x的左邊,一半在x的右邊(上圖展示看得更清楚些)。所以,如果你想讓文本在整個Canvas居中,就需要將fillText的x值設置成canvas的寬度的一半。
  • Word表格中文字如何自動換行輸入文字右側不換行的方法
    問題描述:  在設定的表格的其中一格輸入文本後(文本有點多但是只顯示開頭的一行其他的不顯示要查看內容需要雙擊才能查看)我要的效果是直接顯示~怎麼設置~  在編輯Word表格內的文字的時候,當文字到了表格的最右邊,再往下打字的時候,那些字都達到表格右邊界限的右邊去了,看不見它們。必須硬回車才能換行,才能看見剛才那幾個字。
  • Word豎排文字方法匯總
    Word豎排文字方向是可以靈活控制的,本文將介紹設置文字方向、文本框、藝術字、表格定位、向左旋轉90度等五種方法來實現Word文字豎排。在Word文檔中右鍵單擊,並選擇彈出菜單上的「文字方向」命令。「文字方向」對話框上有幾種方向選擇,根據需要對文字進行豎排。在下面的應用於,可以選擇Word豎排文字是對整篇文檔還是插入點之後。
  • excel單元格中的文本自動換行和取消換行的教程 - 國哥筆記
    為了方便閱讀,不建議在同一個單元格中寫太長的文本內容。如果在同一個單元格中必須寫很長的文本內容,我們可以讓該單元格中的文本自動換行,從而達到文本不被隱藏的目的。一聽到換行,大家都很熟悉,直接在需要換行的位置使用【Enter鍵】。但很遺憾的是,並沒有達到換行的效果,只是滑鼠光標移到了下一行的單元格。
  • 「強烈建議收藏」小程序canvas繪製帶二維碼海報全流程(枚舉踩坑,詳解解決方案)
    ✅② taro-vue 初始化獲取不到canvas上下文怎麼辦,完全繪製不出來圖片?✅小程序canvas遇到的坑③ 關於canvas 寬高以及縮放比問題,繪製的元素變形,畫布的高度真得等於cavans標籤設置的寬高麼?✅④ canvas怎麼繪製疊在一起的兩張圖片,並控制層級?✅⑤ 如何用canvas繪製,多行文本?
  • Excel表格文字橫排設置成豎排技巧(建議收藏)
    Excel中大量的公式函數可以應用選擇,使用Microsoft Excel可以執行計算,分析信息並管理電子表格或網頁中的數據信息列表與數據資料圖表製作,可以實現許多方便的功能。我這裡給大家說說Excel表格文字橫排設置成豎排的技巧方法、就是利用文字方向來實現豎排和Excel自動換行來實現豎排!
  • Canvas 基本繪製(上)
    又如何進行Canvas進行圖像的繪製呢?在Canvas當中有哪些繪製圖形的方法?來看看下面的文章吧。canvas元素本身並沒有繪製能力(它僅僅是圖形的容器) - 您必須使用腳本來完成實際的繪圖任務。Canvas的基本知識 - getContext對象getContext()方法可返回一個對象,該對象提供了用於在畫布上繪圖的方法和屬性。
  • Word輸入英文自動換行怎麼辦?給文字添加漸變特效?
    我們在進行英語輸入的時候,會自動進行換行,這樣會導致上方的大片空白,排版十分不美觀,下面小編就來教大家如何解決這個問題吧!1.我們打開Word文檔,輸入文字,在最後輸入英文字母。2.當英文字母過長時會自動進行換行,這個時候就會導致上方出現空白。3.我們按Ctrl+A將文本全部選中,點擊段落的設置按鈕。4.點擊中文版式選項,找到——允許西文在單詞中間換行。5.將這個選項勾選,點擊——確定。
  • 自動換行的快捷鍵是什麼?
    在Excel中可以使用alt+enter的快捷鍵對單元格內文本進行換行。這個換行的快捷鍵適用於想在同一單元格內編輯內容,讓內容換行,而不跳到下一行單元格。自動換行功能十分的方便,可以省去設置複雜的單元格格式,在表格編輯中十分實用。
  • html中繪製圖形標籤的詳細介紹
    本篇將介紹的是html中<canvas>標籤的用法,感興趣的朋友可以一起研究一下!在html5中,新增了很多實用的標籤,今天為大家介紹的是html5新增標籤<canvas>,<canvas>標籤只是一個容器,對內容並沒有樣式的更改。那它在html中有什麼用,接下來我們就一起來看看吧!
  • matplotlib如何實現圖形繪製在tkinter的Canvas中?
    matplotlib如何實現圖形繪製在tkinter的Canvas中?今天番茄加速就來分享一下。下面就是最重要的tkinter和matplotlib集成部分,matplotlib提供FigureCanvasTkAgg對象,只需三行代碼,實現圖形繪製在tkinter的Canvas中:canvas_l = FigureCanvasTkAgg(figure_l, frame_l)# 用draw代替canvas_l.draw()canvas_l.get_tk_widget
  • 如何實現PPT中的表格複製到Excel中不自動換行?
    之所以導致PPT或doc文檔中的表格複製到Excel中會換行,是由於PPT/doc文檔中的換行符在Excel是切換到下個單元格,這個大家在日常使用中都有體會,若要在Excel的某個單元格實現換行需要用Alt+Enter的組合鍵來實現。
  • 為何在word中打字時未到行尾就自動換行
    在使用Word時有時可能會遇到這種情況,就是在頁面中打字時明明還沒到行尾,所打的字就自動換到下一行了,如下圖中的文字在箭頭所指的位置就自動換行了。這種情況影響了頁面排版的規整和美觀。其實Word中未到行尾就自動換行基本都是因為誤調整了右縮進引起的(縮進是指Word中的文本與頁面邊界之間的距離)。下圖箭頭所指的標尺處的小三角就是調整Word右縮進的滑塊。這個小三角在標尺的哪個位置,Word中的文字就會在哪裡自動換到下一行。如果想讓Word中的文字恢復成在行尾自動換行,可以用滑鼠左鍵點擊右縮進滑塊後按住滑鼠左鍵不放,向右拖動滑鼠。
  • 微信小程序canvas繪製海報並保存本地相冊
    在做微信小程序電商項目中,想要分享一款商品,使用最多並且最簡便的方法就是使用小程序自帶的分享api進行分享,但是分享出去的頁面比較難看;另一種方法就是自己使用小程序canvas繪製分享的海報,這個海報可以保存在相冊裡,而且可以按照自己的需求效果進行頁面繪製。
  • excel怎麼自動換行?
    前面也跟大家分享過Excel自動換行的視頻教程(excel自動換行本來就簡單,你還在找自動換行快捷鍵?)還是有同學說整一份圖文的,那就來分享一下吧!Excel自動換行名詞解釋:自動換行的意思就是在一個單元格多行顯示超長文本,以便查看所有內容。
  • Excel如何實現單元格內換行與取消換行?
    在使用Excel統計和整理數據時,往往會因為某個單元格中的數據太多,影響整個工作表的數據分析和美觀性,怎麼實現Excel單元格內換行,減少單元格所佔的空間呢?實現Excel單元格內換行的操作方法有兩種,第一種是單元格自動換行,第二種是單元格手動換行,兩種操作方法各有優劣。接下來為大家依次介紹單元格內換行及取消換行的方法。首先介紹的是單元格自動換行的方法。
  • excel怎麼設置自動換行
    了解Excel軟體的小夥伴都知道,在它的單元格中輸入文本時,不會像word文檔那樣可以自行換行,需要進行手動設置。那麼,excel怎麼設置自動換行呢?一起來看看吧。1、在打開的excel工作簿中,我們輸入一行文本後發現它無法自動換行,此時該怎麼設置讓它自動換行?