马黑黑 发表于 2022-3-3 18:22

画布中文本自动折行的演示

本帖最后由 马黑黑 于 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 18:23

本帖最后由 马黑黑 于 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 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;//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 = [];//行数组

在小键盘上一口气写这么多文字,好为难我,如果有缺漏或说的不清楚,还请原谅。欢迎讨论。

大猫咪 发表于 2022-3-3 18:44

<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>

大猫咪 发表于 2022-3-3 18:49

老黑{:5_117:}字怎么跑沙发上和你重叠了啊   晕   

红影 发表于 2022-3-3 19:43

哦哦,和前面讲的文本还不一样,这个是可以自动分行的。

红影 发表于 2022-3-3 19:43

大猫咪 发表于 2022-3-3 18:49
老黑字怎么跑沙发上和你重叠了啊   晕

你可能没改文件名,在同一个帖子里会相互影响的{:4_173:}

红影 发表于 2022-3-3 19:49

大猫咪 发表于 2022-3-3 18:49
老黑字怎么跑沙发上和你重叠了啊   晕

帮你把文件名改了。灵位,空格占一个位置,都好和汉字一样,占2个位置。我把你的空格也改了{:4_173:}

马黑黑 发表于 2022-3-3 19:49

大猫咪 发表于 2022-3-3 18:49
老黑字怎么跑沙发上和你重叠了啊   晕

HTML里,ID号是唯一的,你重新做一次就只能和前面的重叠,实际上是在原有基础上再做一次,虽然是不合法的

马黑黑 发表于 2022-3-3 19:50

红影 发表于 2022-3-3 19:43
哦哦,和前面讲的文本还不一样,这个是可以自动分行的。

实际上是差不多的,仅在文本修饰上有所不同,这里着重演示文本折行问题

大猫咪 发表于 2022-3-3 19:51

红影 发表于 2022-3-3 19:49
帮你把文件名改了。灵位,空格占一个位置,都好和汉字一样,占2个位置。我把你的空格也改了

嗯嗯   原来上这样啊{:4_189:}谢谢红影,一会学习{:4_179:}

红影 发表于 2022-3-3 19:52

马黑黑 发表于 2022-3-3 18:23
解释(先占个位)

很多没看懂的,比如“计算单字长度”等等,等着看解释。

大猫咪 发表于 2022-3-3 19:54

马黑黑 发表于 2022-3-3 19:49
HTML里,ID号是唯一的,你重新做一次就只能和前面的重叠,实际上是在原有基础上再做一次,虽然是不合法的

{:4_179:}谢谢老黑,明白   慢慢学习    {:4_179:}{:4_204:}

马黑黑 发表于 2022-3-3 19:54

大猫咪 发表于 2022-3-3 19:54
谢谢老黑,明白   慢慢学习

id就是身份证的意思

马黑黑 发表于 2022-3-3 19:55

本帖最后由 马黑黑 于 2022-3-3 22:24 编辑

红影 发表于 2022-3-3 19:52
很多没看懂的,比如“计算单字长度”等等,等着看解释。
单字长度就是单个字所占的宽度

大猫咪 发表于 2022-3-3 19:57

马黑黑 发表于 2022-3-3 19:54
id就是身份证的意思

嗯嗯{:4_179:} 明白

红影 发表于 2022-3-3 21:22

大猫咪 发表于 2022-3-3 19:51
嗯嗯   原来上这样啊谢谢红影,一会学习

我也干过这样的事,后来记得要把ID名修改的事了{:4_173:}

红影 发表于 2022-3-3 21:22

马黑黑 发表于 2022-3-3 19:55
单字长度就是单个子所占的宽度

那个计算方式没懂。

大猫咪 发表于 2022-3-3 21:28

红影 发表于 2022-3-3 21:22
我也干过这样的事,后来记得要把ID名修改的事了

嗯嗯{:4_179:}

马黑黑 发表于 2022-3-3 21:29

红影 发表于 2022-3-3 21:22
那个计算方式没懂。

var cWidth = ttWidth / txtAr.length;

单字长度就是单一字符个体所占的宽度像素值。我们已经知道有多少个字(txtAr.length),又已经知道文本串的占位长度,这样还看不出来么?

说说分钱钱吧,这个好理解:

已知有多少人,又已知有多少钱,那么,每一个人能分多少钱?
页: [1] 2 3 4
查看完整版本: 画布中文本自动折行的演示