热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

canvas换图时候会闪烁_canvas文本绘制自动换行、字间距、竖排等实现

作者|张鑫旭来源|http:www.zhangxinxu.comwordpress?p7362一、canvas对文字排版的支持很弱和CSS相比,SVG以及canvas
9140509399eeb8e31352d6128e26dcd7.png

作者 | 张鑫旭

来源 | http://www.zhangxinxu.com/wordpress/?p=7362

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

和CSS相比,SVG以及canvas对文字排版的支持很弱。在CSS中天然支持的文本自动换行,其他letter-sapcing字间距,writing-mode竖排等都是一个CSS属性就可以实现。但是在canvas中,全部都不支持。canvas绘制文本API为:

CanvasRenderingContext2D.fillText(text, x, y [, maxWidth]);

  • text

  • text是需要绘制的文本。

  • x

  • x是文本绘制的水平参考点坐标。随着CanvasRenderingContext2D.textAlign的设置不同,x的坐标位置也不同。可以表示这段文字内容左侧坐标,或水平中心坐标,或右侧坐标。

    y

    y是文本绘制的垂直参考点坐标。随着CanvasRenderingContext2D.textBaseline的设置不同,y的坐标位置也不同。支持多种基线类型(CSS中也有对应概念),MDN上有一张图可以很好地表示文本基线和文本垂直位置的关系。

    1e26f9e4b6c2d5d7814664c5ef728188.png

    maxWidth

    maxWidth表示文本内容占据的最大宽度。这里的maxWidth概念和CSS中的max-width差别很大,其最终的文本表现是:当文本占据宽度超过maxWidth的后,所有的文本自动变窄以适应这个最大宽度限制。表现类似这样:

    1dd46cfad76999e5838701dccca392ce.png

    您可以狠狠地点击这里: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支持自动换行?首先有一点可以肯定,就是到目前为止,canvas中并没有任何可以让文本自动换行的现成的API。因此注定这个看上去简单的事情实践起来并没有那么容易。通常比较好的实现方法有下面两种:

1. canvas计算与逐行绘制

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

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 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);};API如下:

CanvasRenderingContext2D.wrapText(text, x, y, maxWidth, lineHeight)其中textxy3个参数和fillText()方法中的这3个参数含义是一样的,不赘述。而maxWidth表示的含义可就不一样了,表示最大需要换行的宽度,此参数可缺省,默认会使用canvas画布的width宽度作为maxWidth;lineHeight表示行高,同样可缺省,默认会使用元素在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浏览器):6f225c3a043fc31b541dd662008e6583.png可以看到上方绘制的文字在核实位置自动换行了,您可以修改中的文字内容,点击“绘制”按钮体验下其他文本内容的自动换行绘制效果。

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

关于SVG 让HTML转换成canvas图片的原理和细节可以参见我之前写的“SVG 基本上,本文后面会介绍到的字符间距,文字竖排等实现都可以使用这个方法实现,因此,为了避免不必要的啰嗦,仅本效果会具体演示代码细节,后面效果大家自行拷贝改改就好了。我们先看实例,您可以狠狠地点击这里:canvas借助SVG foreignObject实现文本自动换行demo结果Chrome浏览器下:091f4afad17659ccfba1b81e15645284.pngJS实现如下:

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,我是一段需要换行的文字啦啦啦';此方法优点在于足够简单,只要一段带style样式的HTML代码即可!唯一不足在于兼容性,IE浏览器不支持,最新的Firefox浏览器虽然支持,但是只能以形式呈现,无法绘制到canvas画布上(若谁知道原因欢迎不吝赐教)。不过好的是移动端Safari浏览器以及微信浏览器都是支持的,因此,此方法理论上是可以在移动端使用的。例如我手机Safari的效果截图:4311c38f2cd4542205e2bf37e55ec2cb.png

三、如何让canvas支持字符间距?

1. 如果只需要兼容Chrome,直接letter-spacing控制

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

canvas { letter-pacing: 5px; }绘制的文字字符间距自动就是5px。如此欣喜的特性有必要亲眼见证一下,您可以狠狠地点击这里:canavs文本间距使用CSS letter-spacing实现demo效果如下GIF示意:6cd2096420e61cf1a83c669900a03786.gif完整测试JS代码如下:

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();根据我的观察,貌似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; // 应用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;};API详细:

CanvasRenderingContext2D.letterSpacingText(text, x, y, letterSpacing);其中textxy3个参数和fillText()方法中的这3个参数含义是一样的,不赘述。letterSpacing表示字符间距大小,数值。可缺省,默认会拿元素在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):930ac8b4583b62e4c0bc8718ab3fae1c.gif此方法兼容性非常好,IE9+浏览器都支持,PC和Mobile通吃。

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

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

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

文字竖直排列,对于玩英文的老外,可以使用context.rotate()旋转90deg实现,但是对于中文等中亚文字,却是完全不适合的。因为两种语言的竖直排版规则是不一样的。中文等东亚文字上,例如一些古诗词文字还是正的,仅仅是阅读方向是从上往下,但是,英文(以及阿拉伯数字)由于本身的字符特性,直接就是旋转排列的。44c1dfe6c1b542c426c459693185df57.png所以单纯旋转策略对于大中国并不实用。在CSS中,我们可以使用writing-mode改变文档流的方向,从而实现文字竖排,相关文章可以参见我之前的文章:“改变CSS世界纵横规则的writing-mode属性”,或者购买我的《CSS世界》,其中有详细介绍。那在canvas中又该如何实现呢?

1. JS混合计算逐字排列

混合计算规则如下:全角字符竖排,英文数字等半角字符旋转排列。下面是我扩展的竖排方法,同样MIT协议,可随意使用,保留上面一段作者和出处说明即可。

/* &#64;author zhangxinxu(.com)&#64;licence MIT&#64;description http://www.zhangxinxu.com/wordpress/?p&#61;7362*/CanvasRenderingContext2D.prototype.fillTextVertical &#61; function (text, x, y) { var context &#61; this; var canvas &#61; context.canvas; var arrText &#61; text.split(&#39;&#39;); var arrWidth &#61; arrText.map(function (letter) { return context.measureText(letter).width;    }); var align &#61; context.textAlign; var baseline &#61; context.textBaseline; if (align &#61;&#61; &#39;left&#39;) { x &#61; x &#43; Math.max.apply(null, arrWidth) / 2; } else if (align &#61;&#61; &#39;right&#39;) { x &#61; x - Math.max.apply(null, arrWidth) / 2; } if (baseline &#61;&#61; &#39;bottom&#39; || baseline &#61;&#61; &#39;alphabetic&#39; || baseline &#61;&#61; &#39;ideographic&#39;) { y &#61; y - arrWidth[0] / 2; } else if (baseline &#61;&#61; &#39;top&#39; || baseline &#61;&#61; &#39;hanging&#39;) { y &#61; y &#43; arrWidth[0] / 2; } context.textAlign &#61; &#39;center&#39;; context.textBaseline &#61; &#39;middle&#39;; // 开始逐字绘制 arrText.forEach(function (letter, index) { // 确定下一个字符的纵坐标位置 var letterWidth &#61; arrWidth[index]; // 是否需要旋转判断 var code &#61; letter.charCodeAt(0); if (code <&#61; 256) { context.translate(x, y); // 英文字符&#xff0c;旋转90° context.rotate(90 * Math.PI / 180); context.translate(-x, -y); } else if (index > 0 && text.charCodeAt(index - 1) <256) { // y修正 y &#61; y &#43; arrWidth[index - 1] / 2; } context.fillText(letter, x, y); // 旋转坐标系还原成初始态 context.setTransform(1, 0, 0, 1, 0, 0); // 确定下一个字符的纵坐标位置 var letterWidth &#61; arrWidth[index]; y &#61; y &#43; letterWidth; }); // 水平垂直对齐方式还原 context.textAlign &#61; align; context.textBaseline &#61; baseline;};API名称是fillTextVertical&#xff0c;语法如下&#xff1a;

CanvasRenderingContext2D.fillTextVertical(text, x, y)其中textxy3个参数和fillText()方法中的这3个参数含义是一样的&#xff0c;不赘述。实现的效果是&#xff1a;英文数字等旋转&#xff0c;中文垂直排列。支持textAligntextBaseline等基本设置。您可以狠狠地点击这里&#xff1a;canavs文本竖排JS逐字计算实现demo使用JS如下&#xff1a;

var canvas &#61; document.querySelector(&#39;canvas&#39;);var context &#61; canvas.getContext(&#39;2d&#39;);context.font &#61; &#39;24px STKaiti, sans-serif&#39;;context.textAlign &#61; &#39;center&#39;;context.textBaseline &#61; &#39;top&#39;;context.fillTextVertical(&#39;anglebaby和黄晓明&#39;, canvas.width / 2, 0);结果如下图所示&#xff1a;84cb9bff73e09df6101ff1fc68227c64.png

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

和自动换行实现类似&#xff0c;详细略。

五、结束语

当年CSS之所以一统天下就是在文本展现文字排版这一块非常方便。看看SVG的文本展现&#xff0c;在看看canvas的文本呈现&#xff0c;难用的很。全靠友军衬托啊&#xff01;问题来了&#xff0c;CSS文本呈现这里厉害&#xff0c;那还需要canvas干什么&#xff1f;因为canvas可以方便把文字转换成图片&#xff0c;例如一些广告工具等等&#xff0c;需要前端合成的&#xff0c;就需要canvas大放异彩了。本文扩展的这些方法并未实际项目大规模验证&#xff0c;有疏漏之处在所难免&#xff0c;欢迎指正&#xff01;感谢阅读&#xff01;35669e365f339025e58e0ce268f861d6.png5b466497ef00836592ee7bda8258b9e2.png



推荐阅读
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 本文介绍了UVALive6575题目Odd and Even Zeroes的解法,使用了数位dp和找规律的方法。阶乘的定义和性质被介绍,并给出了一些例子。其中,部分阶乘的尾零个数为奇数,部分为偶数。 ... [详细]
  • 有没有一种方法可以在不继承UIAlertController的子类或不涉及UIAlertActions的情况下 ... [详细]
author-avatar
娟儿2502923263
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有