SOURCE

console 命令行工具 X clear

                    
>
console
let green1 = "#DBFBF0";
let green2 = "#AAE5D3";
let green3 = "#68d2b0";
let green4 = "#E9FCF5";

let duration = 100;

let linkRadius = 300;

let grayLinkWidth = 3;

let bubbleNumber = 10;

let lineDropRate = 0.5;

let radiusMin = 5;

let radiusMax = 15;

let multiplier = 1.5;

let screenWidth = window.screen.width;
let screenHeight = window.screen.height;

function getRandom(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

let nodes = d3.range(bubbleNumber).map((d, i) => ({
    cx: getRandom(0, screenWidth),
    cy: getRandom(0, screenHeight),
    vx: getRandom( - 100, 100) / 25,
    vy: getRandom( - 100, 100) / 25,
    r: getRandom(multiplier * radiusMin, multiplier * radiusMax),
    id: i
}));

let svg = d3.select("svg")
    .style('width', screenWidth)
    .style('height', screenHeight)
    .style('margin-left', -screenWidth / 2)
    .style('margin-top', -screenHeight / 2)

let svg_nodes = svg.append('g').selectAll('circle')
    .data(nodes)
    .enter()
    .append('circle')
    .attr('cx', d => d.cx)
    .attr('cy', d => d.cy)
    .attr('r', d => d.r)
    .attr('fill', '#F3F3F3');

let svg_lines = svg.append('g');

let ticked = () => {
    let innerWidth = window.innerWidth;
    let innerHeight = window.innerHeight;
    svg_nodes.transition().duration(0)
        .attr('cx', d => {
        if (d.cx + d.r < ((screenWidth - innerWidth) / 2)) {
            d.cx += innerWidth + 2 * d.r;
        }
        if (d.cx - d.r > ((screenWidth + innerWidth) / 2)) {
            d.cx -= innerWidth + 2 * d.r;
        }
        return d.cx;
    })
        .attr('cy', d => {
        if (d.cy + d.r < ((screenHeight - innerHeight) / 2)) {
            d.cy += innerHeight + 2 * d.r;
        }
        if (d.cy - d.r > ((screenHeight + innerHeight) / 2)) {
            d.cy -= innerHeight + 2 * d.r;
        }
        return d.cy;
    });
    svg_nodes.transition().duration(duration).ease(d3.easeLinear)
        .attr('cx', d => (d.cx += d.vx))
        .attr('cy', d => (d.cy += d.vy));

    let links = [];

    for (let i = 0; i < nodes.length - 1; i++) {
        for (let j = i + 1; j < nodes.length; j++) {
            if (((nodes[i].cx - nodes[j].cx) * (nodes[i].cx - nodes[j].cx) + (nodes[i].cy - nodes[j].cy) * (nodes[i].cy - nodes[j].cy)) < linkRadius * linkRadius && (nodes[i].cx - nodes[i].r) > (screenWidth - innerWidth) / 2 && (nodes[i].cx + nodes[i].r) < (screenWidth + innerWidth) / 2 && (nodes[i].cy - nodes[i].r) > (screenHeight - innerHeight) / 2 && (nodes[i].cy + nodes[i].r) < (screenHeight + innerHeight) / 2 && (nodes[j].cx - nodes[j].r) > (screenWidth - innerWidth) / 2 && (nodes[j].cx + nodes[j].r) < (screenWidth + innerWidth) / 2 && (nodes[j].cy - nodes[j].r) > (screenHeight - innerHeight) / 2 && (nodes[j].cy + nodes[j].r) < (screenHeight + innerHeight) / 2) {
                links.push({
                    source: i,
                    target: j
                });
            }
        }
    }

    svg_lines.selectAll('line[ends]')
        .data(links, function(d) {
        return d ? (d.source + '_' + d.target) : (ends)
    })
        .transition().duration(duration).ease(d3.easeLinear)
        .attr('x1', d => (nodes[d.source].cx))
        .attr('y1', d => (nodes[d.source].cy))
        .attr('x2', d => (nodes[d.target].cx))
        .attr('y2', d => (nodes[d.target].cy))
        .style('stroke-width', d => (d.source + d.target) % (1 / lineDropRate) < 1 ? "0": "" + grayLinkWidth);

    svg_lines.selectAll('line[ends]')
        .data(links, function(d) {
        return d ? (d.source + '_' + d.target) : (ends)
    })
        .enter()
        .append('line')
        .attr('ends', d => (d.source + '_' + d.target))
        .attr('x1', d => (nodes[d.source].cx))
        .attr('y1', d => (nodes[d.source].cy))
        .attr('x2', d => (nodes[d.target].cx))
        .attr('y2', d => (nodes[d.target].cy))
        .style('stroke', '#F3F3F3')
        .style('stroke-width', '0')
        .transition().duration(duration).ease(d3.easeLinear)
        .style('stroke-width', d => (d.source + d.target) % (1 / lineDropRate) < 1 ? "0": "" + grayLinkWidth);

    svg_lines.selectAll('line[ends]')
        .data(links, function(d) {
        return d ? (d.source + '_' + d.target) : (ends)
    })
        .exit()
        .attr('ends', null)
        .remove();
}

ticked();
setInterval(ticked, duration);
<svg>
</svg>
body {
    background: black;
}

svg {
    position: fixed;
    left: 50%;
    right: 50%;
    top: 50%;
    bottom: 50%;
}

line {}

本项目引用的自定义外部资源