实现如标题所示的这些文本效果,在css看来,不就是一两行css属性。然而,对于canvas来讲,要想呈现这样的文字样式,就没css那么轻松简便了。

既然如此,那为何还要使用对文本支持度不友好的canvas来绘制文字呢?而不是采用对文本天然支持的css呢?

canvas 绘制文本的场景

在给为何弃对文本天然支持的 css而不用,拣起了文本支持度不友好的canvas的答复之时。不妨先来看看,究竟是在什么样的场景下,需要用到canvas去绘制文本呢?

常逛商场的,总会不时地看到一些商家为了吸引客流而制作出的一张精美海报;再看艺术展前,也总能在大门看到本次参展的“艺术家”们最为得意的艺术及简介,所绘制的一张海报;去电影院观看的,大都是被该片前期在各大商场及公共场合宣传的那张海报而吸引来的。……

是咯,照这样看来,海报的确可以作为事物的重要途径,虽然海报承载的内容有限,但一张足够优美的海报,总是能把人们的目光给吸引过来的,好比去书店买书,总是会翻到书的背面。

甭管对前沿技术的学习,抑或是为了解决问题而去搜寻资料,你总免不了在各大博客(如:秋码记录 )、论坛社区以及生活类情感博客等网站游离,那些博文、贴文等总能解决你的燃眉之急的问题,而你也会在兴奋之余随手将该文分享给朋友。

很显然,文章分享功能可以说是各大网站的基础功能,网站运营者也热衷于你把文章分享出去,从而悄无声息的向你朋友推荐了该网站。所以,网站运营者是绝不会错失这样一个向用户宣传网站媒介之一的机会。然而,社会分享组件(第三方分享:诸如 QQ、微信、微薄……)并不能很好的推介网站,基于这种状况,一张既有文章的概要,又有网站的简介的海报便应运而生了。(秋码记录 便能为文章生成一张精美的海报)。

网站生成海报的几种方案

无论是线下的,还是线上的海报,它都是一张。对于线下,想要制作出一张海报,只需将事先做好的海报图打印出来即可,但就线上而言,网站页面结构主要由htmlcssjavascript构成,而文章内容及文章封面图都是不一样的,那么,在这样的情况下,又该如何为文章生成一张海报呢?

既然知道了,海报是一张,而文章页面结构又是html,那么只需将html转变成不就成了吗?

没错,要想为文章生成海报,只需把html转成图片即可,而且还有开源第三方实现。

  • 1、html2canvas.js:从其命名上来看,一眼便能知晓,它是将页面的html转成canvas。这也是众多网站生成海报的首选之一。它的便捷在于你想要为海报生成什么款式的,你只需像修改你网站那样去修改就好。(目前 秋码记录 是采用这种方式来为文章生成海报
  • 2、dom-to-image.js:与html2canvas.js大致差不多。
  • 3、canvas:直接使用canvas绘制而生成的海报,基于wordpress的很多主题也都使用它来生成海报。( 秋码记录 即将采用这种方案)

canvas绘制文本

在使用canvas绘制文本之前,有必要了解下什么是canvas

其实,canvas也是作为html标签而存在于html结构中,而它常常被用来绘制图形及图形动画。

在现实生活中,艺术家想要画出一张画,有两样东西是必不可少的——素描纸(写生纸)、HB铅笔

而在canvas中,同样需要设置画布的大小——widthheight

canvas绘制文本API

CanvasRenderingContext2D.fillText(text, x, y [, maxWidth]);
  • texttext是需要绘制的文本。
  • xx是文本绘制的水平参考点坐标。随着CanvasRenderingContext2D.textAlign的设置不同,x的坐标位置也不同。可以表示这段文字内容左侧坐标,或水平中心坐标,或右侧坐标。
  • yy是文本绘制的垂直参考点坐标。随着CanvasRenderingContext2D.textBaseline的设置不同,y的坐标位置也不同。支持多种基线类型(CSS中也有对应概念),MDN上有一张图可以很好地表示文本基线和文本垂直位置的关系。
  • maxWidthmaxWidth表示文本内容占据的最大宽度。这里的maxWidth概念和CSS中的max-width差别很大,其最终的文本表现是:当文本占据宽度超过maxWidth的后,所有的文本自动变窄以适应这个最大宽度限制。表现类似这样:

canvas 如何实现文本首行缩进、自动换行、内容过多省略号呢?

虽然到目前为止,canvas API中还并没有提供文本首行、自动换行、内容过多以省略号结束等的支持,但还是可以通过计算X Y偏移量来实现的。

首行缩进

对于文本首行缩进,也就是在首行空出相对应的字符个数的空白位置,从而使得与其它行在视觉上达到了缩进的效果。

那么,canvas要想绘制出如css那样的首行缩进的文本样式效果,只需将X水平向右偏移相应的像素(pixel)即可。

内容过多以省略号结尾

无论是海报,还是书本的封面,总不肯多写几个字,倒像是一个惜字如命的家伙,生怕自己写多了,给自个儿带来了寿命减少几秒钟的担忧。那么,这时候,省略号这时候便闪亮登场了,很好地诠释着这一足够吸引用户眼球的重要任务。

而在canvas绘制文本时,只需判断是否绘制到文本的最后,若是,便在最后给原有文本追加上...即可。

自动换行

相对于首行缩进来讲,canvas要想实现文本自动换行,不单单只是计算X水平偏移量那么简单咯!

而还要计算Y纵向(垂直)偏移量,为了让你能够理解Y纵向偏移,我举个通俗的例子,我们平时不管是拿笔在纸上写字,还是在电子设备上敲击着文字,文本内容无不是由上而下自上而下从上而下的顺序呈现在我们的面前。由于纸或电子设备的宽度所限制,文本不得不另起一行,而这另起一行与之前的一行,就存在着纵向关系——另起一行是在之前的一行的下面(下方/ under),而之前的一行则就在另起一行的上方(上面 / upper)。

在实现自动换行这一效果时,我们应考虑到canvas该如何知道绘制当前在哪个位置(X水平位置)确需另起一行呢?这一点很重要,也是实现canvas自动换行的核心所在。

还是拿我们在纸张上写字或在电子设备上敲击文本时,我们知道换行(那是由于在纸张写的字大小差不多,一行只能容纳这么多文字)或电子设备设定好了字体大小,以此来推算出你写到哪个文字时,让你另起一行

那么,canvas实现自动换行可以通过计算文字字体大小`及纵向(Y)偏移量的。

canvas提供了CanvasRenderingContext2D.measureText(text)这个API,它可以用来计算字符的宽度。

canvas 是以左上角为原点,也就是说,X轴 水平向右是正数,反之亦然;Y 轴纵向则是以向下为正数,反向则负。

要让canvas知道该在哪个位置自动换行,只需将文本的每个字符宽度进行累加,判断总字符宽度是否达到了canvas所设定的画布宽度,若是,则另起一行继续绘制剩余的文本字符,当然咯,在另起一行时,纵向 Y偏移量是要往下移动。

 function drawMoreLines(canvas, style, content) {
     const ctx = canvas.getContext('2d')
     //从字体中解析字体大小 如:  font: '25px Helvetica'
     const fontHeight = parseInt(style.font.match(/\d+/), 10)
     //设置 canvas 文本属性
     ctx.font = style.font
     ctx.fillStyle = style.color
     ctx.textBaseline = 'top'
     ctx.textAlign = 'left'
     
     // X 轴水平偏移量 根据实际情况自行修改
     let alignX = 40
     //行宽
     let lineWidth = 0
     //文本内容截取的最后一个索引
     let lastSubStrIndex = 0
     //Y 轴 纵向偏移量
     let offsetY = 0
     
     //遍历文本字符
     for (let i = 0; i < content.length; i++) {
         //计算并累加每个字符的宽度
         lineWidth += ctx.measureText(content[i]).width;
         //判断累加后的字符宽度是否大于 画布宽度 减去 某个特定值(根据实际情况自行修改)
         if (lineWidth > canvas.width - 140) {

             //判断是否是首行
             if(lastSubStrIndex == 0 && style.textIndent){
                 //首行 X 轴水平向右偏移 style.textIndent 个像素
                 ctx.fillText(content.substring(lastSubStrIndex, i + 1), alignX + style.textIndent , offsetY);
             }else {
                 //非首行
                 
                 //判断是否是最后一行
                 if(style.ellipsis && i == content.length - 1){
                     //将在原有文本字符末尾追加上 ...
                     ctx.fillText(content.substring(lastSubStrIndex, i ) + '...', alignX , offsetY);

                 }else{
                     //按照正常的方式绘制文本字符
                     ctx.fillText(content.substring(lastSubStrIndex, i + 1), alignX , offsetY);

                 }


             }


             //另起一行,Y 轴偏移量是通过累加 字体大小 乘以 字符 行高
             offsetY += fontHeight * style.lineHeight
             //新的一行,将 lineWidth 重新设置为 0
             lineWidth = 0
             //文本内容截取索引设置为 另起一行 前 循环的索引
             lastSubStrIndex = i


         }
       
     }
 }

通过以下方式进行测试:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>秋码记录</title>
</head>
<body>
    
    <canvas id="content" width="500" height="400"></canvas>

    <script>
        const contentStyle = {
            font: '25px Helvetica',
            lineHeight: 1.5,
            position: 'left',
            color: 'rgba(88, 88, 88, 1)',
            textIndent: 40,
            ellipsis: true
        }
        
        let text = '自 秋码记录 没再为云服务器续费那会儿起,便选用了由Hugo驱动的博客主题,来迁移秋码记录 上的所有文章,然而,在众多Hugo博客主题之列,竟找不出一套属于自己想要的风格的主题,故而,也只能暂且'
 '
       
       let $content = document.getElementById('content')
       drawMoreLines($content, contentStyle, text);
    </script>
    </body>
</html>

在这里插入图片描述

canvas 绘制竖排文本

css中,要想实现文字竖排,只需改变writing-mode文档流的方向即可。然而只需这一行属性便可实现竖排文本的效果,使用canvas却不是那么容易的事情。

function verticalText(canvas,style,content){

        const ctx = canvas.getContext('2d')
        
        //设置 canvas 文本属性
        ctx.font = style.font
        ctx.fillStyle = style.color
        ctx.textBaseline = 'top'
        ctx.textAlign = 'left'

         // X 轴水平偏移量 根据实际情况自行修改
        let alignX = 40
        //Y 轴 纵向偏移量
        let offsetY = 10

         // 开始逐字绘制
        for (let i = 0; i < content.length; i++) {
            // 确定下一个字符的纵坐标位置
            let letterWidth = ctx.measureText(content[i]).width;

            if( i > 0){
                offsetY += (ctx.measureText(content[i - 1]).width) / 2
            }

            ctx.fillText(content[i], alignX , offsetY);
            // 旋转坐标系还原成初始态
            ctx.setTransform(1, 0, 0, 1, 0, 0);
            // 确定下一个字符的纵坐标位置
            letterWidth =  ctx.measureText(content[i]).width;
            offsetY += letterWidth;
        }

        // 水平垂直对齐方式还原
        ctx.textAlign = 'left';
        ctx.textBaseline = 'top';

       }

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>秋码记录</title>
</head>
<body>

    <canvas id="vertical-text" width="100" height="300"></canvas>
    
    <script>
         let verText = '你关注的领域,'

        let $varticleText = document.getElementById('vertical-text')
        verticalText($varticleText , contentStyle, verText);
    </script>
    
    </body>
</html>

在这里插入图片描述

至于竖排自动换行不在本文的话题中,其实,能实现水平(X 轴)自动换行,那么 Y 轴(纵向)文本自动换行,不就是判断文字字体高度累加是否达到了canvas画布的高度,若是,则 X 轴水平向右移动(X 轴偏移量多少取决于文字的宽度及字间距`)。

还是来看看 秋码记录 为文章生成的海报(目前还是基于 html2canvas.js实现的,迟早会换掉的)

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐