女朋友常逛的设计网站这两天页面上多了下雪的效果,于是问我我的网站能下雪吗,作为一个程序员我一样平常会说实现不了,但是作为男朋友,不能说弗成。
雪
雪我们可以利用span标签和css的径向渐变大略意思一下:
.snow{display:block;width:100px;height:100px;background-image:radial-gradient(#fff0%,rgba(255,255,255,0)60%);border-radius:50%;}复制代码
效果如下:
很多雪
一片雪是不足的,成千上万才浪漫,天下上没有两片相同的雪花,以是每片雪都有自己的大小位置速率等属性,为此先创建一个雪花类:
classSnow{constructor(opt={}){//元素this.el=null//直径this.width=0//最大直径this.maxWidth=opt.maxWidth||80//最小直径this.minWidth=opt.minWidth||2//透明度this.opacity=0//水平位置this.x=0//重置位置this.y=0//速率this.speed=0//最大速率this.maxSpeed=opt.maxSpeed||4//最小速率this.minSpeed=opt.minSpeed||1//浏览器窗口尺寸this.windowWidth=window.innerWidththis.windowHeight=window.innerHeightthis.init()}//初始化各种属性init(){this.width=Math.floor(Math.random()this.maxWidth+this.minWidth)this.opacity=Math.random()this.x=Math.floor(Math.random()(this.windowWidth-this.width))this.y=Math.floor(Math.random()(this.windowHeight-this.width))this.speed=Math.random()this.maxSpeed+this.minSpeed}//设置样式setStyle(){this.el.style.cssText=`position:fixed;left:0;top:0;display:block;width:${this.width}px;height:${this.width}px;opacity:${this.opacity};background-image:radial-gradient(#fff0%,rgba(255,255,255,0)60%);border-radius:50%;z-index:9999999999999;pointer-events:none;transform:translate(${this.x}px,${this.y}px);`}//渲染render(){this.el=document.createElement('div')this.setStyle()document.body.appendChild(this.el)}}复制代码
init方法用来天生随机的初始大小、位置、速率等属性,在浏览器窗口内new100片试试:
letsnowList=[]for(leti=0;i<100;i++){letsnow=newSnow()snow.render()snowList.push(snow)}复制代码
效果如下:
动起来
雪动起来才能叫下雪,动起来很大略,不断改变x和y坐标就可以了,给snow类加个运动的方法:
classsnow{move(){this.x+=this.speedthis.y+=this.speedthis.el.style.left=this.x+'px'this.el.style.top=this.y+'px'}}复制代码
接下来利用requestAnimationFrame不断刷新:
moveSnow(){window.requestAnimationFrame(()=>{snowList.forEach((item)=>{item.move()})moveSnow()})}复制代码
效果如下,由于速率是正数,以是整体是往右斜的:
可以看到动起来了,但是出屏幕就不见了,以是雪是会消逝得对吗?要让雪一直很大略,检测雪的位置,如果超出屏幕了就让它回到顶部,修正一下move方法:
move(){this.x+=this.speedthis.y+=this.speed//完备离开窗口就调一下初始化方法,其余还须要修正一下init方法,由于重新涌现我们是希望它的y坐标为0或者小于0,这样就不会又凭空涌现的觉得,而是从天高下来的if(this.x<-this.width||this.x>this.windowWidth||this.y>this.windowHeight){this.init(true)this.setStyle()}this.el.style.left=this.x+'px'this.el.style.top=this.y+'px'}复制代码
init(reset){//...this.width=Math.floor(Math.random()this.maxWidth+this.minWidth)this.y=reset?-this.width:Math.floor(Math.random()this.windowHeight)//...}复制代码
这样就能源源不断地下雪了:
优化1.水平速率
水平和垂直方向的速率是一样的,但是看起来有点太斜了,以是调度一下,把水平速率和垂直速率区分开来:
classSnow{constructor(opt={}){//...//水平速率this.sx=0//垂直速率this.sy=0//...}init(reset){//...this.sy=Math.random()this.maxSpeed+this.minSpeedthis.sx=this.syMath.random()}move(){this.x+=this.sxthis.y+=this.sy//...}}复制代码
由于整体向右倾斜,以是左下角大概率没有雪,这可以通过让雪随机涌如今左侧来办理:
init(reset){//...this.x=Math.floor(Math.random()(this.windowWidth-this.width))this.y=Math.floor(Math.random()(this.windowHeight-this.width))if(reset&&Math.random()>0.8){//让一小部分的雪初始化在左侧this.x=-this.width}elseif(reset){this.y=-this.width}//...}复制代码
3.面前的雪
随机性的选择一点雪给它较大的体积、透明度和速率,然后再利用css3的3D透视效果,把它的z轴数值调大一点,这样的觉得就彷佛是在面前划过的一样:
<bodystyle="perspective:500;-webkit-perspective:500"></body>复制代码
classSnow{constructor(opt={}){//...//z轴数值this.z=0//快速划过的最大速率this.quickMaxSpeed=opt.quickMaxSpeed||10//快速划过的最小速率this.quickMinSpeed=opt.quickMinSpeed||8//快速划过的宽度this.quickWidth=opt.quickWidth||80//快速划过的透明度this.quickOpacity=opt.quickOpacity||0.2//...}init(reset){letisQuick=Math.random()>0.8this.width=isQuick?this.quickWidth:Math.floor(Math.random()this.maxWidth+this.minWidth)this.z=isQuick?Math.random()300+200:0this.opacity=isQuick?this.quickOpacity:Math.random()//...this.sy=isQuick?Math.random()this.quickMaxSpeed+this.quickMinSpeed:Math.random()this.maxSpeed+this.minSpeed//...}move(){//...this.el.style.transform=`translate3d(${this.x}px,${this.y}px,${this.z}px)`}}复制代码
雪花嘛,轻如鹅毛,鹅毛是怎么飘的?是不是旁边摆动的飘?那我们也可以选择一部分的雪花让它跟鹅毛一样飘,旁边摇摆很大略,速率一会加一会减就可以了:
classSnow{constructor(opt={}){//...//是否旁边摇摆this.isSwing=false//旁边摇摆的步长this.stepSx=0.03//...}//随机初始化属性init(reset){//...this.isSwing=Math.random()>0.8//...}move(){if(this.isSwing){if(this.sx>=1||this.sx<=-1){this.stepSx=-this.stepSx}this.sx+=this.stepSx}//...}}复制代码
除了上述这种方法,旁边摇摆还有一种办法,便是利用正弦或余弦函数,由于它们的曲线翻转90度便是旁边摇摆:
img
我们利用正弦函数,公式为:y=sin(x),x的值是弧度表示,只要一贯增加就可以了,y的值用来修正雪花的水平方向的速率变革步长:
classSnow{constructor(opt={}){//...//是否旁边摇摆this.isSwing=false//旁边摇摆的正弦函数x变量this.swingRadian=0//旁边摇摆的正弦x步长this.swingStep=0.01//...}init(reset){//...this.swingStep=0.01Math.random()}move(){if(this.isSwing){this.swingRadian+=this.swingStepthis.x+=this.sxMath.sin(this.swingRadianMath.PI)0.2}else{this.x+=this.sx}//...}}复制代码
由于正弦函数y的值是从1变革到-1,摆动幅度太了,以是乘了个小数0.2缩小一点,想要幅度小一点,还有一个方法是不要利用全体正弦曲线,可以从中截取一个适宜的区间大小,比如就让x的值在0.9π到1.1π之前变革:
classSnow{constructor(opt={}){//...//是否旁边摇摆this.isSwing=false//旁边摇摆的正弦函数x变量this.swingRadian=1//须要改成一个中间值//旁边摇摆的正弦x步长this.swingStep=0.01//...}init(reset){//...this.swingStep=0.01Math.random()this.swingRadian=Math.random()(1.1-0.9)+0.9//也让它随机一下}move(){if(this.isSwing){if(this.swingRadian>1.1||this.swingRadian<0.9){this.swingStep=-this.swingStep}this.swingRadian+=this.swingStepthis.x+=this.sxMath.sin(this.swingRadianMath.PI)}else{this.x+=this.sx}//...}}复制代码
5.下得慢一点
既然给水平加了曲线,垂直方向上是不是也可以改成非匀速呢?当然可以,差异是速率得一贯是正的,不然就要涌现反自然征象了,改变速率曲线同样可以利用正余弦,上面我们利用了0.9π到1.1π之间的正弦曲线,根据上图可以创造对应的余弦曲线都是负的,趋势是先慢后快,以是可以利用这一段来改变垂直方向的速率:
move(){if(this.isSwing){if(this.swingRadian>1.1||this.swingRadian<0.9){this.swingStep=-this.swingStep}this.swingRadian+=this.swingStepthis.x+=this.sxMath.sin(this.swingRadianMath.PI)this.y-=this.syMath.cos(this.swingRadianMath.PI)//由于速率都是负的,以是改成-}else{this.x+=this.sxthis.y+=this.sy}//...}复制代码
6.在最上面
为了防止为页面上原来层级更高的元素遮挡,给雪花的样式加一个很大的层级:
render(){this.el=document.createElement('div')this.el.style.cssText=`//...z-index:9999999999999;`document.body.appendChild(this.el)}复制代码
7.看不见我
修正了层级,以是雪花会在页面的最上层,那么可能会挡住其他元素的鼠标事宜,须要禁止它相应鼠标事宜:
render(){this.el=document.createElement('div')this.el.style.cssText=`//...pointer-events:none;`document.body.appendChild(this.el)}复制代码
8.更好一点
利用性能更好的transform属性来做动画:
render(){this.el=document.createElement('div')this.el.style.cssText=`left:0;top:0;transform:translate(${this.x}px,${this.y}px);`document.body.appendChild(this.el)}复制代码
move(){//...//this.el.style.left=this.x+'px'//this.el.style.top=this.y+'px'this.el.style.transform=`translate(${this.x}px,${this.y}px)`}复制代码
当然,最好的办法是用canvas来画。
终极效果:
下雨&雨夹雪
下完雪,接下来顺便下个雨,雨和雪差不多,都是从天上掉下来,但是雨的速率更快,常日也不会旁边摇摆什么的,方向也基本是同等的,先来修正一下样式:
setStyle(){this.el.style.cssText=`//...width:1px;//...`}复制代码
很大略,只要把宽度写去世为1就行了:
接下来把摇摆去掉:
move(){this.x+=this.sxthis.y+=this.sy//...}复制代码
效果如下:
可以创造雨是竖着在水平移动,显然是弗成的,须要让它倾斜一定的角度,和运动方向保持同等,这个也很大略,算一下斜率,水平速率除以垂直速率:
move(){//...this.el.style.transform=`translate(${this.x}px,${this.y}px)${this.getRotate(this.sy,this.sx)}`}getRotate(sy,sx){return`rotate(${sx===0?0:(90+Math.atan(sy/sx)(180/Math.PI))}deg)`}复制代码
由于tan(θ)=sy/sx,θ=Math.atan(sy / sx),由于雨的线段默认是从上到下垂直的,θ是代表和水平方向上的夹角,以是须要先旋转90度,再旋转夹角的度数,末了弧度转角度的公式为:角度=弧度(180/π)。
雨和雪都实现了,让它们一起出来,便是雨夹雪了:
根据景象下雪
把上面的代码放到网站上就有下雪的效果了,其余也可以利用景象厂商的api,根据实时景象来下雪或者下雨,再实现一下太阳、乌云等效果,一个沉浸式景象就完成了,有兴趣的可自行实践。
完全代码
https://github.com/wanglin2/snow
源自:juejin.cn/post/6910857740327845901