Day19 - 中场休息时间 - 怎麽样用Canvas精准的写出一个『字』 - 成为Canvas Ninja ~ 理解2D渲染的精髓

呃,首先呢~

敝人小弟在下我今天仔细的思考了一下,决定这次还是再来一篇『中场休息』科普文,等到明天再来继续磁力/引力的部分

其实是因为刚好工作应接不暇来不及写新的案例 = = (嘘

Canvas文字的艺术

这次的中场休息系列,我们要来探讨的是要怎麽样在Canvas上面绘制文字

讲到这边大概有很多人会觉得超级莫名其妙:

阿不就是用ctx.fillText就解决了吗? 还要讨论什麽?

不要急嘛。

通常初次使用fillText,正常人应该会很直观的就这样写:

function text(ctx,textSource){
  ctx.font = '50px serif';
  ctx.fillStyle="black";
  ctx.fillText(textSource,0,0);
}

(()=>{
  const ctx = document.querySelector('canvas').getContext('2d') ;
  text(ctx,'TEST')
})()

接着打开浏览器,然後就发现萤幕上面什麽都没出现 ~

img

为什麽?看起来该做的都做了啊,怎麽什麽都没长出来?

其实第一个原因就在於ctx.fillText的第2 、 3个参数,这个x = 0, y = 0的基准点,其实预设并不是从该行文字左上角作为顶点起算,而是从左下角起算。

有些人接着说,『那这样是不是我随意地给Y一个足够大的数值,就可以了?』

OK~那就改成这样

function text(ctx,textSource){
  ctx.font = '50px serif';
  ctx.fillStyle="black";
  ctx.fillText(textSource,0,50);
}

(()=>{
  const ctx = document.querySelector('canvas').getContext('2d') ;
  text(ctx,'TEST')
})()

img

文字这样就出来了~!这样就可以打完收工了吗?! YAH! 今天真是EASY!

『错误。』

这边很明显的问题就是50 这个数字是我们随便乱给的,实际上你并不知道Y应该要给多少才能够完整刚好的把文字show出来。

那麽应该怎麽做呢?

请使用ctx.textBaseline 这个属性,ctx.textBaseline是用来决定要把文字哪一处作为垂直渲染基准线property,而当我们把ctx.textBaseline设定在top的时候,文字就会以上缘作为垂直渲染基准线,这样就可以看到我们绘制的文字了~。

延伸阅读: MDN 上的 ctx.textBaseline: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textBaseline

而既然我们提到了垂直渲染基准线,那肯定就也有水平渲染基准线的设置方式~
那就是ctx.textAlign,这个的用法其实就跟css差不多~

延伸阅读: MDN 上的 ctx.textAlign: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/textAlign

那今天就到这边了吗 ~ 好像也不怎麽难嘛哈哈~

『错误。』

眼比较尖的人可能会注意到~我们用fillText写出来的字,好像跟一般html打出来的文字比起来:

就是有那麽一点糊糊

到这边可能就有些人开始翻文件找是不是有什麽反锯齿啦~提升解析度property~但是在找了一阵子之後才发现根本不如所想,接下来可能就开始怀疑是不是硬体效能问题。

咦?你说我怎麽知道你心里在想什麽? 那当然是因为小弟我第一次用fillText时就是撞死在这面墙上 :P

不知道大家还记不记得我们在这个系列文初期的时候有提到过:『一张宽度100px, 高度100px的canvas,它实际上就是100*100 = 10000个像素的集合体』。

记得啊。然後呢?

其实,浏览器画面本身也可以看作是一个比较大号的Canvas,但不同的点之一就在於它的像素密度可能比一般的Canvas还要高。

为什麽说『可能』呢,原因其实就是我们刚刚提到的硬体差异

一般来说,如果使用者的电脑萤幕是Retina萤幕,或是其他类型的高解析度显示器,他的萤幕像素密度会是一般显示器,或是Canvas2倍以上。

这就意味着在同样100x100范围中绘制的文字,高解析度显示器的浏览器画面实际上是显示了至少20000个像素,这就导致用DOM渲染的文字,画质远比用Canvas渲染的文字来的清晰。

但是这个状况当然也有解决办法,那就是强制对Canvas进行压缩处理

还记得我们之前有提到过用css的方式去扩大Canvas的尺寸会导致像素密度变低吗? 这边我们就是要逆转这个现象,首先把Canvas的width/height属性都提高N倍,然後再用css去把style的width/height再缩减N倍。

这麽一来,像素的密度就直接被提高N倍了,而且Canvas的长宽还不变。

接着可能有人会问:

有没有办法求得N实际上应该要给多少才够呢,给太大也不好吧?

这时候就该Window.devicePixelRatio这个property登场啦~

延伸阅读: MDN上的Window.devicePixelRatio: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio

Window.devicePixelRatio这个property就是可以用来侦测当前的显示器像素密度值,例如,Macbook ProRetina萤幕的这个值会是2,而其他种类的显示器也有不同的值。

妥善的运用Window.devicePixelRatio,我们就可以在Canvas上画出跟Dom渲染文字具有同等水平解析度的文字~

到这边为止,我们已经学到怎麽用Canvas去画一行清晰的文字,但是实际上我们今天的内容~

『还没有结束。』

我们今天的目标是「用Canvas精准的写出一个『字』」,但是我们从刚刚到现在,其实都还是在一张固定大小的Canvas上绘制文字。

试想今天如果有一个需求:『如果我需要用Canvas去画一个字,而画好字的canvas,他的长宽尺寸要完全贴合这个画出来的字,要怎麽办?』

碰到像这样的状况,就代表着我们需要获取『字』的『高度』/『宽度』等数据。

这时候可能就会有人怀疑:

有可能取得这些东西吗? 我在用DOM渲染文字的时候从没听说过有这种数据可以取得

基本上,api其实都是应需求而生的,而在canvas的世界中,想要去获取当下所渲染出来的一个文字的高度/宽度,当然也是合情合理~

这边要登场的就是ctx.measureText() 这个api

延伸阅读: MDN上的ctx.measureText: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText

这个api的用法就在於可以传入一个字串,然後他就会根据当前ctx的font属性设置的字体大小等参数,来回传一个TextMetrics 物件。

什麽是TextMetrics 物件? 这是一种包含了ctx环境下渲染某字串的各项视觉数值的一个物件,除了基本的长宽,还能透过部分数值计算出很多人在菜鸟时期可能都想要计算过的文字实际高度(就是文字实际具有颜色的部分所占的高度,不是行高, 也不是X高~!)

延伸阅读: MDN上的TextMetrics物件介绍: https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics

img

透过上述的方法,我们其实就可以利用Canvas做出近似用DOM渲染的文字,这样做的好处就是我们还可以对这样的Canvas文字作进一步加工! 例如填充渐层填充素材图样,甚至可以做出文字变形动画!(然後再把做好的Canvas转成inline-block当成行内元素塞到其他元素当成段落文字的一部分)

小结

以上就是我们第二篇中场休息系列文,虽然是有点想放一些实作的案例,但是碍於时间问题没有完成(之前是有在公司专案实作过用Canvas做渐层文字的IE11 PolyFill~效果真的不错),总之还是希望大家喜欢这次的科普介绍~


<<:  连续 30 天 玩玩看 ProtoPie - Day 19

>>:  不玩惹把钱还给我好否 - 抽单

D30: 工程师太师了: 第16话

工程师太师了: 第16话 杂记: 今天终於是第三十天了, 漫长的三十天, 每天都要努力发文, 有监於...

Learning How to Make a Movie

"The Great Movie Experience" as Myron En...

[VSCodeVim] 官方文件没有详述的实用技巧:以virtualedit所解决的情境为例

(图源:Tim Pope's twitter) [系列文目录] 这篇文章会介绍一个VSCodeVi...

予焦啦!结论与展望(一):Hoddarla 专案的过去、现在与未来

阮毋是喜爱虚华,阮只是环境来拖磨; 人客若叫阮,风雨嘛着行,为伊唱出留恋的情歌。 -- 流浪到淡水...

Day 22. slate × Operation × transform

今天的内容将延续 上一篇 文章中 Operation Process 里的 3. ,同时我们会非常...