canvas绘制经典星空连线效果

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

<canvas height="620" width="1360" id="canvas"></canvas>
复制代码

加上一些默认的样式: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 };dom

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() ,为何?spa

假设咱们以前链接了 A、B两点,也就是外侧循环是A,内侧循环是B,那么在下一次循环中,外侧为B,内侧为A,是否是也会建立一条边呢?而实际上,这两个边除了方向不同之外是彻底同样的,这彻底没有必要并且占用资源。所以咱们在 addEdge 函数中进行一个判断: function addEdge(edge) { var ignore = false;code

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; mousePos1 = e.clientY; }

window.requestAnimationFrame(step);
复制代码

大功告成!!

相关文章
相关标签/搜索