JavaScript星星连线技巧

323 阅读5分钟

我正在参与掘金创作者训练营第4期,点击了解活动详情,一起学习吧!

效果图 1111.png

一个星星的实现

对视口进行处理: 获取视口是为了当canvas进行绘制的时候,可以在整个视口中进行操作。然后我们就是需要生成星星,为了更加美观好看,我们需要让星星在canvas操作区域中去随机生成星星的位置。当星星生成后,需要控制星星在x轴和y轴方向上按照一定的速度去运行,这样看起来更加有动画效果。最后就是将星星填充到canvas中。

// 获取视口的宽
var width = document.documentElement.clientWidth;
// 获取视口的高
var height = document.documentElement.clientHeight;

获取id为myCanvas的标签,对canvas处理:

// 获取canvas
var canvas = document.getElementById("myCanvas");
// 获取画笔
var ctx = canvas.getContext("2d");
// 赋值canvas的宽
canvas.width = width;
// 赋值canvas的高
canvas.height = height;
// 改变填充色
ctx.fillStyle = "white";

星星x坐标的位置和y坐标的位置进行处理操作:

// 定义星星x值
var x = parseInt(Math.random() * width);
// 定义星星y值
var y = parseInt(Math.random() * height);
// 星星移动速度的x方向
var x_speed = .6; 
// 星星移动速度的y方向
var y_speed = .7; 
// 星星的半径
var r = 2;
// 绘制星星
// 开启路径
ctx.beginPath();
// 绘制圆
ctx.arc(x, y, r, 0, Math.PI * 2);
// 闭合路径
ctx.closePath();
// 填充
ctx.fill();

最后一步设置定时器,在定时器中进行一系列的操作

// 开启定时器
var timer = setInterval(function() {
        // 清屏
        ctx.clearRect(0, 0, width, height);
        // 移动
        x -= x_speed;
        y -= y_speed;
        // 渲染
        // 开启路径
        ctx.beginPath();
        // 绘制圆
        ctx.arc(x, y, r, 0, Math.PI * 2);
        // 闭合路径
        ctx.closePath();
        // 填充
        ctx.fill();
}, 20)

优化为面向对象形式

第一步与上面不变,改变的是我们定义了start类,使用面向对象设计方法,看起来更方便,而且可以把星星的相关操作都放到一起。

// 定义Star类
function Star(ctx, x, y, r) {
    this.ctx = ctx;
    this.x = x;
    this.y = y;
    this.r = r;
    this.x_speed = (parseInt(Math.random() * 3) + 1) * Math.pow(-1, parseInt(Math.random() * 10) + 1);
    this.y_speed = (parseInt(Math.random() * 3) + 1) * Math.pow(-1, parseInt(Math.random() * 10) + 1);
}

然后讲方法写到类上,定义了移动方法,这样可以在代码各个地方很方便去调用。这里触发边缘是,将方向进行反转操作。

// 方法要写在原型上
// 移动方法
Star.prototype.move = function() {
        this.x -= this.x_speed;
        this.y -= this.y_speed;
}
// 转向X方法
Star.prototype.changeX = function() {
        this.x_speed = - this.x_speed;
}
// 转向Y方法
Star.prototype.changeY = function() {
        this.y_speed = - this.y_speed;
}

然后进行渲染方法和初始化,闭合路径使用closePath,定义圆使用arc,填充使用fill方法。

// 渲染方法
Star.prototype.render = function() {
    // 开启路径
    this.ctx.beginPath();
    // 绘制圆
    this.ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
    // 闭合路径
    this.ctx.closePath();
    // 填充
    this.ctx.fill();
}
// 初始化星星对象
var star = new Star(ctx, Math.random() * width, Math.random() * height, 2);
star.render();

最后修改定时器,定时器中使用清屏clearRect方法,以及边界的判断操作。

// 开启定时器
var timer = setInterval(function() {
    // 清屏
    ctx.clearRect(0, 0, width, height);
    // 移动
    star.move();
    // 判断边界
    if (star.x < 0 || star.x > width) {
            star.changeX();
    }

    if (star.y < 0 || star.y > height) {
            star.changeY();
    }
    // 渲染
    star.render();
}, 20)

浏览器显示结果 image.png

多个星星处理

我们在面向对象的基础上修改start类的定义

	// 定义Star类
function Star(ctx, x, y, r) {
    this.ctx = ctx;
    this.x = x;
    this.y = y;
    this.r = r;
    this.x_speed = (parseInt(Math.random() * 3) + 1) * Math.pow(-1, parseInt(Math.random() * 10) + 1) / 5;
    this.y_speed = (parseInt(Math.random() * 3) + 1) * Math.pow(-1, parseInt(Math.random() * 10) + 1) / 5;
}

多个星星需要我们定义一个数组进行存储,这里的100是刚打开页面,就会随机产生的数量,不至于刚打开页面看起来空白的感觉。如果不使用Math.randow,可能星星都出现在一起,看起来不自然。

// 定义数组 用于存放每一个星星对象
var arr = [];
// 初始化星星对象
for (var i = 0; i < 100; i++) {
        arr.push(new Star(ctx, Math.random() * width, Math.random() * height, 1));
}

然后在定时器中使用数组循环,对每个星星的进行边界值判断,最后render进行渲染。

// 开启定时器
var timer = setInterval(function() {
    // 清屏
    ctx.clearRect(0, 0, width, height);
    arr.forEach(function(value, index) {
        // 移动
        value.move();
        // 判断边界
        if (value.x < 0 || value.x > width) {
                value.changeX();
        }
        if (value.y < 0 || value.y > height) {
                value.changeY();
        }
        // 渲染
        value.render();
    })
}, 20)

效果图如下

image.png

星星连线

定义Star类,分别来表示x位置,y位置,范围和运行的速度。这里的速度自己可以控制,这里使用parseInt进行取整,以及使用Mathrandom方法进行随机取值,这样更贴切实际。

function Star(ctx, x, y, r) {
    this.ctx = ctx;
    this.x = x;
    this.y = y;
    this.r = r;
    this.x_speed = (parseInt(Math.random() * 3) + 1) * Math.pow(-1, parseInt(Math.random() * 10) + 1) / 5;
    this.y_speed = (parseInt(Math.random() * 3) + 1) * Math.pow(-1, parseInt(Math.random() * 10) + 1) / 5;
}

这次我们需要对鼠标操作进行处理,在鼠标移动时,我们需要使用clientX去确定鼠标的位置便于对星星进行处理。将获取的位置赋值给移动的星星,这样鼠标移动到哪里就会于附近的星星进行连线操作。

// 创建鼠标星星对象
var mouse_star = new Star(ctx, 0, 0, 2);
document.onmousemove = function(e) {
    // 获取鼠标的位置
    var x = e.clientX;
    var y = e.clientY;
    // 赋值mouse_star对象中的x 和 y值
    mouse_star.x = x;
    mouse_star.y = y;
}

定时器中需要增加鼠标操作以及星星之间的关系处理,我们需要在星星运动后,对每个星星进行边界处理,这样当星星触到边界,就会进行反向运行。当星星与星星之间的距离在某个范围内时,然后将星星之间进行连线操作,这样看起来更有动画效果,更好看。我们需要注意的是当判断星星x和y范围时,当前星星与周围的星星和星星之前与星星之间的位置判断,都需要使用Math.abs进行处理。

// 开启定时器
var timer = setInterval(function() {
    // 清屏
    ctx.clearRect(0, 0, width, height);
    // 渲染星星对象的方法
    mouse_star.render();
    arr.forEach(function(value, index) {
        // 移动
        value.move();
        // 判断边界
        if (value.x < 0 || value.x > width) {
                value.changeX();
        }

        if (value.y < 0 || value.y > height) {
                value.changeY();
        }
        // 渲染
        value.render();
    });

    // 循环判断
    arr.forEach(function(value, index) {
        // value表示每一个星星,我们应该拿着这个星星与其它所有星星作比较
        for (var i = index + 1; i < arr.length; i++) {
            if (Math.abs(value.x - arr[i].x) < 50 && Math.abs(value.y - arr[i].y) < 50) {
                // 连线
                line(value.x, value.y, arr[i].x, arr[i].y);
            }
        }

        // 判断星星与其它所有星星之间的关系
        if (Math.abs(value.x - mouse_star.x) < 150 && Math.abs(value.y - mouse_star.y) < 150) {
            // 连线
            line(value.x, value.y, mouse_star.x, mouse_star.y);
        }
    })
}, 20);

然后我们需要添加点击事件,会不断生成星星,将生成的星星通过push方法,统一放到数组中。然后生成的星星就会慢慢移动,最后断开连线,移动到各个地方。这里需要注意的是点击鼠标点击事件是对document进行操作的。

// 给document添加点击事件
// 当点击的时候出现多个星星
document.onmousedown = function(e) {
    for (var i = 0; i < 5; i++) {
        arr.push(new Star(ctx, e.clientX, e.clientY, 1));
        arr.shift();
    }
}

最后进行封装,封装line这个函数,方便在各个地方去调用,这样就省去了重复的代码,便于维护起来。其中canvas中一些常用的操作moveTolineToclosePathstroke

// 封装函数,传递两个点,绘制两个点之间的线段
function line(x1, y1, x2, y2) {
    // 开启路径
    ctx.beginPath();
    // 移动画笔到某个位置
    ctx.moveTo(x1, y1);
    // 绘制路径
    ctx.lineTo(x2, y2);
    // 关闭路径
    ctx.closePath();
    // 描边
    ctx.stroke();
}

完成代码,只需要新建一个html文件,复制全部代码粘贴就行,需要注意背景图片自己可以换一个

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style type="text/css">
        * {
            margin: 0;
            padding: 0;
        }
        body, html {
            width: 100%;
            height: 100%;
            overflow: hidden;
        }
        canvas {
            background-image: url(imgs/sf.jpg);
        }
    </style>
</head>
<body>
    <canvas id="myCanvas"></canvas>
    <script type="text/javascript">
    // 获取视口的宽
    var width = document.documentElement.clientWidth;
    // 获取视口的高
    var height = document.documentElement.clientHeight;
    // 获取canvas
    var canvas = document.getElementById("myCanvas");
    // 获取画笔
    var ctx = canvas.getContext("2d");
    // 赋值canvas的宽
    canvas.width = width;
    // 赋值canvas的高
    canvas.height = height;
    // 改变填充色
    ctx.fillStyle = "white";
    // 改变线条颜色
    ctx.strokeStyle = "rgba(255, 255, 123, .4)";
    // 改变线宽
    ctx.lineWidth = ".3";
    // 定义Star类
    function Star(ctx, x, y, r) {
        this.ctx = ctx;
        this.x = x;
        this.y = y;
        this.r = r;
        this.x_speed = (parseInt(Math.random() * 3) + 1) * Math.pow(-1, parseInt(Math.random() * 10) + 1) / 5;
        this.y_speed = (parseInt(Math.random() * 3) + 1) * Math.pow(-1, parseInt(Math.random() * 10) + 1) / 5;
    }

    // 方法要写在原型上
    // 移动方法
    Star.prototype.move = function() {
        this.x -= this.x_speed;
        this.y -= this.y_speed;
    }

    // 转向X方法
    Star.prototype.changeX = function() {
        this.x_speed = - this.x_speed;
    }
    // 转向Y方法
    Star.prototype.changeY = function() {
        this.y_speed = - this.y_speed;
    }

    // 渲染方法
    Star.prototype.render = function() {
        // 开启路径
        this.ctx.beginPath();
        // 绘制圆
        this.ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
        // 闭合路径
        this.ctx.closePath();
        // 填充
        this.ctx.fill();
    }

    // 定义数组 用于存放每一个星星对象
    var arr = [];
    for (var i = 0; i < 100; i++) {
        arr.push(new Star(ctx, Math.random() * width, Math.random() * height, 1));
    }


    // 创建鼠标星星对象
    var mouse_star = new Star(ctx, 0, 0, 2);
    document.onmousemove = function(e) {
        // 获取鼠标的位置
        var x = e.clientX;
        var y = e.clientY;
        // 赋值mouse_star对象中的x 和 y值
        mouse_star.x = x;
        mouse_star.y = y;
    }


// 开启定时器
var timer = setInterval(function() {
    // 清屏
    ctx.clearRect(0, 0, width, height);
    // 渲染星星对象的方法
    mouse_star.render();
    arr.forEach(function(value, index) {
        // 移动
        value.move();
        // 判断边界
        if (value.x < 0 || value.x > width) {
            value.changeX();
        }

        if (value.y < 0 || value.y > height) {
            value.changeY();
        }
        // 渲染
        value.render();
    });

    // 循环判断
    arr.forEach(function(value, index) {
        // value表示每一个星星,我们应该拿着这个星星与其它所有星星作比较
        for (var i = index + 1; i < arr.length; i++) {
            if (Math.abs(value.x - arr[i].x) < 50 && Math.abs(value.y - arr[i].y) < 50) {
                // 连线
                line(value.x, value.y, arr[i].x, arr[i].y);
            }
        }

        // 判断星星与其它所有星星之间的关系
        if (Math.abs(value.x - mouse_star.x) < 150 && Math.abs(value.y - mouse_star.y) < 150) {
            // 连线
            line(value.x, value.y, mouse_star.x, mouse_star.y);
        }
    })
}, 20);

    // 给document添加点击事件
    // 当点击的时候出现多个星星
    document.onmousedown = function(e) {
        for (var i = 0; i < 5; i++) {
            arr.push(new Star(ctx, e.clientX, e.clientY, 1));
            arr.shift();
        }
    }

    // 封装函数,传递两个点,绘制两个点之间的线段
    function line(x1, y1, x2, y2) {
        // 开启路径
        ctx.beginPath();
        // 移动画笔到某个位置
        ctx.moveTo(x1, y1);
        // 绘制路径
        ctx.lineTo(x2, y2);
        // 关闭路径
        ctx.closePath();
        // 描边
        ctx.stroke();
    }
    </script>
</body>
</html>

一开始使用js进行代码书写,后来使用面向对象将很多方法进行封装,简化代码,这样写起来更容易,而且对多个星星进行操作更加方便,代码维护起来也比较方便。思路来源于以前看到的网站背景,觉得挺好玩的,就手动去实现了一个,这样以后也可以作为自己博客的背景动画效果。

每步的案例源代码已上传,需要的可以下载自己看看,做个网站背景来玩,挺好的。点击星星连线