|
|

楼主 |
发表于 2022-3-3 18:23
|
显示全部楼层
本帖最后由 马黑黑 于 2022-3-3 22:39 编辑
解释:
在画布上作画,每一次都要获得画布操作句柄、2d画笔句柄,所以,下面这几句中,
var canv = document.getElementById('txtCv');
var ct = canv.getContext('2d');
ct.font = "bold 60px 微软雅黑";
ct.fillStyle = "blue";
我们只简单解释后两句:设定字体,我们用了粗体、60px大小、微软雅黑字体,填充颜色是蓝色(blue)。
然后给出欲输出的字串:
var txtStr = "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。";
接着,我们用 measureText() 方法算出字串 txtStr 在画布中如果按设定好的样式输出,它总体应占的宽度并将值赋值给变量 ttWidth:
var ttWidth = ct.measureText(txtStr).width;
当 ttWidth 大于画布的宽度,就需要折行,否则直接输出:
if(ttWidth > canv.width){
// 这里处理折行然后多行输出文本
} else {
// 这里直接输出文本
}
折行处理是核心。
首先,我们需要把文本拆分成以单字为单位的数组并赋值给数组变量 txtAr:
var txtAr = txtStr.split("");
这里,split() 方法是按指定分隔符拆分字符串,参数("")表示没有分隔符,得到的结果是逐一拆分出来的单一的字,每一个字就是一个数组的构成元素。我们获取数组 txtAr 的长度实际上就等于字符串 txtStr 的字符个数,它将参与到后续的计算中。
画布的宽度是事先定好的,所以每一行能装多少个字要计算好,而要计算这个,需要知道每一个字所占的宽度。字符串占位总宽度除以字符串总字数可得到每一个字 cWidth 的宽度:
var cWidth = ttWidth / txtAr.length;
我们还希望画布左右两端总要留一点空白(相当于padding),所以,每一行文本的占位长度应考虑在内,我们这样计算:
var cNum = Math.floor(canv.width * 0.9 / cWidth);
先讲括号里面的式子:画布宽度的90% 除以 单字宽度;再讲 Math.floor 方法:向下取括号中所得的值,也就是忽略小数点后面的数值——这是合理的,是为了确保最右边的字不超出画布。
下面计算需要多少行并赋值给变量 lNum,这时候我们用了 Math.ceil() 方法,向上取括号里的值,即小数点后面的全部数字只要不为0则向上取值,2.0001就取3,因为最后一行哪怕只有一个字也要成行。
var lNum = Math.ceil(txtAr.length / cNum);
括号里的式子不难理解:文本总数 除以 每行字数。
上面解决了每行装的字数、需要行数,下面就是重点中的重点了:用一个双 for 循环语句,令每一行的文本从数组 txtAr 中获取各自的文本——
for(j=0; j<lNum; j++){
var tmpStr = "";
for(k=0; k<cNum; k++){
if(j*cNum+k < txtAr.length) tmpStr += txtAr[j*cNum+k];//console.log(j*cNum+k);
}
ct.fillText(tmpStr,20,j*70+70);
}
外层的 for 语句,按行(lNum)循环,每进行一次循环,临时变量 tmpStr 的清空,仅获取内循环中获取的值,然后在画布上不同的垂直位置输出文本。
内层循环依据每行字数展开,里面涉及到的算法非常精巧:
第一,条件符合才赋值给 tmpStr 临时变量,就是说,处理的第N个字符串个体的N不能大于字符串总字数。
第二,式子 j*cNum+k 是获取每一个字符个体的关键算法,j是外层循环的步进变量,cNum是每行字数,k是内层循环步进变量。我们举例说明一下:
当外层开始循环,j为0,内层循环即k的变化从 0 到 cNum(本例中cNum的值为4),那么:
k首次为 0:0*4+0 = 0,tmpStr 得到 txtAr[0]的字符;
k第二次为 1:0*4+1 = 1,tmpStr 得到 txtAr[1]的字符
k第三次为 2:0*4+2 = 2,tmpStr 得到 txtAr[2]的字符
…………
当外层第二次循环,j为1,内层循环即k的变化依然是从 0 到 cNum,那么:
k第一次为 0:1*4+0 = 4,tmpStr 得到 txtAr[4]的字符;
k第二次为 1:1*4+1 = 5,tmpStr 得到 txtAr[5]的字符
k第三次为 2:1*4+2 = 6,tmpStr 得到 txtAr[6]的字符
……
数学不好的话要慢慢去理解上面的算法式子,可能有些难度。
外层循环每一次都在不同的垂直位置写一次文本到画布上,里面也有算法:
ct.fillText(tmpStr,20,j*70+70);
20是 x 坐标,都在画布的 20 个像素处起笔写字;j*70+70 是 y 坐标,这个式子怎么来?我们知道,本例字体大小设置为 60 个像素,它被视为等宽等高的单位,高度也是60个像素。我们希望行和行之间要拉开一点距离,所以就是70一行,然后:
第一行:0*70+70 = 70;
第二行:1*70+70 = 140;
…………
讨论到这里,聪明的小童鞋发笑了:明明就已经知道了字体大小,你特么还要去计算每个字的宽度?别笑,这是展示算法,同时,ct.font的设置未必就是单个字符的实际占位,很可能因为其他的修饰会是的字体的站位略高于它的大小设置。不过,字体大小是可以参照使用的。
另外提一下,源码中有一句废码,我忘记删除了,特此说明:
var lineAr = [];//行数组
在小键盘上一口气写这么多文字,好为难我,如果有缺漏或说的不清楚,还请原谅。欢迎讨论。
|
评分
-
| 参与人数 2 | 威望 +80 |
金钱 +160 |
经验 +80 |
收起
理由
|
加林森
| + 30 |
+ 60 |
+ 30 |
赞一个! |
红影
| + 50 |
+ 100 |
+ 50 |
赞一个! |
查看全部评分
|