画布中文本自动折行的演示
本帖最后由 马黑黑 于 2022-3-3 22:22 编辑画布中的文本是不会自动折行的,也没有相关设置和方法,必须自己来。这将涉及到精巧的算法。
基于文本的画布有一个 measureText() 方法,它能“测量”出画笔即将处理的文本的站位总宽度。语法简单:
context.measureText(text);
利用这个方法,我们可以计算出画布的宽度能否装得下全部文字,如果不能,则经过一系列计算应占多少行,然后一一“画”出多行文本。先给出我刚写好的演示例子,有空再做解释:
<canvas id="txtCv" width="400" height="400" style="border:1px solid;">浏览器老</canvas>
<script>
var canv = document.getElementById('txtCv');
var ct = canv.getContext('2d');
ct.font = "bold 60px 微软雅黑";
ct.fillStyle = "blue";
var txtStr = "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。";
var ttWidth = ct.measureText(txtStr).width; //获得文本占位总长度
if(ttWidth > canv.width){ //若文本长度大于画布长度
var txtAr = txtStr.split(""); //将文本逐字放入数组
var cWidth = ttWidth / txtAr.length; //计算单字长度
//计算每行能装多少个字:cNum
var cNum = Math.floor(canv.width * 0.9 / cWidth);
//计算需要多少行:lNum
var lNum = Math.ceil(txtAr.length / cNum);
//用一个双循环画出文本
for(j=0; j<lNum; j++){
var tmpStr = "";
for(k=0; k<cNum; k++){
if(j*cNum+k < txtAr.length) tmpStr += txtAr;//console.log(j*cNum+k);
}
ct.fillText(tmpStr,20,j*70+70);
}
} else { // 否则直接画出文本
ct.fillText(txtStr,20,70);
}
</script>
本帖最后由 马黑黑 于 2022-3-3 22:23 编辑 <br /><br /><canvas id="txtCv" width="400" height="400" style="border:1px solid;">浏览器老</canvas>
<script>
var canv = document.getElementById('txtCv');
var ct = canv.getContext('2d');
ct.font = "bold 60px 微软雅黑";
ct.fillStyle = "blue";
var txtStr = "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。";
var ttWidth = ct.measureText(txtStr).width; //获得文本占位总长度
if(ttWidth > canv.width){ //若文本长度大于画布长度
var txtAr = txtStr.split(""); //将文本逐字放入数组
var cWidth = ttWidth / txtAr.length; //计算单字长度
//计算每行能装多少个字:cNum
var cNum = Math.floor(canv.width * 0.9 / cWidth);
//计算需要多少行:lNum
var lNum = Math.ceil(txtAr.length / cNum);
for(j=0; j<lNum; j++){
var tmpStr = "";
for(k=0; k<cNum; k++){
if(j*cNum+k < txtAr.length) tmpStr += txtAr;//console.log(j*cNum+k);
}
ct.fillText(tmpStr,20,j*70+70);
}
} else {
ct.fillText(txtStr,20,70);
}
</script>
本帖最后由 马黑黑 于 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;//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的字符;
k第二次为 1:0*4+1 = 1,tmpStr 得到 txtAr的字符
k第三次为 2:0*4+2 = 2,tmpStr 得到 txtAr的字符
…………
当外层第二次循环,j为1,内层循环即k的变化依然是从 0 到 cNum,那么:
k第一次为 0:1*4+0 = 4,tmpStr 得到 txtAr的字符;
k第二次为 1:1*4+1 = 5,tmpStr 得到 txtAr的字符
k第三次为 2:1*4+2 = 6,tmpStr 得到 txtAr的字符
……
数学不好的话要慢慢去理解上面的算法式子,可能有些难度。
外层循环每一次都在不同的垂直位置写一次文本到画布上,里面也有算法:
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 = [];//行数组
在小键盘上一口气写这么多文字,好为难我,如果有缺漏或说的不清楚,还请原谅。欢迎讨论。
<canvas id="txtCv1" width="400" height="400" style="border:1px solid;">浏览器老</canvas>
<script>
var canv = document.getElementById('txtCv1');
var ct1 = canv.getContext('2d');
ct1.font = "bold 60px 微软雅黑";
ct1.fillStyle = "blue";
var txtStr1 = "千山鸟飞绝,万径人踪灭。欲穷千里目 ,更上一层楼。 ";
var ttWidth = ct1.measureText(txtStr).width; //获得文本占位总长度
if(ttWidth > canv.width){ //若文本长度大于画布长度
var lineAr = [];//行数组
var txtAr = txtStr.split(""); //将文本逐字放入数组
var cWidth = ttWidth / txtAr.length; //计算单字长度
//计算每行能装多少个字:cNum
var cNum = Math.floor(canv.width * 0.9 / cWidth);
//计算需要多少行:lNum
var lNum = Math.ceil(txtAr.length / cNum);
//用一个双循环画出文本
for(j=0; j<lNum; j++){
var tmpStr = "";
for(k=0; k<cNum; k++){
if(j*cNum+k < txtAr.length) tmpStr += txtAr;//console.log(j*cNum+k);
}
ct1.fillText(tmpStr,20,j*70+70);
}
} else { // 否则直接画出文本
ct1.fillText(txtStr,20,70);
}
</script>
老黑{:5_117:}字怎么跑沙发上和你重叠了啊 晕 哦哦,和前面讲的文本还不一样,这个是可以自动分行的。 大猫咪 发表于 2022-3-3 18:49
老黑字怎么跑沙发上和你重叠了啊 晕
你可能没改文件名,在同一个帖子里会相互影响的{:4_173:} 大猫咪 发表于 2022-3-3 18:49
老黑字怎么跑沙发上和你重叠了啊 晕
帮你把文件名改了。灵位,空格占一个位置,都好和汉字一样,占2个位置。我把你的空格也改了{:4_173:} 大猫咪 发表于 2022-3-3 18:49
老黑字怎么跑沙发上和你重叠了啊 晕
HTML里,ID号是唯一的,你重新做一次就只能和前面的重叠,实际上是在原有基础上再做一次,虽然是不合法的 红影 发表于 2022-3-3 19:43
哦哦,和前面讲的文本还不一样,这个是可以自动分行的。
实际上是差不多的,仅在文本修饰上有所不同,这里着重演示文本折行问题 红影 发表于 2022-3-3 19:49
帮你把文件名改了。灵位,空格占一个位置,都好和汉字一样,占2个位置。我把你的空格也改了
嗯嗯 原来上这样啊{:4_189:}谢谢红影,一会学习{:4_179:} 马黑黑 发表于 2022-3-3 18:23
解释(先占个位)
很多没看懂的,比如“计算单字长度”等等,等着看解释。 马黑黑 发表于 2022-3-3 19:49
HTML里,ID号是唯一的,你重新做一次就只能和前面的重叠,实际上是在原有基础上再做一次,虽然是不合法的
{:4_179:}谢谢老黑,明白 慢慢学习 {:4_179:}{:4_204:} 大猫咪 发表于 2022-3-3 19:54
谢谢老黑,明白 慢慢学习
id就是身份证的意思 本帖最后由 马黑黑 于 2022-3-3 22:24 编辑
红影 发表于 2022-3-3 19:52
很多没看懂的,比如“计算单字长度”等等,等着看解释。
单字长度就是单个字所占的宽度 马黑黑 发表于 2022-3-3 19:54
id就是身份证的意思
嗯嗯{:4_179:} 明白 大猫咪 发表于 2022-3-3 19:51
嗯嗯 原来上这样啊谢谢红影,一会学习
我也干过这样的事,后来记得要把ID名修改的事了{:4_173:} 马黑黑 发表于 2022-3-3 19:55
单字长度就是单个子所占的宽度
那个计算方式没懂。 红影 发表于 2022-3-3 21:22
我也干过这样的事,后来记得要把ID名修改的事了
嗯嗯{:4_179:} 红影 发表于 2022-3-3 21:22
那个计算方式没懂。
var cWidth = ttWidth / txtAr.length;
单字长度就是单一字符个体所占的宽度像素值。我们已经知道有多少个字(txtAr.length),又已经知道文本串的占位长度,这样还看不出来么?
说说分钱钱吧,这个好理解:
已知有多少人,又已知有多少钱,那么,每一个人能分多少钱?