iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 16
0
Modern Web

JS30 錄系列 第 16

Day 16 - Mouse Move Shadow

緣起

還記得 Dudi 嗎? 在 Day 12 , Dudi 始終沒有接收到來自家鄉的老公的回應,為此她倍感消沈,每日借酒澆愁,好不愉快。隨著酗酒的日子長了, Dudi 開始對周遭的人聲稱時常看到殘影,這個現象已經嚴重影響到她的工作。她的事業合夥人看不下去了,帶她去給醫生看,醫生們紛紛搖頭,不得其解。

直到後來,遇到了一名宣稱可以根治此症狀的名醫,但他缺少一樣工具協助他診斷。醫生需要一個可以模擬 Dudi 眼中看到殘影現象的頁面,來協助他分析!

於是, Dudi 的事業合夥人找上了我們...

任務目標

在一個頁面上有一行字,字有多個殘影,我們希望殘影能夠隨著滑鼠的移動而改變相對位置。範例連結

作法

下面是HTML,外層類別 hero 代表作用域,滑鼠進到這裡面時,才會讓殘影與滑鼠互動。內層元素 h1 就是我們要產生殘影的字。其中, contenteditable 屬性讓使用者能夠改動其內容。

<div class="hero">
  <h1 contenteditable>Dudi 喝醉酒...</h1>
</div>

接著看 JS 程式碼,步驟大致如下:

  • 設立監聽器,讓每次滑鼠在 hero 內移動時,會呼叫自訂函式來控制陰影位置
  • 自訂函式控制陰影的步驟為:
    1. 記錄滑鼠在 hero 內的座標位置
    2. 計算滑鼠相對於字體中心的位移
    3. 將計算出的位移轉換為陰影移動的距離
    4. 以計算結果更新文字陰影位置

看起來不難,一項一項來吧! 首先建立監聽器與自訂函式。

const hero = document.querySelector('.hero');
const text = document.querySelector('h1');

function shadow(e) {
  //  控制陰影位置
}

hero.addEventListener('mousemove', shadow);

接下來是自訂函式的內容,要想讓陰影位置隨著滑鼠而動,首先要計算滑鼠位置。

function shadow(e) {
  // 記錄滑鼠位置在 `.hero` 內的位置
  let {offsetX: x, offsetY: y} = e;
}

這裡必需用到回傳的事件物件,所以函數內要設參數 e ,負責接收回傳的事件物件。

事件物件中, offsetXoffsetY 屬性負責記錄滑鼠在 target 中的位置。 target 上個章節有略提到,是指觸發該滑鼠事件的元素。

滑鼠在 .hero 區塊上時, target.hero 元素, 但當滑鼠移動到 h1 上時, target 就會更改為 h1 元素。做為參考點的元素改變了,因此 offsetXoffsetY 的位置也會跟著改變。為了補償此改變,必須對滑鼠在 h1 上的狀況做修正,程式碼如下:


function shadow(e) {
  // 記錄滑鼠位置在 `.hero` 內的位置
  let {offsetX: x, offsetY: y} = e;
  // 若滑鼠不在 `.hero` 上,修正座標
  if(this !== e.target) {
    x += e.target.offsetLeft;
    y += e.target.offsetTop;
  }
}

修正的邏輯為,計算 h1 原點位置與 .hero 原點位置的距離,補償給滑鼠在 h1 上的 offsetXoffsetY

這裡提一下 {offsetX: x, offsetY: y} = e 這串,這是 ES6 新的變數指定值的方法,稱為解構賦值 (Destructuring Assignment)。相當於下列程式碼:

let x = e.offsetX;
let y = e.offsetY;

接著要計算滑鼠相對於字體中心的位移,然後轉換為陰影移動的距離。程式碼如下:

// 自訂的最大陰影移動距離
const walk = 100;

function shadow(e) {
  // 記錄滑鼠位置在 `.hero` 內的位置
  let {offsetX: x, offsetY: y} = e;
  // 若滑鼠不在 `.hero` 上,修正座標
  if(this !== e.target) {
    x += e.target.offsetLeft;
    y += e.target.offsetTop;
  }
  
  // 記錄 hero 元素的總長、總寬
  const {offsetWidth: width, offsetHeight: height} = hero;
  // 計算相對位移,轉換為陰影位置
  const xWalk = Math.round((x / width - 0.5) * walk);
  const yWalk = Math.round((y / height - 0.5) * walk);
  // 陰影擴散範圍
 const shadowLevel = (Math.abs(xWalk) + Math.abs(yWalk)) / 2 / walk * 30; 
}

在此例,字體中心位置在 .hero 元素的正中心,因此算出滑鼠位置在 .hero 元素內的長度比例後,扣掉 0.5,即為兩者相對位移。

該位移乘上最大陰影移動距離,即為滑鼠在該位置時陰影的距離。陰影擴散範圍則隨個人喜好計算。

最後更新文字陰影位置,程式碼如下:

function shadow(e) {
  // 前略
  text.style.textShadow = `${-xWalk}px ${-yWalk}px ${shadowLevel}px rgba(0, 0, 0, 0.5), 
    ${xWalk * 3}px ${yWalk * 3}px ${shadowLevel}px rgba(0, 0, 0, 0.3)
  `;  
}

textShadow 前兩個參數代表陰影位置,第三個參數代表陰影擴散距離,第四個參數代表陰影顏色。另外,使用逗號讓我們可以指定多重陰影。

如此一來, Dudi 眼裏所看到的多重景象得以完整呈現,透過醫生的分析,證明了 Dudi 純粹只是酒喝太多罷了!接受飲酒戒斷後的 Dudi 重獲新生,也放下了她的過去,開始了新的生活,這都要感謝大家的支持,謝謝你們, Dudi 會更好。

以上是 JS30 第十六篇!

Reference

Destructuring Assignment

Text Shadow


上一篇
Day 15 - Local Storage
下一篇
Day 17 - Sort Without Articles
系列文
JS30 錄30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言