炎炎夏日,快用代码下场雨

点击上方 前端瓶子君,关注公众号

回复算法,加入前端编程面试算法每日一题群

来源:北极光之夜

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总结笔记 ......等等 进我主页看更多~

最后

欢迎关注【前端瓶子君】✿✿ヽ(°▽°)ノ✿

回复「算法」,加入前端编程源码算法群,每日一道面试题(工作日),第二天瓶子君都会很认真的解答哟!

回复「交流」,吹吹水、聊聊技术、吐吐槽!

回复「阅读」,每日刷刷高质量好文!

如果这篇文章对你有帮助,「在看」是最大的支持

》》面试官也在看的算法资料《《

“在看和转发”就是最大的支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值