[JavaScript 30]Day16-CSS Text Shadow Mouse Move Effect

Ivy Ho
IvyCodeFive
Published in
10 min readJul 7, 2022

今天要練習如何偵測到滑鼠移動位置,進而利用 JS 動態改變 CSS 樣式,製作出「隨著滑鼠移動,會變換位置的陰影效果」

鼠標移動至 <h1> 字樣中心 :

鼠標在 <h1> 字樣周圍移動,不同顏色的陰影樣式,位置會跟著產生變化 :

學習重點

  • 監聽 mousemove 事件
  • ES6 解構賦值
  • offsetWidthoffsetHeight : 元素寬、高
  • offsetLeftoffsetTop : 元素左邊、上面與 offsetParent 的距離
  • e.offsetXe.offsetY : 滑鼠距離元素左上角的 X、Y 軸距離
  • Math.round() : 回傳四捨五入後的近似值

觀察範例檔案初始狀態

HTML

html 有個簡單的結構 ,.hero 裡包著一個 <h1>

<div class="hero">
<h1 contenteditable>🔥WOAH!</h1>
</div>

CSS

  • 根據 CSS 設定,.hero 是佔滿整個瀏覽器視窗範圍的狀態。
  • 範例預先在 h1 加上了一個 text-shadow 樣式,可能是方便讓我們了解設定方法。之後用 JS 加上其他陰影效果後,可以將原先在 CSS 內的這段 text-shadow 設定刪除。
.hero {  min-height: 100vh;  display: flex;  justify-content: center;  align-items: center;  color: black;}h1 {  text-shadow: 10px 10px 0 rgba(0,0,0,1);  font-size: 100px;}

(最後完成的完整 JS 程式碼,以及檔案下載、課程連結,將放在最下方)

步驟拆解

一、對 .hero 監聽 mousemove 事件

二、取得 .hero 在視窗上的寬高範圍,以及動態滑鼠座標

三、整合鼠標進入<h1> 範圍內的座標

四、將滑鼠座標數值,轉換為想設定的樣式 px 範圍

五、調整陰影位置

六、加入多種顏色、不同位置的陰影樣式

一、對 .hero 監聽 mousemove 事件

二、取得 .hero 在視窗上的寬高範圍,以及動態滑鼠座標

[小補充] ES6 解構賦值寫法 :

const width = hero.offsetWidth;
const height = hero.offsetHeight;

// 可簡寫成
const { offsetWidth: width, offsetHeight: height} = hero;

===

let x = e.offsetX;
let y = e.offsetY;
// 可簡寫成
let { offestX: x, offsetY: y } = e;

三、整合鼠標進入 <h1> 範圍內的座標

當我們用 console.log(x, y) 組成座標,觀察滑鼠座標的變化,會發現當鼠標移動進 <h1> 範圍內的左上角,座標又會從(0 , 0) 開始變化。

為了方便識別 h1 範圍,將 h1 的背景色暫時改為紅色

問題 :

雖然我們是對 .hero 監聽 mousemove 事件,但 .hero 裡面有包裹一個子層,也就 <h1>,當滑鼠移動到 <h1> 範圍內,e.target 將會改變為子層的 <h1>,回傳的 e.offsetXe.offsetY,也會變成在 <h1> 範圍內的座標。

解決方法:

為了防止鼠標進入 <h1> 範圍後,變成以<h1> 範圍為基準的相對座標,我們可以想辦法偵測當鼠標進入 <h1> 範圍後,就將 <h1> 距離視窗左邊的寬度,以及距離上方的高度,補進座標數值內。如此一來,我們抓到的座標就得以連貫。

如何判斷鼠標進入到 <h1> 範圍內了?

因為我們是對 .hero 監聽 mousemove 事件,所以 shadow() 函式內的 this 會指向 .hero

e.target 會隨著鼠標移動至 .hero<h1> 的範圍,而有所改變。

所以我們可以用 if(this !== e.target) {} 來判斷,當 e.target 不等於 .hero,就表示此時鼠標已經移進 <h1> 的範圍內。

四、將滑鼠座標數值,轉換為想設定的樣式 px 範圍

1. 宣告 walk 變數,設定範圍

我們希望陰影樣式的位置,變化範圍在 100 px 內

const walk = 100;

2. 計算陰影位置座標數值

  • xWalk 計算原理 :
    x / width ,可以得到「目前 x 座標數值」佔「 .hero 寬度範圍」的百分比,再將它乘上 walk,即可算出滑鼠移動到任意 x 座標時,陰影樣式會變化到距離原字樣多少 px 的位置
    再使用 Math.round() 取得四捨五入後的整數。
  • yWalk 計算原理與 xWalk 相似。
const xWalk = Math.round((x / width * walk));const yWalk = Math.round((y / height * walk));

3. 為 <h1> 加上 CSS 陰影樣式

帶入剛剛計算的 xWalk、yWalk,作為陰影樣式的前兩個設定值。
隨著鼠標移動,xWalk、yWalk 數值也會變化,進而造成陰影位置變化,形成動態效果。

text.style.textShadow = `${xWalk}px ${yWalk}px 0 red`;

五、調整陰影位置

問題 :

此時的紅色陰影樣式,雖然會隨著鼠標變換位置,但效果並非我們所預期。

鼠標移至視窗右下角 :

鼠標移至視窗左上角 :

希望紅色陰影在黑字樣的左上方,但呈現的效果卻是與黑字重疊

因為我們目前的設定,會讓陰影位置的數值變化如下 :

鼠標移至右下角 => 陰影 CSS : text-shadow: 100px 100px 0px red;

鼠標移至左上角 => 陰影 CSS : text-shadow: 0px 0px 0px red;

而我們想要的效果,應該要<h1> 字樣中心為基準點,去做相對位置數值的變化

鼠標移至右下角 => 陰影 CSS : text-shadow: 50px 50px 0px red;

鼠標移至左上角 => 陰影 CSS : text-shadow: -50px -50px 0px red;

解決方法 :

在 xWalk、yWalk 的計算式,分別都再減去二分之一的 walk 值 :

如此一來,陰影位置,就可以以 <h1> 字樣中心點去做變化。

鼠標移至視窗左上角,陰影位置能成功往左上方移動 :

六、加入多種顏色、不同位置的陰影樣式

text-shadow 可設定多層的陰影,將每層陰影設定不同顏色,並在 x、y 軸數值上做一點小變化,讓不同顏色的陰影朝不同方向、位置做變化。

text.style.textShadow = `  ${xWalk}px ${yWalk}px 0 rgba(255,0,255,0.7),  ${xWalk * -1}px ${yWalk}px 0 rgba(0,255,255,0.7),  ${yWalk}px ${xWalk * -1}px 0 rgba(0,255,0,0.7),  ${yWalk * -1}px ${xWalk}px 0 rgba(0,0,255,0.7)`;

就能完成今天範例的效果 :

也可以玩玩不同陰影樣式,例如讓它呈現霓虹燈光暈的效果,並隨著鼠標往不同方向移動 :

text.style.textShadow = `  ${xWalk}px ${yWalk}px 5px white,  ${xWalk}px ${yWalk}px 10px white,  ${xWalk}px ${yWalk}px 15px white,  ${xWalk}px ${yWalk}px 40px #ff6723,  ${xWalk}px ${yWalk}px 70px #ff6723`;

今天的練習就到這邊結束囉~

完整 JS 程式碼

const hero = document.querySelector('.hero');
const text = hero.querySelector('h1');
const walk = 500; // 500px
function shadow(e) {
const { offsetWidth: width, offsetHeight: height } = hero;
let { offsetX: x, offsetY: y } = e;
if (this !== e.target) {
x = x + e.target.offsetLeft;
y = y + e.target.offsetTop;
}
const xWalk = Math.round((x / width * walk) - (walk / 2));
const yWalk = Math.round((y / height * walk) - (walk / 2));
text.style.textShadow = `
${xWalk}px ${yWalk}px 0 rgba(255,0,255,0.7),
${xWalk * -1}px ${yWalk}px 0 rgba(0,255,255,0.7),
${yWalk}px ${xWalk * -1}px 0 rgba(0,255,0,0.7),
${yWalk * -1}px ${xWalk}px 0 rgba(0,0,255,0.7)
`;
}
hero.addEventListener('mousemove', shadow);

--

--

Ivy Ho
IvyCodeFive

"You don't have to be great to start, but you have to start to be great."