前言

最近总是梦见一些小时候的故事,印象最深刻的就是夏天坐在屋顶上,看着满天的繁星,一颗,两颗,三颗...不由自主地开始了数星星的过程。不经意间,一颗流星划过夜间,虽然只是转瞬即逝,但它似乎比夜空中的其它繁星更吸引着我。听老人说,看见流星的时候许愿,愿望是可以实现的,此时早已把数星星抛之脑后,开始期待着下一颗流星的出现。但是那天晚上,流星再也没有出现,这也成了自己小时候的一个遗憾。

今天,我决定用canvas为大家带来一场流星雨视觉盛宴。

如果这篇文章有帮助到你,️关注+点赞️鼓励一下作者,文章公众号首发,关注 前端南玖 第一时间获取最新文章~

需求分析

首先我们需要的元素有:夜空、满天繁星、流星雨。

满天繁星: 这个其实就是画上一个个点,然后不断的通过颜色交替,营造出一种星星闪烁的意境。

流星雨: 流星处于他自己的运动轨迹之中,当前的位置最亮,轮廓最清晰,而之前划过的地方离当前位置轨迹距离越远就越暗淡越模糊,其实它就是一个渐变的过程,恰巧canvas有方法可以创建一个沿参数坐标指定的直线的渐变。然后让它从右上向左下移动,这样就能营造一种流星雨的效果,同时实现动画的循环。

OK,需求分析结束,准备动手开干~

实现过程

1.绘制满天繁星

//创建一个星星对象
class Star {
constructor() {
this.x = windowWidth * Math.random(); //横坐标
this.y = 5000 * Math.random(); //纵坐标
this.text = "."; //文本
this.color = "white"; //颜色
}
//初始化
init() {
this.getColor();
}
//绘制
draw() {
context.fillStyle = this.color;
context.fillText(this.text, this.x, this.y);
}
} //画星星
for (let i = 0; i < starCount; i++) {
let star = new Star();
star.init();
star.draw();
arr.push(star);
}

来看下此时的效果:

夜空中的满天繁星现在是有了,但是缺乏一点意境,我们得想办法让这些繁星都闪烁起来。

2.满天繁星闪起来

//创建一个星星对象
class Star {
constructor() {
this.x = windowWidth * Math.random(); //横坐标
this.y = 5000 * Math.random(); //纵坐标
this.text = "."; //文本
this.color = "white"; //颜色
}
// 获取随机颜色
getColor() {
let _r = Math.random();
if (_r < 0.5) {
this.color = "#333";
} else {
this.color = "white";
}
} //初始化
init() {
this.getColor();
}
//绘制
draw() {
context.fillStyle = this.color;
context.fillText(this.text, this.x, this.y);
}
} //画星星
for (let i = 0; i < starCount; i++) {
let star = new Star();
star.init();
star.draw();
arr.push(star);
} //繁星闪起来
let t1
function playStars() {
for (let n = 0; n < starCount; n++) {
arr[n].getColor();
arr[n].draw();
}
t1 = requestAnimationFrame(playStars);
}

繁星闪烁的元素就在于这个getColor方法,通过不断地切换星星的颜色,来达到星星闪烁的效果。

再来看看这时的效果:

此刻的自己就可以开始数星星了,一颗,两颗,三颗...

3.绘制流星

简单点理解,流星其实就是一条渐变的线段,当前的位置最亮,轮廓最清晰,而之前划过的地方离当前位置轨迹距离越远就越暗淡越模糊。

这里的关键API是createLinearGradient,用于创建一个沿参数坐标指定的直线的渐变。

语法:

CanvasGradient ctx.createLinearGradient(x0, y0, x1, y1);

  • x0:起点的 x 轴坐标。
  • y0:起点的 y 轴坐标。
  • x1:终点的 x 轴坐标。
  • y1:终点的 y 轴坐标。

使用createLinearGradient() 方法初始化一个线性渐变。在这个线性渐变中添加三种颜色,达到一种渐变的效果来模拟出流星划过夜空的状态。

/**绘制流星**/
draw() {
//绘制一个流星的函数
context.save();
context.beginPath();
context.lineWidth = 1; //宽度
context.globalAlpha = this.alpha; //设置透明度
//创建横向渐变颜色,起点坐标至终点坐标
let line = context.createLinearGradient(
this.x,
this.y,
this.x + this.width,
this.y - this.height
);
//分段设置颜色
line.addColorStop(0, "white");
line.addColorStop(0.3, this.color1);
line.addColorStop(0.6, this.color2);
context.strokeStyle = line;
//起点
context.moveTo(this.x, this.y);
//终点
context.lineTo(this.x + this.width, this.y - this.height);
context.closePath();
context.stroke();
context.restore();
}

现在我们来看一看当年的那个流星:

4.流星划过夜空

流星有了,现在我们得想办法让它动起来。这里其实就是通过不断地计算位置来达到流星动起来的效果。

move() {
//清空流星像素
let x = this.x + this.width - this.offset_x;
let y = this.y - this.height;
context.clearRect(x - 3, y - 3, this.offset_x + 5, this.offset_y + 5);
//重新计算位置,往左下移动
this.countPos();
//透明度增加
this.alpha -= 0.002;
//重绘
this.draw();
}

现在,我们就可以看到当年的那颗流星了,是不是很激动。稍安勿躁,为了弥补当年的遗憾,这里决定来一场从未真实见过的流星雨。

5.流星雨

写到这里,实现流星雨其实就很简单了,我们只需要再多生成一些流星,为它们各自分配不同的坐标即可。

let t2
// 创建流星雨对象
class MeteorRain {
constructor() {
this.x = -1;
this.y = -1;
this.length = -1; //长度
this.angle = 30; //倾斜角度
this.width = -1; //宽度
this.height = -1; //高度
this.speed = 1; //速度
this.offset_x = -1; //横轴移动偏移量
this.offset_y = -1; //纵轴移动偏移量
this.alpha = 1; //透明度
this.color1 = ""; //流星的色彩
this.color2 = ""; //流星的色彩
}
init() {
//初始化
this.getPos();
this.alpha = 1; //透明度
this.getRandomColor();
//最小长度,最大长度
let x = Math.random() * 80 + 150;
this.length = Math.ceil(x);
x = Math.random() + 0.5;
this.speed = Math.ceil(x); //流星的速度
let cos = Math.cos((this.angle * 3.14) / 180);
let sin = Math.sin((this.angle * 3.14) / 180);
this.width = this.length * cos;
this.height = this.length * sin;
this.offset_x = this.speed * cos;
this.offset_y = this.speed * sin;
}
/**获取随机颜色函数**/
getRandomColor() {
let a = Math.ceil(255 - 240 * Math.random());
//中段颜色
this.color1 = "rgba(" + a + "," + a + "," + a + ",1)";
//结束颜色
this.color2 = "black";
}
/**重新计算流星坐标的函数**/
countPos() {
//
//往左下移动,x减少,y增加
this.x = this.x - this.offset_x;
this.y = this.y + this.offset_y;
}
/**获取随机坐标的函数**/
getPos() {
//
//横坐标
this.x = Math.random() * window.innerWidth; //窗口高度
//纵坐标
this.y = Math.random() * window.innerHeight; //窗口宽度
}
/**绘制流星**/
draw() {
//绘制一个流星的函数
context.save();
context.beginPath();
context.lineWidth = 1; //宽度
context.globalAlpha = this.alpha; //设置透明度
//创建横向渐变颜色,起点坐标至终点坐标
let line = context.createLinearGradient(
this.x,
this.y,
this.x + this.width,
this.y - this.height
);
//分段设置颜色
line.addColorStop(0, "white");
line.addColorStop(0.3, this.color1);
line.addColorStop(0.6, this.color2);
context.strokeStyle = line;
//起点
context.moveTo(this.x, this.y);
//终点
context.lineTo(this.x + this.width, this.y - this.height);
context.closePath();
context.stroke();
context.restore();
}
move() {
//清空流星像素
let x = this.x + this.width - this.offset_x;
let y = this.y - this.height;
context.clearRect(x - 3, y - 3, this.offset_x + 5, this.offset_y + 5);
//重新计算位置,往左下移动
this.countPos();
//透明度增加
this.alpha -= 0.002;
//重绘
this.draw();
}
} //绘制流星
function playRains() {
for (let n = 0; n < rainCount; n++) {
// console.log(rains, "--");
let rain = rains[n];
rain.move(); //移动
if (rain.y > window.innerHeight) {
//超出界限后重来
context.clearRect(rain.x, rain.y - rain.height, rain.width, rain.height);
rains[n] = new MeteorRain();
rains[n].init();
}
}
t2 = requestAnimationFrame(playRains);
}

6.merge视觉盛宴

流星极短暂的星星是也,它不像恒星和行星那般耀眼,却用短暂的生命,划破夜空,用瞬间潇洒的弧线留住美丽的光辉。昙花和流星瞬间之美令我无法忘怀,大自然神奇的造物者给了我们许多的美,不论瞬间的还是永恒的,我们都要用真心去欣赏去品味。

通过合并前面五个步骤,我们就能够一睹这流星刹那间的交错,而后瞬间就穿透为永恒,只是一刻用生命幻化的美。

最后

今天的视觉盛宴就到这里了,想要查看源码的同学公众号回复流星雨

原文首发地址点这里,欢迎大家关注公众号 「前端南玖」,如果你想进前端交流群一起学习,请点这里

我是南玖,我们下期见!!!

最新文章

  1. [转]SVN客户端解决authorization failed问题
  2. 系统默认Select框 知多少
  3. 自定义Template,向其中添加新的panel
  4. Netbackup备份失败:ORA-19506 ORA-27028 ORA-19511
  5. C#中鼠标划过按钮时候的提示信息
  6. 老李分享:JDK,JRE,JVM区别与联系
  7. SDN学习之OpenFlow协议分析
  8. Ext常用开发基础知识
  9. SASS笔记
  10. python基础—函数装饰器
  11. mysql 与 oracle 的连表update
  12. 机器学习之--kmeans聚类简单算法实例
  13. Qt Designer问题(挖坑)
  14. PHP函数memory_get_usage获取PHP内存清耗量
  15. 运用visual studio进行简单的单元测试
  16. IRing项目开发
  17. flume handler
  18. 【Linux】WinSCP普通用户登录sftp后切换到root权限
  19. CSU 1102 多连块拼图
  20. 洛谷P1291 [SHOI2002]百事世界杯之旅 [数学期望]

热门文章

  1. 附加进程 到远程服务器中Docker容器内 调试
  2. PKUSC 2022 口胡题解
  3. 线程本地存储 ThreadLocal
  4. 使用three.js(webgl)搭建智慧楼宇、设备检测、数字孪生——第十三课
  5. java学习第七天xml.day18
  6. Excelize 发布 2.6.0 版本,功能强大的 Excel 文档基础库
  7. MySQL设置字段从指定数字自增,比如10000
  8. 基于开源方案构建统一的文件在线预览与office协同编辑平台的架构与实现历程
  9. ViewGroup事件分发源码分析
  10. openstack中Keystone组件简解