原本淡入淡出特效只要透過CSS的opacity與transition就能做到。但這是針對射後不理型的淡入淡出特效而言。一到達某個點,就啟動特效,而且一次就演到完,不管網頁上其他物件的狀態。
在視差滾動的設計中,各種特效的施展除了射後不理的類型外,還有依據滾動的程度變換的特效,在這次的習作選用的是操縱opacity這個屬性來達到淡入淡出效果。
除了線性的淡出淡入,我們還可以用上都還給數學老師的三角函數來製作循環淡出淡入特效。
首先,從上一篇的習作裡面,我們用getProgress這個值代表目前捲動了幾個螢幕的進度(0代表沒有捲動,100代表捲動了一整個螢幕的長度):
var getProgress = function(){ return parseInt(body.scrollTop * 100 / document.body.clientHeight); }
接下來,我們就能利用這個progress值來製作一個函式設定DOM元素的透明度的函式opacityImage,我們餵給它目前的捲動進度progress,想要開始特效的起點,想要完成特效的終點,還有元素的CSS path:
opacityImage = function (progress, start, end, imgSelector) { // 只有在開始與結束的區間內才要計算透明度,避免浪費CPU if (progress > start && progress < end) { // 取得DOM元素 var img = document.querySelector(imgSelector); if (img && img.style) { //DOM元素的透明度從0漸漸變成1 img.style.opacity = ((progress - start)/(end - start)); } } },
這樣我們只要設定一個更新的函式,讓它負責所有要淡入的元素的透明度更新,每次onscroll事件都呼叫它即可。
在這邊我抓取兩個元素 .fade1 與 .fade2。.fade1要開始淡入的起點是捲動到20%螢幕長度時開始,然後在捲動到44%時完整呈現。.fade2則是在捲動到83%與117%時執行特效。這邊的數值都是根據元素實際捲進螢幕的位置與作者想要的效果而決定,換句話說,沒有任何公式可以預測,只能自己測量,算是一種會算到火大的手工業!
fadeInOutHandler = function (progress) { opacityImage(progress, 20, 44, '.fade1'); opacityImage(progress, 83, 117, '.fade2'); },
這樣的幾行程式碼就能做到如下圖一般的簡單淡入效果:
不過這樣簡單的特效是沒辦法滿足我們這些程式狂的!做出來預設特效之後,接下來的就是想辦法讓我們可以用類似的方法,製造出淡出與其他類型的透明度變換特效!
得益於JavaScript是個functional language的特色,它的函式能傳遞的東西除了數值、字串、物件等類型之外,我們也是可以把一個函式當成參數傳遞進函式的!
現在讓我們來改造原本的opacityImage函式,加進一個新的參數叫做customizedFunc,這個參數接受一個函式,且這函式會接受progress, start與end當做參數,並回傳一個介於0到1之間的數字。以決定DOM元素的透明度:
opacityImage = function (progress, start, end, imgSelector, customizedFunc) { // 只有在開始與結束的區間內才要計算透明度,避免浪費CPU if (progress > start && progress < end) { // 取得DOM元素 var img = document.querySelector(imgSelector); if (img && img.style) { // 如果customizedFunc有定義則呼叫customizedFunc,否則預設效果為從0漸漸變成1 img.style.opacity = (customizedFunc && customizedFunc(progress, start, end)) || ((progress - start)/(end - start)); } } },
img.style.opacity = (customizedFunc && customizedFunc(progress, start, end)) || ((progress - start)/(end - start));
這裡面其實是用了短路求值(Short-circuit evaluation)達到這樣的效果,上面那行與下面的程式碼基本上是一樣的:
if (customizedFunc) { img.style.opacity = customizedFunc(progress, start, end); } else { img.style.opacity = (progress - start)/(end - start); }
這種簡化if/else為短短一行的作法在JavaScript與C家族的macro上常常看見,通常都會是艱深難懂的火星文程式碼的必備成員。但其實運用得當的話,可以讓程式簡潔好懂--只要邏輯不過分複雜、排版正確且正確使用小括號的話。
fadeOutEffect = function (progress, start, end) { return 1 - ((progress - start)/(end - start)); };
這是把原本的淡入特效反過來用,隨著捲軸滾動向下,該元素的透明度會漸漸變淡,直到消失。
memoryEffect = function (progress, start, end) { // Range : 0.1 ~ 1 return 0.1 + ((Math.sin(Math.PI * progress/10)+1) * 0.45); };
這個特效就有趣了,它借助了三角函式Math.sin()的幫助,依照progress每次滾動20%的螢幕,製造了一個在-1與1區間來回擺盪的數值,再加上1之後讓數值調整為在0與2之間迴盪,再乘與0.45使他的數值擺盪的區間變成0到0.9。最後再加上調整值0.1,這樣整個透明度就會在0.1與1之間擺盪。
簡單的三角函數與線性調整就可以替我們創造閃爍的淡入淡出特效了!數學在程式的世界其實比想像中要常用、好用,大家要好好學數學啊!
以上的特製函式都準備完成後,只要很簡單的把他加進原本的處理函式,整個工作就大功告成了!
fadeInOutHandler = function (progress) { opacityImage(progress, 20, 44, '.fade1'); opacityImage(progress, 83, 117, '.fade2'); opacityImage(progress, 200, 300, '.fade3', fadeOutEffect); opacityImage(progress, 130, 170, '.cycle1', memoryEffect); opacityImage(progress, 150, 200, '.cycle2', memoryEffect); }
最後我們在scroll事件處理函式把fadeInOutHandler放進去,特效就能動了:
body.addEventListener('scroll', function (e){ var progress = getProgress(), ...... fadeInOutHandler(progress); ...... });
這個展示頁面顯示了以上提及的淡入淡出特效,有興趣的人可以玩玩看。
習作 1-1 純CSS視差滾動設計(Pure CSS Parallax design)
習作 1-2 視差滾動設計與 requestAnimationFrame