资讯专栏INFORMATION COLUMN

canvas绘制经典星空连线效果

wenzi / 3462人阅读

摘要:点都构建完毕了,就要构建点与点之间的连线了,我们用到双重遍历,把两个点捆绑成一组,放到数组中。最后加入鼠标移动事件,启动定时器大功告成

废话不说先上图:

关于这个效果我第一次见是在
https://www.mengxiaozhu.cn/
后来知乎登录页也开始用了
https://www.zhihu.com/
网络上还有很多地方都在用,效果还是不错的。
我见了之后觉得挺有意思的就研究了一下原理
下面开始coding:
先写个canvas标签

加上一些默认的样式:

*{
    margin:0;
    padding:0;
}
body{
    overflow: hidden;
}

这里的overflow:hidden是为了防止出现滚动条
下面开始写JS:
首先我们要得到那个 canvas 并得到绘制上下文:

var canvasEl = document.getElementById("canvas");
var ctx = canvasEl.getContext("2d");
var mousePos = [0, 0];

紧接着我们声明两个变量,分别用于存储“星星”和边:

var nodes = [];
var edges = [];

然后我们定义一些其他的变量:

var easingFactor = 5.0;  //缓动因子
var backgroundColor = "#000"; //背景颜色
var nodeColor = "#fff"; //点颜色
var edgeColor = "#fff"; //边颜色
var pageWidth = window.innerWidth, //窗口宽度 
    pageHeight = window.innerHeight; //窗口高度

设置画布的大小铺满整个屏幕:

window.onresize = function () {
    canvasEl.width = pageWidth;
    canvasEl.height = pageHeight;

    if (nodes.length == 0) {
        constructNodes();
    }

    render();
};

window.onresize(); 

准备工作完成,我们要开始构建点了:

function constructNodes() {
    for (var i = 0; i < 100; i++) {
        var node = {
            drivenByMouse: i == 0,
            x: Math.random() * canvasEl.width,
            y: Math.random() * canvasEl.height,
            vx: Math.random() * 1 - 0.5,
            vy: Math.random() * 1 - 0.5,
            radius: Math.random() > 0.9 ? 3 + Math.random() * 3 : 1 + Math.random() * 3
        };

        nodes.push(node);
    }

    nodes.forEach(function (e) {
        nodes.forEach(function (e2) {
            if (e == e2) {
                return;
            }

            var edge = {
                from: e,
                to: e2
            }

            addEdge(edge);
        });
    });
}

先创建100个点,每个点设置6个属性,drivenByMouse属性只有第一个点为true,其他的点为false,第一个点作为鼠标跟随点,不显示出来,可以与其他点连线。x,y作为点的初始位置,取得是画布内的随机点,vx,vy表示点的初始速度,范围为-0.5到0.5之间的随机数,radius表示点的半径,大部分的点为小的,少数的点为大的。

点都构建完毕了,就要构建点与点之间的连线了,我们用到双重遍历,把两个点捆绑成一组,放到 edges 数组中。注意这里我用了另外一个函数来完成这件事,而没有直接用 edges.push() ,为什么?

假设我们之前连接了 A、B两点,也就是外侧循环是A,内侧循环是B,那么在下一次循环中,外侧为B,内侧为A,是不是也会创建一条边呢?而实际上,这两个边除了方向不一样以外是完全一样的,这完全没有必要而且占用资源。因此我们在 addEdge 函数中进行一个判断:

function addEdge(edge) {
    var ignore = false;

    edges.forEach(function (e) {
        if (e.from == edge.from & e.to == edge.to) {
            ignore = true;
        }

        if (e.to == edge.from & e.from == edge.to) {
            ignore = true;
        }
    });

    if (!ignore) {
        edges.push(edge);
    }
}

至此,我们的准备工作就完毕了,下面我们要让点动起来:

function step() {
    nodes.forEach(function (e) {
        if (e.drivenByMouse) {
            return;
        }

        e.x += e.vx;
        e.y += e.vy;

        function clamp(min, max, value) {
            if (value > max) {
                return max;
            } else if (value < min) {
                return min;
            } else {
                return value;
            }
        }

        if (e.x <= 0 || e.x >= canvasEl.width) {
            e.vx *= -1;
            e.x = clamp(0, canvasEl.width, e.x)
        }

        if (e.y <= 0 || e.y >= canvasEl.height) {
            e.vy *= -1;
            e.y = clamp(0, canvasEl.height, e.y)
        }
    });

    adjustNodeDrivenByMouse();
    render();
    window.requestAnimationFrame(step);
}

function adjustNodeDrivenByMouse() {
    nodes[0].x += (mousePos[0] - nodes[0].x) / easingFactor;
    nodes[0].y += (mousePos[1] - nodes[0].y) / easingFactor;
}

这段代码就是遍历粒子,并且更新其状态。根据一个简单的物理公式 s = s + v,每次执行都会 更新到点的下一步的状态。
adjustNodeDrivenByMouse函将第一个点作为鼠标的跟随点,easingFactor为缓动因子可以让点的运动比鼠标运动的稍慢一点。
然后我们要让整个粒子系统连续地运转起来就需要一个timer了,但是十分不提倡大家使用 setInterval,而是尽可能使用 requestAnimationFrame,它能保证你的帧率锁定在当前浏览器的频率下,一般为60HZ。

剩下的就是绘制了

function render() {
    ctx.fillStyle = backgroundColor;
    ctx.fillRect(0, 0, canvasEl.width, canvasEl.height);

    edges.forEach(function (e) {
        var l = lengthOfEdge(e);
        var threshold = canvasEl.width / 8;

        if (l > threshold) {
            return;
        }

        ctx.strokeStyle = edgeColor;
        ctx.lineWidth = (1.0 - l / threshold) * 2.5;
        ctx.globalAlpha = 1.0 - l / threshold;
        ctx.beginPath();
        ctx.moveTo(e.from.x, e.from.y);
        ctx.lineTo(e.to.x, e.to.y);
        ctx.stroke();
    });
    ctx.globalAlpha = 1.0;

    nodes.forEach(function (e) {
        if (e.drivenByMouse) {
            return;
        }

        ctx.fillStyle = nodeColor;
        ctx.beginPath();
        ctx.arc(e.x, e.y, e.radius, 0, 2 * Math.PI);
        ctx.fill();
    });
}
function lengthOfEdge(edge) {
    return Math.sqrt(Math.pow((edge.from.x - edge.to.x), 2) + Math.pow((edge.from.y - edge.to.y), 2));
}

绘制的时候我们要判断线的长度如果大于某一个值,则不绘制该线了,如果在范围之内粗细,与颜色的透明度都与线的长度相关,点除了第一个鼠标跟随点,其他的画入即可。
最后加入鼠标移动事件,启动定时器:

window.onmousemove = function (e) {
    mousePos[0] = e.clientX;
    mousePos[1] = e.clientY;
}

window.requestAnimationFrame(step);

大功告成!!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/112079.html

相关文章

  • canvas绘制经典星空连线效果

    摘要:点都构建完毕了,就要构建点与点之间的连线了,我们用到双重遍历,把两个点捆绑成一组,放到数组中。最后加入鼠标移动事件,启动定时器大功告成 废话不说先上图:showImg(https://segmentfault.com/img/bVOH83?w=1312&h=586); 关于这个效果我第一次见是在https://www.mengxiaozhu.cn/后来知乎登录页也开始用了https:/...

    hzc 评论0 收藏0
  • Canvas 点线动画案例

    摘要:运动坐标变量坐标变量绘制方法画布渲染清除画布位置变化绘制继续渲染动起来的多点多线动的是点,画的是线给对象添加运动变量和两个值表示点在轴和轴的运动量此处为在之间运动。 Canvas 点线动画案例 画圆: arc(x, y, r, start, stop) 画线: moveTo(x, y) 定义线条开始坐标lineTo(x, y) 定义线条结束坐标 填充: fill() 绘制: stro...

    mykurisu 评论0 收藏0
  • 一步步实现nest粒子特效

    摘要:尝试实现画出一个弹射的小球很简单,那怎么用多个小球实现这样的效果呢。 本文首发于我的博客,这是我的github,欢迎star。   这篇博客是模仿nest.js实现一个demo,由简单到复杂,来一步步的实现它。这里是效果预览。我的github里边还有很多别的前端的demo,喜欢的话可以点个star,你的支持就是我的动力。 从一道面试题开始 实现一个半径10px的小球在500px*5...

    ky0ncheng 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<