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


    来自:https://segmentfault.com/a/1190000009675230

    下面开始coding:
    先写个canvas标签

    <canvas height="620" width="1360" id="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
            };
    
            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);
    

    大功告成!!

  • 相关阅读:
    BZOJ2763 [JLOI2011]飞行路线(SPFA + DP)
    HDU5838 Mountain(状压DP + 容斥原理)
    HDU4787 GRE Words Revenge(AC自动机 分块 合并)
    HDU5909 Tree Cutting(树形DP + FWT)
    HDU5456 Matches Puzzle Game(DP)
    SPOJ DQUERY D-query(主席树)
    POJ2104 K-th Number(主席树)
    Tsinsen A1493 城市规划(DP + CDQ分治 + NTT)
    BZOJ3438 小M的作物(最小割)
    BZOJ1565 [NOI2009]植物大战僵尸(拓扑排序 + 最大权闭合子图)
  • 原文地址:https://www.cnblogs.com/colorful-paopao1/p/10562687.html
Copyright © 2020-2023  润新知