想想坐在火車上回家的時候,窗外飛舞的雪花,一家人坐在一起吃年夜飯時,漫天飛舞的大雪,怎麼樣,雪花雖冷,随總是讓我們感到一股暖意,自古也有瑞雪兆豐年一說,今天就帶大家一起親手來用代碼下一場雪。
首先來看一下成品圖,怎麼樣,還行吧,(因為gif錄制原因,這是把屏幕放得比較小錄制的)
成品
實現 先來一個背景
背景
首先來一個灰蒙蒙的背景,别問我為啥搞一個這樣的背景(問就是這其實是我下一篇文章寫的東西)
畫一朵雪花
雪花
我們這裡沒有過于追求雪花的精細程度,所以就簡單弄了一個小圓點來模拟雪花,再來一個白色的陰影,給雪花一種朦胧感,是不是就有那麼回事了。
生成雪花上面的雪花我們是寫死位置的,僅僅是為了先定義雪花的外形,下面我們就來模拟真實下雪的樣子,随機的生成雪花,如果你看過之前的打年獸的文章,你肯定會與生成雪花用的方法了然于胸,沒錯,我們首先獲取屏幕的寬度,然後定時的生成雪花,并給每一篇雪花一個随機的寬度,注意,這個寬度要在屏幕的寬度之内,否則我們下到屏幕外面也沒啥意義啊,還浪費性能,之後我們給每一片雪花一個定時器,定時往下移動,然後就成了。
這裡我們還用requestAnimationFrame,但是requestAnimationFrame沒有定時功能,所以我們要記錄最後一次生成雪花的時間,和當前時間對比,如果達到我們想要的間隔了,就創建下一片雪花
lastSnowTime: '', // 最後一片雪花生成時間
snowSpeed: 3, // 雪花下落的速度
lastSnowTime: '', // 最後一片雪花生成時間
snowFrequency: 4, // 雪花生成的頻率
snowStart () {
// 雪花生成的頻率
let now = new Date().getTime()
if (now - this.lastSnowTime > (1000 / this.snowFrequency)) {
console.log(1);
// 創建雪花
let snowItem = document.createElement('div')
snowItem.className = 'snow-item'
snowItem.style.top = -snowItem.offsetWidth 'px'
snowItem.style.left = Math.random() * this.screenWidth 'px'
this.$refs.snowWrap.appendChild(snowItem)
// 雪花移動
let snowMove = () => {
snowItem.style.top = snowItem.offsetTop this.snowSpeed 'px'
// 如果雪花距離屏幕頂部距離大于等于屏幕高度,則移除此雪花
if (snowItem.offsetTop > this.screenHeight) {
this.$refs.snowWrap.removeChild(snowItem)
} else {
requestAnimationFrame(snowMove)
}
}
snowMove()
this.lastSnowTime = now
}
this.createSnowInterval = requestAnimationFrame(this.snowStart)
}
是不是有那麼點意思了
優化雪花雖然已經達到了我們的初步目的,但是這雪總看着有那麼點假,為什麼?因為大自然哪有這麼規律的雪,一樣大小,一樣速度的,所以我們得來給他加入更多的随機性。
首先就是透明度,讓每一片雪花的透明度來一個随機值
snowItem.style.opacity = Math.random()
其次是大小,我們給每一片雪花一個随機的大小
snowItem.snowScale = Math.random() * 0.5 0.5
snowItem.style.width = snowItem.offsetWidth
* snowItem.snowScale 'px'
snowItem.style.height = snowItem.offsetHeight
* snowItem.snowScale 'px'
那麼問題來了,大的雪花和小的雪花下落速度一樣嗎?我還真沒仔細觀察過,不過應該是不一樣的吧,大的落的快,小的落的慢?所以這裡我們讓雪花的下落速度跟他的大小扯上關系,大家可以看到,我們上面給雪花随機大小的時候留了一個snowScale的東西,我們暫且稱呼他為縮放系數,那麼我們的下落速度就要跟這個縮放系數成正比
let moveY = this.snowSpeed * snowItem.snowScale
snowItem.style.top = snowItem.offsetTop moveY 'px'
這裡我們将縱向偏移量記下來,後面有用,現在再來看一下效果,每片雪花都會基于我們設定的基礎大小和速度再增加一定的随機變化,是不是好多了。
這裡有一個遺留的小問題啊,我們前面定義了一個snowFrequency變量,用來控制雪花的生成頻率,乍一看好像沒啥問題,但是如果我們在不同的設備上看就會發現,屏幕越大,雪花越稀疏,屏幕越小,雪花越密集,這肯定不對啊,所以這個頻率我們要讓它随着屏幕的變化而變化,并且同時還要我們可以控制。那麼我們就可以設定一個變量,假如它是200,就代表1秒時間,每200像素的區域生成一片雪花,這樣屏幕越大,一秒鐘生成的雪花越多,屏幕越小,生成的雪花也就越少我也不知道咋稱呼,咱們暫且稱之為區域密度,我們拿屏幕寬度除以這個區域密度,就得出了最終的雪花生成頻率。
snowFrequencyRatio: 300, // 雪花頻率系數,越大雪花越少
mounted () {
// 根據雪花頻率系數和屏幕寬度計算雪花生成的頻率
this.snowFrequency = Math.floor(this.screenWidth
/ this.snowFrequencyRatio)
this.createCity()
this.snowStart()
},
既然是飄舞的雪花,一直垂直降落多沒意思啊,不如我們來點風,讓它飄起來。
思路分析,既然來電風,那肯定就是讓雪花橫向移動,那移動多少呢?我們最開始給雪花下落的速度給了一個定值,按照這個想法來,我們給雪花橫向的偏移量也來一個定值肯定沒問題,但是現在的問題是我們雪花的下降速度是和基礎下降速度、自身大小都有關的,我們再設一個橫向的偏移距離,再讓它也跟大小有關系這就太麻煩了,況且這個值設立多少全憑我們随意,太不嚴謹了。
那麼你有沒有什麼好的辦法呢?哈哈哈,這裡我們想象一下雪花下落的樣子(不考慮雪花曲線飛舞),考慮一下橫向偏移量和縱向偏移量的關系,是不是一個Rt△(部分學渣同學是不是已經忘記這是啥了,沒錯,這就是直角三角形)
這裡∠α是我們設置的偏移量,a就是垂直方向的位移,這兩個我們都知道了,那麼利用正切公式,tanα = b / a,可以很輕易的算出b的值,也就是橫向偏移量的值,在js中我們可以用Math.tan這個方法實現相關的功能,Math.tan方法接收一個弧度值,角度和弧度的轉換公式是
弧度角度
所以這裡我們橫向的偏移量就是
let moveX = Math.tan(this.snowAngle * Math.PI / 180) * moveY
snowItem.style.left = snowItem.offsetLeft - moveX 'px'
這樣雪花橫向移動的效果就出來了(學霸同學是不是很簡單,學渣的我看了好半天的正切東西才迷糊過來)
看起來很美好,但是問題又來了,細心的同學會發現,右下角總是沒有雪,那是因為現在雪的軌迹是這樣的
我們在屏幕最右邊生成的雪花,落到地上就不在最右邊了,而是會偏移一個b的距離,那這個問題怎麼解決呢,那就是在雪花生成的時候,就把這個b的距離給算進去,就像這樣
那這個b怎麼求的呢?其實跟我們求雪花每一次移動的橫向距離的方法是一樣的,隻不過我們這裡的a是屏幕的高度罷了,所以我們生成雪花時給予的随機橫向距離應該是這樣的
// 在給雪花随機分配橫向坐标時,範圍應該把雪花的偏移量也算進去,否則屏幕右下角會出現空白
let _left = (this.screenWidth Math.tan(this.snowAngle *
Math.PI / 180) * this.screenHeight) * Math.random()
snowItem.style.left = _left 'px'
這樣就沒啥問題了,不過這樣子會有一定的性能問題,就是我們會額外生成很多的雪花,就像圖中那樣,橙色區域的雪花雖然我們看不到,但是他們都在運動,并且消耗着性能,同時存在的雪花數量越多,性能損耗就越嚴重
右邊的雪花我不知道怎麼優化,但是左邊的,我們可以加一個判斷,當雪花超出左側屏幕時,将其移除即可。
// 如果雪花偏移角度大于0,則判斷雪花是否超出左側屏幕
if (this.snowAngle > 0) {
if (snowItem.offsetLeft < (-snowItem.offsetWidth)) {
this.$refs.snowWrap.removeChild(snowItem)
return
}
}
好了,本篇文章到這裡就結束了,相應的完整代碼都會在我下一篇文章誕生的時候貼出來哦(希望能夠順産吧),本人文化水平較低,技術有限,大家如果有什麼建議或者指正,歡迎評論區留言
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!