前端開發來說,通過動畫來提升交互效果是很常見的。在很早以前,做web動畫主要通過javascript或者jquery或者flash這樣的手段,非常麻煩,自打有了ccs3,做動畫就太方便了,只需幾行css代碼就可以搞定。
這里我們就演示一個常見的循環滾動效果,任務是這樣:先準備一個圖片,平鋪到頁面上充滿整個屏幕,然后就讓畫面一直向上循環滾動,形成無邊無際的感覺。
雖然可以從網上搜到一些類似的代碼,但是魚龍混雜,無關緊要的代碼非常多,不夠純粹。如果要弄明白動畫的原理,只有自己動手做一遍才能真正消化吃透。所以我們來一步步原創這個代碼,排除所有不必要的基礎樣式,只說要點,3個步驟你就可以完全掌握其精髓!
第一步:布局
首先,滾動的圖片需要放在一個容器里,一行html代碼即可完成:
第二步:把圖片放進容器
css中body的邊界設為0,把容器設高度100%以充滿屏幕,再調用背景圖pic.png
第三部:讓畫面動起來
咱不做標題黨,循環滾動靠的就是3行css動起來的。
先是1行 -webkit-animation屬性:4個參數分別表示:動畫名稱scroll,1秒時長,移動速度為線性的,無限循環。
然后是對應的關鍵幀 @-webkit-keyframes 屬性,這是自己定義的動畫規則,只需寫2行規則即可:
原理:動畫就是畫從一個地方動到另一個地方。對普通滾動效果來說,有起點和終點這兩個節點的位置就夠了。所以我們用0%和100%分別表示起點和終點,指定2個背景圖的xy位置坐標即可。圖片會在規定時間內從起點移動到終點,并循環下去,數值是負表示是向上移動。320px正好是圖片的高度,這樣循環的時候是無縫銜接的。
好了,最終完整的代碼如下,是不是很精練呢?保存成 index.html 即可
代碼寫完了,還要記得在當前目錄要有pic.png這個圖片哦,我隨便畫了幾筆,絕無觀賞性,建議自己找個好看點的圖片來代替。
現在用瀏覽器打開index.html即可看到效果,比較魔性的地方在于,如果你盯著看久了,關閉窗口以后會出現幻覺,仿佛整個顯示器都在向上飛,哈哈!
最后我們來說說瀏覽器兼容性問題:
大家可能注意到了,前面那2個古怪的 -webkit-animation, @-webkit-keyframes 這里的-webkit-其實是一個前綴,animation和@keyframes才是CSS的標準屬性。
當加上-webkit-后,就形成了一個針對特殊瀏覽器的專有屬性,表示用在谷歌的chrome和蘋果的safari瀏覽器上。此外還有-moz前綴代表針對firefox瀏覽器的私有屬性。
所以我們在用到css3的一些特性的時候,經常使用一大堆的重復性的代碼,比如我們今天的這個代碼,有人會寫成這個樣子:
一個簡單的動畫就要寫這么多冗余的代碼,為的只是支持一些舊的瀏覽器,有必要嗎?為什么在這個例子中我們僅僅采用了-webkit-而沒有使用其它專有屬性呢?
因為現在已經是2019年了!谷歌蘋果的瀏覽器是主流,占據了絕大部分,而其它小眾瀏覽器也大多能夠兼容他們,在版本上,大部分人安裝瀏覽器是直接下載新版本安裝使用,而非找出家里陳年的老軟盤、老光盤去安裝,家中的老電腦也早已升級不知多少回了,所以也幾乎沒有機會使用低版本的瀏覽器了!
至于微軟的IE,就更別提了,IE9以前不支持動畫的,只能用js或者jquery來寫動畫,直到IE10才支持css動畫,隨后IE被放棄,主推Edge,搞了幾天越來越頭大干脆也放棄,現在直接使用chrome內核了,所以針對ie的兼容性除非有特殊要求已經無需考慮。
你在網上能看到的范例代碼,如果有寫成那么復雜臃腫的,估計也都是3-5年前發的老文,或者抄來抄去不做思考的搬磚工留下的“初學者”筆記。
我們不仿測試一下幾款主流瀏覽器的情況看看,結論:
測試結果表明,-webkit-的寫法在4款不同內核的瀏覽器上都能正常使用,所以我們的代碼因此能得以簡化。
當然,這個例子也有局限性,比如你看,只有蘋果safari不支持標準寫法,萬一將來他改邪歸正了呢?畢竟標準寫法才是眾望所歸不是?使用針對個別瀏覽器的私有屬性寫法,雖可用但畢竟有些怪怪的,將來怎么樣還很難說呢。這樣看來,如果使用古老的處理辦法,重復N次為每個專屬瀏覽器各寫一份代碼,除了辣眼睛也真沒什么錯。
瀏覽器的兼容問題涉及面實在是非常廣,三言兩語還真說不完,以后會專門來講。
覽器內核中有一個渲染進程,又名Renderer進程,我們頁面的渲染,js的執行,事件的循環都在這一進程內進行,也就是說,該進程下面擁有著多個線程,靠著這些現成共同完成渲染任務。這個進程里面主要有一下幾個線程:
1、 圖形用戶界面GUI渲染線程
2、JS引擎執行線程
3、定時觸發器線程
4、異步HTTP請求線程
5、事件觸發線程
瀏覽器Event Loop圖:
示例代碼:
1. 初始化狀態都為空,瀏覽器控制臺是空的的,調用堆棧也是空的
2. console.log(‘Hi’)添加到調用堆棧中
3. 執行console.log(‘Hi’)
4. console.log(‘Hi’)從調用堆棧中移除。
5. setTimeout(function cb1() { … }) 添加到調用堆棧。
6. setTimeout(function cb1() { … }) 執行,瀏覽器創建一個計時器計時,這個作為Web api的一部分。
7. setTimeout(function cb1() { … })本身執行完成,并從調用堆棧中刪除。
8. console.log(‘Bye’) 添加到調用堆棧
9. 執行 console.log(‘Bye’)
10. console.log(‘Bye’) 從調用調用堆棧移除
11. 至少在5秒之后,計時器完成并將cb1回調推到回調隊列。
12. 事件循環從回調隊列中獲取cb1并將其推入調用堆棧。
13. 執行cb1并將console.log(‘cb1’)添加到調用堆棧。
14. 執行 console.log(‘cb1’)
15. console.log(‘cb1’) 從調用堆棧中移除
16. cb1 從調用堆棧中移除
快速回顧:
在JavaScript中,任務被分為Task(又稱為MacroTask,宏任務)和MicroTask(微任務)兩種。它們分別包含以下內容:
MacroTask: script(整體代碼), setTimeout, setInterval, setImmediate(node獨有), I/O, UI rendering
MicroTask: process.nextTick(node獨有), Promises, Object.observe(廢棄), MutationObserver
需要注意的一點是:
在同一個上下文中,總的執行順序為同步代碼—>microTask—>macroTask
具體來說,瀏覽器會不斷從task隊列中按順序取task執行,每執行完一個task都會檢查microtask隊列是否為空(執行完一個task的具體標志是函數執行棧為空),如果不為空則會一次性執行完所有microtask。然后再進入下一個循環去task隊列中取下一個task執行,以此類推。
示例代碼:
主程序和和settimeout都是宏任務,兩個promise是微任務
第一個宏任務(主程序)執行完,執行全部的微任務(兩個promise),再執行下一個宏任務(settimeout),所以結果為:
正確答案是
script start, script end, promise1, promise2, setTimeout
1. 進程:程序的一次執行, 它占有一片獨有的內存空間
2. 線程: CPU的基本調度單位, 是程序執行的一個完整流程
3. 進程與線程
* 一個進程中一般至少有一個運行的線程: 主線程
* 一個進程中也可以同時運行多個線程, 我們會說程序是多線程運行的
* 一個進程內的數據可以供其中的多個線程直接共享
* 多個進程之間的數據是不能直接共享的
4. 瀏覽器運行是單進程還是多進程?
* 有的是單進程
* firefox
* 老版IE
* 有的是多進程
* chrome
* 新版IE
5. 如何查看瀏覽器是否是多進程運行的呢?
* 任務管理器==>進程
6. 瀏覽器運行是單線程還是多線程?
* 都是多線程運行的
1. 如何證明js執行是單線程的?
* setTimeout()的回調函數是在主線程執行的
* 定時器回調函數只有在運行棧中的代碼全部執行完后才有可能執行
2. 為什么js要用單線程模式, 而不用多線程模式?
* JavaScript的單線程,與它的用途有關。
* 作為瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。
* 這決定了它只能是單線程,否則會帶來很復雜的同步問題
3. 代碼的分類:
* 初始化代碼(同步代碼)
* 回調代碼
4. js引擎執行代碼的基本流程
* 先執行初始化代碼: 包含一些特別的代碼
* 設置定時器
* 綁定監聽
* 發送ajax請求
* 后面在某個時刻才會執行回調代碼
同步 同步執行完成才會去執行異步
異步 只要是異步的任務都會有自己的管理模塊進行托管
異步任務: (分為:微任務 、宏任務)
回調
事件
定時器
ajax
生命周期回調函數
常見的微任務有:
常見的宏任務有:
注意:在微任務執行之后再執行宏任務。
1. 所有代碼分類
* 初始化執行代碼(同步代碼): 包含綁定dom事件監聽, 設置定時器, 發送ajax請求的代碼
* 回調執行代碼(異步代碼): 處理回調邏輯
2. js引擎執行代碼的基本流程:
* 初始化代碼===>回調代碼
3. 模型的2個重要組成部分:
* 事件管理模塊
* 回調隊列
4. 模型的運轉流程
* 執行初始化代碼, 將事件回調函數交給對應模塊管理
* 當事件發生時, 管理模塊會將回調函數及其數據添加到回調列隊中
* 只有當初始化代碼執行完后(可能要一定時間), 才會遍歷讀取回調隊列中的回調函數執行
理解:JavaScript事件執行隊列:
將所有js事件依次放在一個執行隊列里:
1.首先放入同步(事件)
2.再放入異步微任務(事件) 如:.then .catch 里的回調
3.再放入異步宏任務(事件) 如: 點擊事件、setTimeout定時器
4.執行完以上事件 會進行一次GUI渲染。
GUI渲染線程負責渲染瀏覽器界面HTML元素,當界面需要重繪(Repaint)或由于某種操作引發回流(reflow)時,該線程就會執行。
5.事件循環:
執行到異步微任務的時候 里面的回調一樣會按順序執行:
1.同步(事件)
2.異步微任務(事件)
3.再異步宏任務
4.再進行一次GUI渲染。
再執行到異步宏任務的時候 里面的回調一樣會按順序執行
1.同步(事件)
2.異步微任務(事件)
3.再異步宏任務
4.再進行一次GUI渲染。
最終完成事件的循環。
setTimeout(() => {
// 異步宏任務代碼
console.log(`執行定時器setTimeout`);
}, 0)
new Promise(resolve => {
console.log(`執行Promise的resolve`); // 同步代碼1
resolve(1)
}).then((val) => {
// 異步微任務代碼
console.log(`執行Promise的then-${val}`);
})
for (let index = 0; index < 10; index++) {
// 同步代碼2
console.log(`執行for循環${index}`);
}
最終執行結果:
1 Promise的resolve 同步代碼1 先執行
2 for循環 同步代碼2 再執行 (無論循環多少次 都是同步代碼就會比異步先執行)
3 then的回調 再執行 異步微任務代碼 (異步中微任務 比 宏任務 先執行)
4 setTimeout 再執行 異步宏任務代碼
歡迎關注我的原創文章:小伙伴們!我是一名熱衷于前端開發的作者,致力于分享我的知識和經驗,幫助其他學習前端的小伙伴們。在我的文章中,你將會找到大量關于前端開發的精彩內容。
學習前端技術是現代互聯網時代中非常重要的一項技能。無論你是想成為一名專業的前端工程師,還是僅僅對前端開發感興趣,我的文章將能為你提供寶貴的指導和知識。
在我的文章中,你將會學到如何使用HTML、CSS和JavaScript創建精美的網頁。我將深入講解每個語言的基礎知識,并提供一些實用技巧和最佳實踐。無論你是初學者還是有一定經驗的開發者,我的文章都能夠滿足你的學習需求。
此外,我還會分享一些關于前端開發的最新動態和行業趨勢?;ヂ摼W技術在不斷發展,新的框架和工具層出不窮。通過我的文章,你將會了解到最新的前端技術趨勢,并了解如何應對這些變化。
我深知學習前端不易,因此我將盡力以簡潔明了的方式解釋復雜的概念,并提供一些易于理解的實例和案例。我希望我的文章能夠幫助你更快地理解前端開發,并提升你的技能。
如果你想了解更多關于前端開發的內容,不妨關注我的原創文章。我會不定期更新,為你帶來最新的前端技術和知識。感謝你的關注和支持,我們一起探討交流技術共同進步,期待與你一同探索前端開發的奇妙世界!
#2023年度創作挑戰#
*請認真填寫需求信息,我們會在24小時內與您取得聯系。