基本原理
首先我們要取得滑鼠在螢幕上的 X 與 Y 的數值,滑鼠在網頁的畫面起始位置是 X=0、Y=0,也就是畫面的最左上。接著我們要監聽當滑鼠進入了目標元素,透過計算滑鼠位置與目標元素的長寬,來算出上下左右,並且加上滑鼠與中心點的距離。
不囉唆直接實作吧~
我們先來建立一個 animate
的 Function,因為這個 Function 最後會放入監聽 mousemove
,所以就要先帶入變數 event
與 item
,event
是這個監聽事件會自動給的,那我們要使用它就要宣告出來。
從這個 event
我們可以拿到滑鼠的 x y 數值,因為我們是監聽該元素,所以此時拿到 x y 值不會像是平常從螢幕最左上開始計算 0 0,而是從該元素的最左上 0 0 開始計算。
再從帶入的 item
取得該元素的寬高值。拿到必要的值後,我們就要算出滑鼠與元素的相對位置,並且給他一個要追的距離。x / width
、y / height
是在計算滑鼠在元素之中的相對位置。因為原本的位置是 0~1 之間,我們想要增加移動的距離,所以要能達到以下目的。
- 當滑鼠位於元素的左邊緣 (x = 0) 時,xMove 等於 -10。
- 當滑鼠位於元素的右邊緣 (x = width) 時,xMove 等於 10。
- 當滑鼠位於元素的中央 (x = width / 2) 時,xMove 等於 0。
所以就有了 move * 2 再 - move 這個公式。
const animate = (event, item) => {
const { offsetX: x, offsetY: y } = event,
{ offsetWidth: width, offsetHeight: height } = item,
move = 10,
xMove = x / width * (move * 2) - move,
yMove = y / height * (move * 2) - move;
}
再來就幾乎結束了,將算好的賦予上 css
這邊有個奇妙的細節,我們不是直接給到 lef top 或者 translate,而是用了一個新的方法,給到變數上面去,這是為了讓之後子層元件同樣能吃到這個 css 變數值,就不用透過 js 再去計算子層,大大增加效能。
const animate = (event, item) => {
const { offsetX: x, offsetY: y } = event,
{ offsetWidth: width, offsetHeight: height } = item,
move = 10,
xMove = x / width * (move * 2) - move,
yMove = y / height * (move * 2) - move;
item.style.setProperty('--mouseX', `${xMove}px`);
item.style.setProperty('--mouseY', `${yMove}px`);
if (e.type === 'mouseleave') {
item.style.setProperty('--mouseX', '0px');
item.style.setProperty('--mouseY', '0px');
}
}
最後裝上監聽,最困難的部分就完成囉!
const FaceItems = document.querySelectorAll('.facewrap__item')
FaceItems.forEach((item) => {
item.addEventListener('mousemove', (e) => {
animate(e, item)
})
item.addEventListener('mouseleave', (e) => {
animate(e, item)
})
})
開始寫上 CSS
以上都是邏輯面,接下來是我自己最愛做的事情,就是特效的細節,就是這些微小的細節才能造就整個動畫的質感提升。我們是做多層的臉,有眼睛、嘴巴、臉這三個圖層,要給他們有不同的移動速度與距離,才能讓臉追蹤看起來更有層次感,更生動。
&__item {
svg {
width: 25rem;
height: 25rem;
#face {
transition: transform 0.1s linear;
transform: translate3d(calc(var(--mouseX) * 0.5), calc(var(--mouseY) * 0.5), 0);
}
#mouth {
transition: transform 0.1s linear;
transform: translate3d(calc(var(--mouseX) * 2), calc(var(--mouseY) * 2), 0);
}
#eyein {
transition: transform 0.1s linear;
transform: translate3d(calc(var(--mouseX) * 1.6), calc(var(--mouseY) * 1.6), 0);
}
可以看到我使用了 var(--mouseX)
,這個就是拿來吃父層剛剛被 js 所賦予的 mouseX mouseY property,所以子層也都需要跟著掛上,再用 calc()
算出你想要的距離即可。
其實整個最奇妙的還是 property,當我發現它可以直接由子層繼承父層時,真的像是發現新大陸一樣呢~希望未來我還能用這個玩出更多不同花樣。
這裡附上整個程式碼 Codepen 的連結~ https://codepen.io/esdesignstudio/pen/BaeBMBe?editors=1111