好歌分享:飛在風中的小雨
很多人可能會認為前端工程師對於美感都有一定的水準,甚至應該要會一點點設計,但現實中應該有很大一部分的前端工程師跟我一樣,其實負責處理狀態管理居多,對設計沒有太多的著墨。
雖然現實是如此,我個人還是非常喜歡欣賞網路上大神們透過 CSS、Web API 所創作的東西,今天就來分享幾個我這陣子看到覺得蠻有趣的 WEB 特效與技巧!
忘了是從哪裡看到這個網站 - Motion Blur Scrolling Demo,驚為天人,雖然看了其實眼睛會不太舒服,但特效實在太酷,無法大聲斥責。
作者的程式碼公開在此:motionblur,我把一些看來是作者嘗試做的優化拿掉,擷取重點來解釋,大家也可以到 CodeSandbox 查看程式碼與把玩 Demo
不過要注意一點,這個特效只有在電腦版的 Chrome 與 Edge 上表現最好,firefox 還 ok,Safari 則是完全崩潰,電腦或手機都無法呈現效果。
從名稱應該就能看出端倪,既然是 motion "blur",自然會想到 CSS 的 filter:blur()
屬性,blur()
接受單一數值,例如:5px
,不能是百分比,數值越大越模糊:
image source: MDN - filter
模糊是有了,但 blur
的模糊感覺跟範例中的 motion blur 好像有點差距,要讓畫面在上下捲動時,有拉長模糊的感覺才對,這種整片均勻模糊的樣子似乎不太對呀?
要解開這個謎題,得先提到一個可能較少為人知的知識:像 blur
這種 filter-function
的屬性大多都有對應的 svg-filter
,透過 svg-filter
你就能更細緻的調整參數。使用方式則為在你想要套用 filter 效果的 DOM 元件上,用 filter: url()
來指定 svg 的 id,e.g.:
<body style="filter: url("#9isnV");">
以 blur(5px)
來說,其內部是用高斯函數的標準偏差值(stdDeviation)來決定畫面上多少 pixels 會互相交錯融合,對應的 svg 為:
<svg style="position: absolute; top: -99999px" xmlns="http://www.w3.org/2000/svg">
<filter id="svgBlur" x="-5%" y="-5%" width="110%" height="110%">
<feGaussianBlur in="SourceGraphic" stdDeviation="5"/>
</filter>
</svg>
調整 stdDeviation
就能有比起單純用 blur(5px)
更豐富的效果,而根據 MDN 資料,stdDeviation
的 type 是 <number-optional-number>
,也就是其實他能有第二個參數,當提供兩個參數時,一個會代表 x 軸的標準偏差值,一個則是 y 軸的。
所以謎題就揭曉了,要做出上下拉長的模糊化,我們可以調整 stdDeviation
的 y 軸值,這也是整個特效的重點。
如果把 y 值設大,例如 20,畫面就會變成這樣:
<feGaussianBlur in="SourceGraphic" stdDeviation="0,20"/>
因此,要做出 motion blur,就是在 scroll 的時候調整 y 值。
把調整 y 值的函式綁定到 scroll
event 的 listener,並根據捲軸捲動的位置變化差距來決定 y 軸值的大小,就能造就出範例中那種動越快越模糊的 motion blur 效果(scroll event listner 的實作不難,可以到 codesandbox 查看):
// 核心程式碼
export function initializeBodyScrollMotionBlur() {
// 動態創建 svg filter DOM 元素,將其存到 bodyBlur 物件
const bodyBlur = createBlurSvg();
// 將 svg filter 綁定到 body 上
bodyBlur.applyTo(document.body);
initializeScrollSpeedWatcher(document.documentElement, (speed) => {
// 註冊 scroll event listener,計算捲軸滾動速度,在滑動時更改 filter DOM stdDeviation attribute。
// 因為我們只需要改 y 軸的值,所以 x 固定為 0
bodyBlur.setBlur({ x: 0, y: Math.abs(speed.y / 2) });
});
}
雖然不太實用,但理解原理後還是會覺得這真的是個有趣又不太難的效果!
第二個想分享的在我平常訂閱的週刊 - Frontend Horse 看到的範例,united sodas 的網站:
透過 GIF 可能看起來不是很清楚,但實際在網站上的效果很不錯,真的有罐子旋轉的感覺。
要讓 DOM object 呈現 3D 有不少做法,利用 CSS 的 3D transforms、perspective 搭配 translate、rotate 就可以,然而週刊的作者提出的作法我覺得更為簡單且有趣,與其真的去旋轉物件,不如利用人眼錯覺,固定住 3D 模樣的罐子,只讓上方的 label 文字滾動。
具體做法如下:
找一個包含陰影的透明罐子圖案(如下圖):
以及罐子的 Label 包裝圖片:
將 Label 圖片疊加在罐子上,利用 JS 讓滾輪滾動時,將圖片從罐子右邊移至左邊。
重點在於最後要利用 clip-path
來製造出一個 svg mask,把圖片修整成貼合罐子的樣子,就可以完成這個滑順的轉動動畫!
週刊作者有提供一個 codepen 連結,可以試著把 Clip Path On
的 checkbox 開啟關閉,應該能清楚了解整體概念:
本週最後一個要分享的是從 css-doodle 的作者 blog 上看到的小技巧,這是作者發現在電腦視覺的 Shader 中,經常利用 Time Uniforms 的方式去控制動畫,這跟一般 CSS @keyframes 透過指定各時間點的屬性變化,利用 interpolation 來形成動畫的方式是不同的思考方式。
然而在 Web 世界中,其實能利用 CSS Houdini 的 @property API 與 CSS @Keyframes
,製作出一個能隨著時間變化其值的 custom property,如此一來就能達成類似 time uniforms 的方法,統一依照時間來控制所有 DOM 物件的動畫:
首先利用 @property
API 客製化屬性:
@property --t {
syntax: "<number>";
initial-value: 0;
inherits: true;
}
透過 @keyfames
來 anitmate 客制屬性,這邊時間用 31536000000
是因為作者想營造無限時間的動畫,用一年為區間算是個很足夠長的模擬方式:
@keyframes animate-time {
from { --t: 0 }
to { --t: 31536000000 }
}
最後將 keyframes 動畫放到 :root
中,這樣所有的 css class 所吃到的 --t
就會是不斷隨著時間而更動的值,此外, anmiation 的 druation 同樣設為一年,時間才會同步:
:root {
animation: animate-time 31536000000ms linear infinite;
}
最後在任意的 element 上頭,只要利用變動的 --t
就能在不另外指定 keyframes 的狀況下一樣產生動畫:
element {
transform: rotate(
calc(var(--t) / 1000 / 10 * 1turn)
);
}
同樣的 --t
值,同時套用在其他元素動畫上時,就能營造出利用 time uniforms 來製造動畫的效果。
附上作者的 codepen 範例:
See the Pen Demo #1 for article (Chrome only) by yuanchuan (@yuanchuan) on CodePen.
今天分享了三個我這週發現的有趣特效與小技巧,雖然沒有自己真正實作,但觀摩別人的創意與程式碼也能學習到非常多,除了當工作上需要使用到類似效果時,會有印象可以怎麼做之外,或許也能讓你舉一反三,融合這些技巧,製作出別出心裁的特效也說不定呢!