点击上方 前端瓶子君,关注公众号
回复算法,加入前端编程面试算法每日一题群
来源:北极光之夜
https://juejin.cn/post/6958698023727661092
一.先看效果(源码在最后):
我的B站地址~效果演示更清晰
图片展示,因为图片限制5m大小,所以演示不太多:
二.实现过程(可一步一步实现):
因为雨是重点,所以中间 logo 部分就不详细写了,可直接看源码~
1.定义canvas标签与设置css基本样式:
<canvas id="canvas"></canvas>
复制代码
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
#canvas{
position: fixed;
background-color: rgb(0, 0, 0);
z-index: -10;
}
复制代码
position: fixed; 固定定位。
2. 开始js部分,获取画布与定义基本变量:
// 获取画布
var canvas = document.querySelector("#canvas");
var rain = canvas.getContext('2d');
// drop数组,存每个散开的小水滴信息
var drop = [];
// water数组,存每丝雨的信息
var water = [];
// 雨的数量,可自行更改
var waterNum = 100;
// 小水(雨)滴的重力
var gravity = 1;
// 鼠标在页面的初始位置
var mouseX=-36,mouseY=-36;
// 关于雨的角度值,值为-1到1,后面讲
var direction = 0;
// 这也是关于鼠标在页面位置的角度值,值为-1到1
var mouseDelay = 0;
// 这是画布自适应窗口大小的函数,复制即可
window.onresize = resizeCanvas;
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
复制代码
2.初始化雨:
// 一丝雨的初始化,封装,后面好几处要调用
function creatWater(){
water.push({
//值为0或1,判断是否要散开水滴
add:1,
//随机初始水平位置
x:Math.random()*3*canvas.width-canvas.width,
// 随机初始垂直位置,在上面一点,这样雨能从上面下落
y: Math.random()*500-500,
// 随机雨的长度
len: Math.random()*20+50,
// 随机雨的速度
speed: Math.random()*10+35,
// 随机雨的随机颜色
color: `rgb(255,255,255,${Math.random()*0.5})`,
// 随机散开水滴数量
dropNum:Math.random()*6+6
})
}
// 雨数组初始化,每丝雨都来
function chushi(){
for (let i = 0; i < waterNum; i++) {
creatWater();
}
}
复制代码
3.初始化雨滴:
// 散开水滴数组初始化,x为水平位置,y为垂直位置,dropNum为数量
function creatDrop (x,y,dropNum){
//给drop数组添加元素
for (let j = 0; j < dropNum; j++) {
drop.push({
// x轴位置
pagex:x,
// y轴位置
pagey:y,
// x轴移动距离
dx:Math.random()*12-6,
// y轴移动距离
dy:Math.random()*10-20,
// 半径
r:Math.random()+2,
//颜色
color: `rgb(255,255,255,${Math.random()*0.5+0.5})`,
})
}
}
复制代码
4.绘画每一丝雨:
// 绘画,画雨
function drawWater(){
//遍历数组
for (let i = 0; i < water.length; i++){
let temp = water[i];
// 颜色
rain.strokeStyle = temp.color;
// 开始路径
rain.beginPath();
// 开始点
rain.moveTo(temp.x,temp.y);
// 结束点,连线,如: 当前x位置+长度*角度值
rain.lineTo(temp.x+temp.len*direction,temp.y+temp.len);
// 线宽度
rain.lineWidth = 3;
// 绘制
rain.stroke();
}
}
复制代码
5.绘画每一个雨滴:
// 绘画雨滴
function drawDrop(){
//遍历
for (let i = 0; i < drop.length; i++){
let temp = drop[i];
// 线宽度
rain.lineWidth = 2;
//颜色
rain.strokeStyle = temp.color;
//开始路径
rain.beginPath();
// 画一个随机的弧度
rain.arc(temp.pagex,temp.pagey,temp.r, Math.PI , Math.random() * 2 * Math.PI);
// 绘制
rain.stroke();
//结束路径
rain.closePath();
}
}
复制代码
5. 雨信息更新,同时判断各种事件:
//雨信息的更新
function updateWater(){
for (let i = 0; i < water.length; i++){
// 判断雨的底部是否碰到鼠标,碰到就散开成水滴,x轴y轴与鼠标的位置绝对值在35之内则散开。
if(Math.abs(mouseX-water[i].x)<35&&Math.abs(mouseY-water[i].y-water[i].len)<35){
// 创建雨滴,传入x轴y轴大小与数量
creatDrop(water[i].x,water[i].y+water[i].len,water[i].dropNum);
// 既然水滴散开了,就清除掉这丝雨
water.splice(i,1);
// 重新来一丝随机的雨
creatWater();
}
// 判断雨的底部是否超过画布底部,且add值为1
if(((water[i].y+water[i].len)>=canvas.height) && water[i].add==1){
// add值为0
water[i].add = 0;
// 创建雨滴,传入x轴y轴大小与数量,y轴位置就为画布高即可
creatDrop(water[i].x,canvas.height,water[i].dropNum);
}
// 判断整丝雨是否超过画布
if(water[i].y>canvas.height){
// 清除它
water.splice(i,1);
// 来个新的
creatWater();
}
// 缓动动画原理,雨角度慢慢接近鼠标的角度
direction += (mouseDelay - direction)*0.0002;
// 雨x轴位置改变
water[i].x += water[i].speed*direction;
//雨y轴位置改变
water[i].y += water[i].speed;
}
}
复制代码
6. 雨滴信息更新:
// 雨滴信息更新
function updateDrop(){
for(let i=0;i<drop.length;i++){
// y轴移动距离加上重力。因为dy一开是负数,所以雨滴先升后降
drop[i].dy += gravity;
// x轴位置改变,同时它也受雨角度影响
drop[i].pagex += drop[i].dx + direction*10;
// y轴位置改变
drop[i].pagey += drop[i].dy;
//判断雨滴是否超过画布
if(drop[i].pagey>canvas.height){
//清除水滴
drop.splice(i,1);
}
}
}
复制代码
7.绑定鼠标事件:
// 绑定鼠标移动事件
window.addEventListener('mousemove',e=>{
// 得到x轴位置
mouseX = e.clientX;
//得到y轴位置
mouseY = e.clientY;
// 雨角度值,在-1到1之间
mouseDelay = (e.clientX-canvas.offsetWidth/2)/(canvas.offsetWidth/2);
})
// 判断鼠标离开事件
window.addEventListener('mouseout',()=>{
// 给个值,不会产生雨滴的值就行
mouseY=canvas.height+40;
})
复制代码
8.设置定时器,开始下雨动画:
// 先初始化雨数组
chushi();
//设置定时器,开始动画
setInterval(function(){
// 清除画布
rain.clearRect(0,0,canvas.width,canvas.height);
// 更新雨和雨滴信息
updateWater();
updateDrop();
// 绘画雨和雨滴
drawWater();
drawDrop();
},20)
复制代码
三.完整代码:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>北极光之夜。</title>
<style>
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
#canvas{
position: fixed;
background-color: rgb(0, 0, 0);
z-index: -10;
}
svg{
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
.txt{
font-family: 'fangsong';
font-weight: 900;
font-size: 80px;
letter-spacing: 3px;
fill: transparent;
stroke: rgb(30, 134, 252);
stroke-width: 1.5px;
stroke-dasharray: 625;
stroke-dashoffset: 625;
animation: draw 2s linear infinite;
text-shadow: 0 0 10px rgb(30, 134, 252),
0 0 20px rgb(30, 134, 252),
0 0 40px rgb(30, 134, 252),
0 0 60px rgb(30, 134, 252);
}
@keyframes draw{
0%,100%{
stroke-dasharray: 625;
stroke-dashoffset: 625;
}
45%,55%{
stroke-dasharray: 652;
stroke-dashoffset: 0;
}
}
</style>
</head>
<body>
<svg width="500" height="200">
<text x="30" y="120" class="txt">北极光之夜。</text>
</svg>
<canvas id="canvas"></canvas>
<script>
// 获取画布
var canvas = document.querySelector("#canvas");
var rain = canvas.getContext('2d');
// drop数组,存每个散开的小水滴信息
var drop = [];
// water数组,存每丝雨的信息
var water = [];
// 雨的数量,可自行更改
var waterNum = 100;
// 小水(雨)滴的重力
var gravity = 1;
// 鼠标在页面的初始位置
var mouseX=-36,mouseY=-36;
// 关于雨的角度值,值为-1到1,后面讲
var direction = 0;
// 这也是关于鼠标在页面位置的角度值,值为-1到1
var mouseDelay = 0;
// 这是画布自适应窗口大小的函数,复制即可
window.onresize = resizeCanvas;
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
resizeCanvas();
// 一丝雨的初始化,封装,后面好几处要调用
function creatWater(){
water.push({
//值为0或1,判断是否要散开水滴
add:1,
//随机初始水平位置
x:Math.random()*3*canvas.width-canvas.width,
// 随机初始垂直位置,在上面一点,这样雨能从上面下落
y: Math.random()*500-500,
// 随机雨的长度
len: Math.random()*20+50,
// 随机雨的速度
speed: Math.random()*10+35,
// 随机雨的随机颜色
color: `rgb(255,255,255,${Math.random()*0.5})`,
// 随机散开水滴数量
dropNum:Math.random()*6+6
})
}
// 雨数组初始化,每丝雨都来
function chushi(){
for (let i = 0; i < waterNum; i++) {
creatWater();
}
}
// 散开水滴数组初始化,x为水平位置,y为垂直位置,dropNum为数量
function creatDrop (x,y,dropNum){
//给drop数组添加元素
for (let j = 0; j < dropNum; j++) {
drop.push({
// x轴位置
pagex:x,
// y轴位置
pagey:y,
// x轴移动距离
dx:Math.random()*12-6,
// y轴移动距离
dy:Math.random()*10-20,
// 半径
r:Math.random()+2,
//颜色
color: `rgb(255,255,255,${Math.random()*0.5+0.5})`,
})
}
}
// 绘画,画雨
function drawWater(){
//遍历数组
for (let i = 0; i < water.length; i++){
let temp = water[i];
// 颜色
rain.strokeStyle = temp.color;
// 开始路径
rain.beginPath();
// 开始点
rain.moveTo(temp.x,temp.y);
// 结束点,连线,如: 当前x位置+长度*角度值
rain.lineTo(temp.x+temp.len*direction,temp.y+temp.len);
// 线宽度
rain.lineWidth = 3;
// 绘制
rain.stroke();
}
}
// 绘画雨滴
function drawDrop(){
//遍历
for (let i = 0; i < drop.length; i++){
let temp = drop[i];
// 线宽度
rain.lineWidth = 2;
//颜色
rain.strokeStyle = temp.color;
//开始路径
rain.beginPath();
// 画一个随机的弧度
rain.arc(temp.pagex,temp.pagey,temp.r, Math.PI , Math.random() * 2 * Math.PI);
// 绘制
rain.stroke();
//结束路径
rain.closePath();
}
}
//雨信息的更新
function updateWater(){
for (let i = 0; i < water.length; i++){
// 判断雨的底部是否碰到鼠标,碰到就散开成水滴,x轴y轴与鼠标的位置绝对值在35之内则散开。
if(Math.abs(mouseX-water[i].x)<35&&Math.abs(mouseY-water[i].y-water[i].len)<35){
// 创建雨滴,传入x轴y轴大小与数量
creatDrop(water[i].x,water[i].y+water[i].len,water[i].dropNum);
// 既然水滴散开了,就清除掉这丝雨
water.splice(i,1);
// 重新来一丝随机的雨
creatWater();
}
// 判断雨的底部是否超过画布底部,且add值为1
if(((water[i].y+water[i].len)>=canvas.height) && water[i].add==1){
// add值为0
water[i].add = 0;
// 创建雨滴,传入x轴y轴大小与数量,y轴位置就为画布高即可
creatDrop(water[i].x,canvas.height,water[i].dropNum);
}
// 判断整丝雨是否超过画布
if(water[i].y>canvas.height){
// 清除它
water.splice(i,1);
// 来个新的
creatWater();
}
// 缓动动画原理,雨角度慢慢接近鼠标的角度
direction += (mouseDelay - direction)*0.0002;
// 雨x轴位置改变
water[i].x += water[i].speed*direction;
//雨y轴位置改变
water[i].y += water[i].speed;
}
}
// 雨滴信息更新
function updateDrop(){
for(let i=0;i<drop.length;i++){
// y轴移动距离加上重力。因为dy一开是负数,所以雨滴先升后降
drop[i].dy += gravity;
// x轴位置改变,同时它也受雨角度影响
drop[i].pagex += drop[i].dx + direction*10;
// y轴位置改变
drop[i].pagey += drop[i].dy;
//判断雨滴是否超过画布
if(drop[i].pagey>canvas.height){
//清除水滴
drop.splice(i,1);
}
}
}
// 绑定鼠标移动事件
window.addEventListener('mousemove',e=>{
// 得到x轴位置
mouseX = e.clientX;
//得到y轴位置
mouseY = e.clientY;
// 雨角度值,在-1到1之间
mouseDelay = (e.clientX-canvas.offsetWidth/2)/(canvas.offsetWidth/2);
})
// 判断鼠标离开事件
window.addEventListener('mouseout',()=>{
// 给个值,不会产生雨滴的值就行
mouseY=canvas.height+40;
})
// 先初始化雨数组
chushi();
//设置定时器,开始动画
setInterval(function(){
// 清除画布
rain.clearRect(0,0,canvas.width,canvas.height);
// 更新雨和雨滴信息
updateWater();
updateDrop();
// 绘画雨和雨滴
drawWater();
drawDrop();
},20)
</script>
</body>
</html>
复制代码
四.总结:
看到这里了,不点个赞再走吗~
五.CSDN其它文章:
环绕倒影加载特效 html+css 气泡浮动背景特效 html+css 简约时钟特效 html+css+js 赛博朋克风格按钮 html+css 仿网易云官网轮播图 html+css+js 水波加载动画 html+css 导航栏滚动渐变效果 html+css+js 书本翻页 html+css 3D立体相册 html+css 霓虹灯绘画板效果 html+css+js 记一些css属性总结(一) Sass总结笔记 ......等等 进我主页看更多~
最后
欢迎关注【前端瓶子君】✿✿ヽ(°▽°)ノ✿
回复「算法」,加入前端编程源码算法群,每日一道面试题(工作日),第二天瓶子君都会很认真的解答哟!
回复「交流」,吹吹水、聊聊技术、吐吐槽!
回复「阅读」,每日刷刷高质量好文!
如果这篇文章对你有帮助,「在看」是最大的支持
》》面试官也在看的算法资料《《
“在看和转发”就是最大的支持