JavaScript Canvas 绘制动态线框效果:从静态折线到动画实现的逻辑优化

作者:竹影时间:2026-10-24 14:20:29浏览:413

本文详解如何利用 JavaScript Canvas 绘制动态线框效果。针对移动端开发中常见的适配与性能问题,文章分析了使用百分比计算节点时因非整数增量导致的绘制缓慢及边框不完整等逻辑弯路。通过引入百分值累加并换算为像素坐标的方案,结合定时器控制增量,实现了高效且灵活的动态线条绘制,提供了完整的代码实现与优化思路。

本文分享了一个使用 JavaScript Canvas 绘制动态线框效果的具体代码案例,供开发者参考。

在本周的一个移动端页面开发项目中,为了满足丰富的动效需求,主要采用了 Canvas 技术。在实现动态线框绘制的过程中,遇到了一些逻辑层面的挑战,这些并非技术实现上的障碍,而是算法逻辑需要调整的地方。

一、涉及的技术点如下:

1. 在 HTML 中引入 canvas 标签,并设置其宽高属性;


<canvas id="canvas" width=xx height=xx>您的浏览器不支持canvas,请更换版本</canvas>

2. 在 JavaScript 中,通过定义 ctx 上下文对象,依次调用 beginPath、moveTo、lineTo、stroke 以及 closePath 方法来构建图形;


var ctx=canvas.getContext('2d');
ctx.beginPath();
ctx.strokeStyle=#f00;
ctx.lineWidth=1;
ctx.moveTo(x1,y1);
ctx.lineTo(x2,y2);
ctx.lineTo(..)..
ctx.stroke();
ctx.closePath();

绘制静态折线相对简单,只需从 moveTo 指定的初始节点开始,通过 lineTo 依次连接各个节点即可。然而,项目需求是实现线条的动画效果,因此实现方式改为:在每一条直线段之间,利用定时器多次重新定位 moveTo 起点,并让 lineTo 指向直线段上按有序增量变化的点位。具体代码如下:


ctx.moveTo(x1,y3);
xm1+=fre1;
ym1+=fre2;
ctx.lineTo(xm1,ym1)

二、逻辑弯路及解决方案

1、遇到的逻辑问题

由于项目面向移动端,需要考虑屏幕适配问题。在绘制节点时,起初尝试使用百分比与获取到的屏幕宽高相乘的结果作为判断 moveTo 是否更改的条件(true 或 false)。然而,由于乘积往往是非整数,在 if 语句中 x 和 y 的增量变化只能使用自增运算符(++)。此外,这种增量是以像素(px)为单位的,导致绘制速度非常缓慢,即使将 setInterval 的时间设置为个位毫秒数,效果依然不佳。


var lg01=ctx.createLinearGradient(0,.12*winH,0,.5*winH);
lg01.addColorStop(0,'#6DEAFF');
lg01.addColorStop(.5,'#78C7FF');
lg01.addColorStop(1,'#4A84FF');
var fre=4;
function drawUpBox(){
    ctx.beginPath();
    ctx.strokeStyle=lg01;
    ctx.lineWidth=0.05*rem;
    if(xm1>x2&&ym1==y1){
        ctx.clearRect(x2,y1-0.025*rem,x3-x2,0.05*rem);
        ctx.moveTo(x3,y1);
        xm1-=fre;
        ctx.lineTo(xm1,ym1)
    }else if(xm1>x1&&ym1<=y2){
        ctx.moveTo(x2,y1);
        xm1-=fre;
        ym1+=fre;
        ctx.lineTo(xm1,ym1)
    }else if(xm1<=x1&&ym1<y3){
        ctx.clearRect(x1-0.025*rem,y2,0.05*rem,y3-y2)
        ctx.moveTo(x1,y2);
        ym1+=fre;
        ctx.lineTo(xm1,ym1)
    } else if(ym1<y4){
        ctx.moveTo(x1,y3);
        xm1+=fre;
        ym1+=fre;
        ctx.lineTo(xm1,ym1)
    }else if(xm1>=x2&&ym1>=y4){

        if(xm1<=winW/2){
            ctx.clearRect(x1,y4-0.025*rem,winH/2-x1,0.05*rem);
            ctx.moveTo(x2,y4)
            xm1+=fre;
            ctx.lineTo(xm1,ym1)
        }
    }
    if(xm2<x5&&ym2==yd1){
        ctx.clearRect(x4,yd1-0.025*rem,x5-x4,0.05*rem);
        ctx.moveTo(x4,yd1);
        xm2+=fre;
        ctx.lineTo(xm2,ym2);
    }else if(xm2<x6&&ym1<=yd2){
        ctx.moveTo(x5,yd1);
        xm2+=fre;
        ym2+=fre;
        ctx.lineTo(xm2,ym2)
    }else if(xm2<=x6&&ym2<yd3){
        ctx.clearRect(x6-0.025*rem,yd2,0.05*rem,yd3-yd2)
        ctx.moveTo(x6,yd2);
        ym2+=fre;
        ctx.lineTo(xm2,ym2)
    }else if(ym2<yd4){
        ctx.moveTo(x6,yd3);
        xm2-=fre;
        ym2+=fre;
        ctx.lineTo(xm2,ym2)
    }else if(xm2<=x5&&ym2>=yd4){

        if(xm2>=winW/2){
            ctx.clearRect(winW/2,yd4-0.025*rem,x6-winW/2,0.05*rem);
            ctx.moveTo(x5,yd4)
            xm2-=fre;
            ctx.lineTo(xm2,ym2)
        }else{
            drawOuterAndInnerLine();
            clearInterval(timer01)
        }
    }
    ctx.stroke();
    ctx.closePath()
}

效果展示:

二、逻辑弯路及解决方案

如果更改 fre 增量的值,例如改为 4,就会出现边框不完整的问题,如图所示:

二、逻辑弯路及解决方案

2. 解决方案:

在判断语句中,将横向分为 100 等分,节点即为 100 以内的整数值,增量也基于此进行累加。在调用 moveTo 和 lineTo 时,再将这些百分值换算为具体的像素坐标。通过百分值处理,可以显著提高单位时间内的绘制效率,此时只需控制增量每次自增(++)。配合定时器的周期设置,很容易实现不同频率的线条绘制。另外,将节点封装在一个对象内,可以快速调整节点以绘制不同尺寸类型的动态线框图:


canvas3.width=winW;
canvas3.height=.15*winH;
//$('#canvas3').css('background','#eee');
var node3X={x1:20,x2:22,x3:36,x4:64,x5:78,x6:80};
var node3Y={y1:2,yh:20};
var xd=node3X.x2-node3X.x1,xml3=node3X.x3,xmr3=node3X.x4,yml3=ymr3= 0;
//var winWB=winW/100,winHB=winH/100,winCHB=winHB/2;
node3Y.y1Ready=node3Y.y1*winCHB;
node3Y.y2Ready=node3Y.y1*winCHB+(node3X.x2-node3X.x1)*winWB;
node3Y.y3Ready=node3Y.y2Ready+node3Y.yh*winCHB;
node3Y.y4Ready=node3Y.y3Ready+(node3X.x2-node3X.x1)*winWB;
var yml3Ready=node3Y.y1Ready;
var ymr3Ready=node3Y.y1Ready;
var ctx3=canvas3.getContext("2d");
var lg03=ctx3.createLinearGradient(0,0,0,canvas3.height);
lg03.addColorStop(0,'#6DEAFF');
lg03.addColorStop(.5,'#78C7FF');
lg03.addColorStop(1,'#4A84FF');
var mainBoxTimer3=setInterval(drawMainBox3,20);
function drawMainBox3(){
    drawPath(ctx3,node3X.x4*winWB,node3Y.y1Ready,4,winWB,lg03)
    drawPath(ctx3,node3X.x3*winWB,node3Y.y1Ready,4,winWB,lg03)
    ctx3.beginPath();
    ctx3.strokeStyle=lg03;
    ctx3.lineWidth=.1*rem;
    //绘制左半部分
    if(xml3>node3X.x2&&yml3==0){
        //ctx3.clearRect(0,0,winW,winH/2);
        xml3--;
        ctx3.moveTo(node3X.x3*winWB,node3Y.y1*winCHB)
        ctx3.lineTo(xml3*winWB,node3Y.y1*winCHB);
    }else if(xml3>node3X.x1&&yml3Ready<node3Y.y2Ready){
        xml3--;
        yml3Ready=node3Y.y1*winCHB+(node3X.x2-xml3)*winWB;
        ctx3.moveTo(node3X.x2*winWB,node3Y.y1*winCHB)
        ctx3.lineTo(xml3*winWB,yml3Ready)
    }else if(xml3==node3X.x1&&yml3<node3Y.yh){
        yml3++;
        ctx3.moveTo(node3X.x1*winWB,node3Y.y2Ready);
        ctx3.lineTo(node3X.x1*winWB,node3Y.y2Ready+yml3*winCHB);
    }else if(yml3==node3Y.yh&&xml3<node3X.x2){
        xml3++;
        ctx3.moveTo(node3X.x1*winWB,node3Y.y3Ready);
        ctx3.lineTo(xml3*winWB,node3Y.y3Ready+(xml3-node3X.x1)*winWB)
    }else if(xml3>=node3X.x2&&xml3<50){
        xml3++;
        ctx3.moveTo(node3X.x2*winWB,node3Y.y4Ready);
        ctx3.lineTo(xml3*winWB,node3Y.y4Ready);
    }
    //绘制右半部分
    if(xmr3<node3X.x5&&ymr3==0){
        xmr3++;
        ctx3.moveTo(node3X.x4*winWB,node3Y.y1*winCHB)
        ctx3.lineTo(xmr3*winWB,node3Y.y1*winCHB);
    }else if(xmr3<node3X.x6&&ymr3Ready<node3Y.y2Ready){
        xmr3++;
        ymr3Ready=node3Y.y1*winCHB+(xmr3-node3X.x5)*winWB;
        ctx3.moveTo(node3X.x5*winWB,node3Y.y1*winCHB)
        ctx3.lineTo(xmr3*winWB,ymr3Ready)
    }else if(xmr3==node3X.x6&&ymr3<node3Y.yh){
        ymr3++;
        ctx3.moveTo(node3X.x6*winWB,node3Y.y2Ready);
        ctx3.lineTo(node3X.x6*winWB,node3Y.y2Ready+ymr3*winCHB);
    }else if(ymr3==node3Y.yh&&xmr3>node3X.x5){
        xmr3--;
        ctx3.moveTo(node3X.x6*winWB,node3Y.y3Ready);
        ctx3.lineTo(xmr3*winWB,node3Y.y3Ready+(node3X.x6-xmr3)*winWB)
    }else if(xmr3<=node3X.x5&&xmr3>50){
        xmr3--;
        ctx3.moveTo(node3X.x5*winWB,node3Y.y4Ready);
        ctx3.lineTo(xmr3*winWB,node3Y.y4Ready);
    }else{
        ctx3.clearRect(0,0,canvas3.width,canvas3.height);
        ctx3.beginPath();
        ctx3.moveTo(node3X.x3*winWB,node3Y.y1Ready);
        ctx3.lineTo(node3X.x2*winWB,node3Y.y1Ready);
        ctx3.lineTo(node3X.x1*winWB,node3Y.y2Ready);
        ctx3.lineTo(node3X.x1*winWB,node3Y.y3Ready);
        ctx3.lineTo(node3X.x2*winWB,node3Y.y4Ready);
        ctx3.lineTo(node3X.x5*winWB,node3Y.y4Ready);
        ctx3.lineTo(node3X.x6*winWB,node3Y.y3Ready);
        ctx3.lineTo(node3X.x6*winWB,node3Y.y2Ready);
        ctx3.lineTo(node3X.x5*winWB,node3Y.y1Ready);
        ctx3.lineTo(node3X.x4*winWB,node3Y.y1Ready);
        clearInterval(mainBoxTimer3);
    }
    ctx3.stroke();
    ctx3.closePath();

}

技术实现是基础,而逻辑优化则是提升代码质量和执行效率的关键。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持本站。

作者最新文章