如何让canvas支持自动?我扩展的文本自动方法

  css文字间距怎么设置_css 段落间距设置_css 文字之间的间距

  作者 | 张鑫旭

  来源 |

  一、canvas对文字排版的支持很弱

  和CSS相比,SVG以及canvas对文字排版的支持很弱。

  在CSS中天然支持的文本自动换行,其他letter-sapcing字间距,writing-mode竖排等都是一个CSS属性就可以实现。但是在canvas中,全部都不支持。

  canvas绘制文本API为:

  <pre class="code-snippet__js" data-lang="css">CanvasRenderingContext2D.fillText(text, x, y [, maxWidth]);</pre>

  如果没有maxWidth限制,则文本会一行走到底,直到超出画布尺寸,有点类似CSS中设置容器white-space:nowrap+overflow:hidden的表现。

  二、如何让canvas支持自动换行?

  如何让canvas支持自动换行?

  首先有一点可以肯定,就是到目前为止,canvas中并没有任何可以让文本自动换行的现成的API。

  因此注定这个看上去简单的事情实践起来并没有那么容易。

  通常比较好的实现方法有下面两种:

  1. canvas计算与逐行绘制

  实现原理的核心是CanvasRenderingContext2D.measureText(text)这个API,可以返回一个TextMetrics对象,其中包含了当前上下文环境下textdouble精度的占据宽度,于是我们就可以通过每个字符宽度的不断累加,精确计算哪个位置应该可以换行。

  下面就是我扩展的文本自动换行方法JS代码:

  <pre class="code-snippet__js" data-lang="javascript">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);`};</pre>

  css 段落间距设置_css文字间距怎么设置_css 文字之间的间距

  API如下:

  <pre class="code-snippet__js" data-lang="css">CanvasRenderingContext2D.wrapText(text, x, y, maxWidth, lineHeight)</pre>

  其中text,x,y3个参数和fillText()方法中的这3个参数含义是一样的,不赘述。

  而maxWidth表示的含义可就不一样了,表示最大需要换行的宽度,此参数可缺省,默认会使用canvas画布的width宽度作为maxWidth;lineHeight表示行高,同样可缺省,默认会使用元素在DOM中继承的line-height作为行高。

  使用示例如下:

  <pre class="code-snippet__js" data-lang="javascript">var canvas = document.querySelector('canvas');`var context = canvas.getContext('2d');context.font = '16px sans-serif';context.textBaseline = 'top';`context.wrapText('我是一段会换行的文字啦啦啦', 0, 0);</pre>

  用法很简单,使用wrapText代替原生的fillText即可!

  您可以狠狠的点击这里:自动换行扩展API wrapText演示demo

  下面截图就是demo页面绘制效果(截自IE9浏览器):

  css 段落间距设置_css文字间距怎么设置_css 文字之间的间距

  可以看到上方绘制的文字在核实位置自动换行了css文字间距怎么设置,您可以修改中的文字内容,点击“绘制”按钮体验下其他文本内容的自动换行绘制效果。

  2. 借助SVG 直接把CSS效果绘制上去

  关于SVG让HTML转换成canvas图片的原理和细节可以参见我之前写的“SVG

  基本上,本文后面会介绍到的字符间距,文字竖排等实现都可以使用这个方法实现,因此,为了避免不必要的啰嗦,仅本效果会具体演示代码细节,后面效果大家自行拷贝改改就好了。

  我们先看实例,您可以狠狠地点击这里:canvas借助SVG foreignObject实现文本自动换行demo

  结果Chrome浏览器下:

  css 段落间距设置_css文字间距怎么设置_css 文字之间的间距

  css 段落间距设置_css文字间距怎么设置_css 文字之间的间距

  JS实现如下:

  <pre class="code-snippet__js" data-lang="javascript">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 () { // 把img绘制在canvas画布上 context.drawImage(this, 0, 0, width, height);};tempImg.src = 'data:image/svg+xml;charset=utf-8,';`</pre>

  此方法优点在于足够简单,只要一段带style样式的HTML代码即可!

  唯一不足在于兼容性,IE浏览器不支持,最新的Firefox浏览器虽然支持,但是只能以

  形式呈现,无法绘制到canvas画布上(若谁知道原因欢迎不吝赐教)。

  不过好的是移动端Safari浏览器以及微信浏览器都是支持的,因此,此方法理论上是可以在移动端使用的。例如我手机Safari的效果截图:

  css 段落间距设置_css 文字之间的间距_css文字间距怎么设置

  三、如何让canvas支持字符间距?1. 如果只需要兼容Chrome,直接letter-spacing控制

  对于Chrome浏览器,无论是字符间距还是单词间距,都可以自动继承于元素,这个特性让人非常感动。

  也就是:

  <pre class="code-snippet__js" data-lang="css">canvas { letter-pacing: 5px; }</pre>

  绘制的文字字符间距自动就是5px。

  如此欣喜的特性有必要亲眼见证一下,您可以狠狠地点击这里:canavs文本间距使用CSS letter-spacing实现demo

  效果如下GIF示意:

  css 段落间距设置_css 文字之间的间距_css文字间距怎么设置

  css文字间距怎么设置_css 段落间距设置_css 文字之间的间距

  完整测试JS代码如下:

  <pre class="code-snippet__js" data-lang="javascript">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'; // 并绘制文本,font属性值设置一定要在这里 context.font = '32px sans-serif'; context.fillText('我是一段文本', 0, 50);};// 改变字符间距后重绘range.addEventListener('change', draw);// 一进来根据默认值绘制`draw();</pre>

  根据我的观察,貌似Chrome浏览器在设置font属性值的时候,把letter-spacing等信息一起算作上下文中了。所以,虽然看上去context.font = '32px sans-serif'一直都没变,但却不能放在draw()方法之外,否则,还是按照老的letter-spacing渲染而看不到字符间距变化。

  此方法最简单最容易理解,只可惜,根据我的测试,目前仅Chrome浏览器支持。Firefox以及Safari全都不行。

  2. canvas计算与逐字绘制

  原理为,每一个字符单独作为一个绘制单元,然后根据字符宽度+letterSpacing间距动态绘制,同样,离不开使用CanvasRenderingContext2D.measureText(text)这个API。

  以下就是自己直接在原型上扩展的字符间距绘制方法letterSpacingText,大家可以直接拷贝过去使用,MIT协议,保留原出处即可。

  <pre class="code-snippet__js" data-lang="javascript">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; // 应用letterSpacing占据宽度 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;};`</pre>

  API详细:

  <pre class="code-snippet__js" data-lang="css">CanvasRenderingContext2D.letterSpacingText(text, x, y, letterSpacing);</pre>

  其中text,x,y3个参数和fillText()方法中的这3个参数含义是一样的,不赘述。letterSpacing表示字符间距大小,数值。可缺省,默认会拿元素在DOM环境下的letter-spacing大小作为计算值。

  使用示意:

  <pre class="code-snippet__js" data-lang="javascript">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);`</pre>

  您可以狠狠地点击这里:canavs字符间距JS逐字计算demo

  效果如下GIF示意(居中对齐,截自IE Edge):

  css 文字之间的间距_css文字间距怎么设置_css 段落间距设置

  css 段落间距设置_css文字间距怎么设置_css 文字之间的间距

  此方法兼容性非常好,IE9+浏览器都支持,PC和Mobile通吃。

  3. 借助SVG 直接把CSS效果绘制上去

  和自动换行实现类似,详细略。

  四、如何让canvas支持竖直排列?

  文字竖直排列,对于玩英文的老外css文字间距怎么设置,可以使用context.rotate()旋转90deg实现,但是对于中文等中亚文字,却是完全不适合的。因为两种语言的竖直排版规则是不一样的。

  中文等东亚文字上,例如一些古诗词文字还是正的,仅仅是阅读方向是从上往下,但是,英文(以及阿拉伯数字)由于本身的字符特性,直接就是旋转排列的。

  所以单纯旋转策略对于大中国并不实用。

  在CSS中,我们可以使用writing-mode改变文档流的方向,从而实现文字竖排,相关文章可以参见我之前的文章:“改变CSS世界纵横规则的writing-mode属性”,或者购买我的《CSS世界》,其中有详细介绍。

  那在canvas中又该如何实现呢?

  1. JS混合计算逐字排列

  混合计算规则如下:全角字符竖排,英文数字等半角字符旋转排列。

  下面是我扩展的竖排方法,同样MIT协议,可随意使用,保留上面一段作者和出处说明即可。

  <pre class="code-snippet__js" data-lang="javascript">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'; // 开始逐字绘制 arrText.forEach(function (letter, index) { // 确定下一个字符的纵坐标位置 var letterWidth = arrWidth[index]; // 是否需要旋转判断 var code = letter.charCodeAt(0); if (code 0 && text.charCodeAt(index - 1) < 256) { // y修正 y = y + arrWidth[index - 1] / 2; } context.fillText(letter, x, y); // 旋转坐标系还原成初始态 context.setTransform(1, 0, 0, 1, 0, 0); // 确定下一个字符的纵坐标位置 var letterWidth = arrWidth[index]; y = y + letterWidth; }); // 水平垂直对齐方式还原 context.textAlign = align; context.textBaseline = baseline;};</pre>

  API名称是fillTextVertical,语法如下:

  css文字间距怎么设置_css 段落间距设置_css 文字之间的间距

  <pre class="code-snippet__js" data-lang="css">CanvasRenderingContext2D.fillTextVertical(text, x, y)</pre>

  其中text,x,y3个参数和fillText()方法中的这3个参数含义是一样的,不赘述。

  实现的效果是:英文数字等旋转,中文垂直排列。支持textAlign和textBaseline等基本设置。

  您可以狠狠地点击这里:canavs文本竖排JS逐字计算实现demo

  使用JS如下:

  <pre class="code-snippet__js" data-lang="javascript">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);`</pre>

  结果如下图所示:

  css 段落间距设置_css 文字之间的间距_css文字间距怎么设置

  2. 借助SVG 直接把CSS效果绘制上去

  和自动换行实现类似,详细略。

  五、结束语

  当年CSS之所以一统天下就是在文本展现文字排版这一块非常方便。

  看看SVG的文本展现,在看看canvas的文本呈现,难用的很。全靠友军衬托啊!

  问题来了,CSS文本呈现这里厉害,那还需要canvas干什么?

  因为canvas可以方便把文字转换成图片,例如一些广告工具等等,需要前端合成的,就需要canvas大放异彩了。

  本文扩展的这些方法并未实际项目大规模验证,有疏漏之处在所难免,欢迎指正!

  感谢阅读!

  css 段落间距设置_css 文字之间的间距_css文字间距怎么设置

文章由官网发布,如若转载,请注明出处:https://www.veimoz.com/1525
0 评论
393

发表评论

!