ItIron2022 Javascript最近開始迷上做佛系瑜伽,教學影片中其中都會穿插一些冥想的玩意,隨著導師的聲音深呼吸、吐氣,這樣的放鬆過程挺有意思的,今天就讓我們做一個超簡單的冥想頁面讓你可以專注在呼吸放鬆上吧!
今天的成品應該是目前以來最為簡單的一個了,我們要做的僅是一個用文字與簡單的動畫引導你呼吸吐氣的玩意而已,javascript的部分也是短短幾行就可以完成了! 主要重點在於css的動畫處理以及垂直置中的技巧上,用輕鬆的心情一起來完成這個小專案吧!

> Step1: 專案結構
一樣請你先建立index.html, style.css & script.js檔案,並在html & css檔案填入以下的內容,bg的圖片請至文末的專案repo那邊下載,我這邊就不再提供囉!
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Relaxer</title>
    <link rel="stylesheet" href="./style.css" />
    <script src="./script.js" defer></script>
  </head>
  <body>
    <h1>Relaxer</h1>
    <div class="container" id="container">
      <div class="circle"></div>
      <p id="text"></p>
      <div class="pointer-container">
        <span class="pointer"></span>
      </div>
      <div class="gradient-circle"></div>
    </div>
  </body>
</html>
@import url("https://fonts.googleapis.com/css?family=Montserrat&display=swap");
* {
  box-sizing: border-box;
}
body {
  background: #224941 url("./bg.jpg") no-repeat center center/cover;
  min-height: 100vh;
  color: #fff;
  font-family: "Montserrat", "sans-serif";
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0;
}
h1 {
  margin-top: 40px;
}
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  margin: auto;
  height: 300px;
  width: 300px;
  position: relative;
  transform: scale(1);
}
.circle {
  background-color: #010f1c;
  height: 100%;
  width: 100%;
  border-radius: 50%;
  position: absolute;
  top: 0;
  left: 0;
  z-index: -1;
}
.gradient-circle {
  background: conic-gradient(
    #55b7a4 0%,
    #4ca493 40%,
    #fff 40%,
    #fff 60%,
    #336d62 60%,
    #2a5b52 100%
  );
  width: 320px;
  height: 320px;
  border-radius: 50%;
  position: absolute;
  z-index: -2;
  top: -10px;
  left: -10px;
}
.pointer {
  background-color: #fff;
  border-radius: 50%;
  height: 20px;
  width: 20px;
  display: block;
}
.pointer-container {
  position: absolute;
  top: -40px;
  left: 140px;
  width: 20px;
  height: 190px;
  animation: rotate 7.5s linear forwards infinite;
  transform-origin: bottom center;
}
@keyframes rotate {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
}
/* add animations for js trigger */
.container.grow {
  animation: grow 3s linear forwards;
}
@keyframes grow {
  from {
    transform: scale(1);
  }
  to {
    transform: scale(1.2);
  }
}
.container.shrink {
  animation: shrink 3s linear forwards;
}
@keyframes shrink {
  from {
    transform: scale(1.2);
  }
  to {
    transform: scale(1);
  }
}
包含bg.jpg檔案,你的專案結構目前應該是這樣的。

畫面上看起來也很正常,先忽略那個轉來轉去的小圓球。

我們css檔案中有幾個點你需要注意一下
 
> Step2: 完成js檔案
雖然我們這次要做的邏輯相當簡單,但還是養成好習慣,動手前先好好想一想吧!
注意到我們上方css檔案中有個rotate的動畫,也就是讓小球繞著轉的那個動畫,我們在css設定中讓它每7.5秒轉一圈,因此我們在利用js讓整個大圓圈放大縮小時也要特別注意這個時間。根據我們之前在conic-gradient的比例規劃(可參照上方的圓餅圖),呼吸、屏氣與吐氣的比例應該為40%:20%:40%,也就是2:1:2,以7.5秒為一圈的話便是3秒、1.5秒與三秒!
最困難的地方就在這邊結束了,我們現在要做的事情就很簡單囉,我們需要照以下的邏輯處理
因此整個js的結構會是這樣的
選取整個圓圈元素與中間顯示文字的元素
宣告相關時間變數
  總長: 7.5秒
  屏氣: 1/5 * 總長
  吸氣: 2 * 屏氣
建立觸發呼吸動畫的函數,其中函數內結構為
  加入grow class讓整個容器放大,中間顯示請吸氣文字
  吸氣時間後將文字改為屏氣,並利用屏氣時間建立新的計時器
  屏氣後加入shrink class讓容器縮小,中間顯示請吐氣文字
  
呼叫第一次的觸發呼吸動畫函數
建立定時器每總長時間便執行以上函數
理解以上結構後就可以把以下程式碼寫入js檔案中囉!
const container = document.getElementById("container");
const text = document.getElementById("text");
const totalTime = 7500;
const holdTime = totalTime / 5;
const breatheTime = holdTime * 2;
function breatheAnimation() {
  text.innerText = "Breathe in!";
  container.className = "container grow";
  setTimeout(() => {
    text.innerText = "Hold it!";
    setTimeout(() => {
      text.innerText = "Breathe out!";
      container.className = "container shrink";
    }, holdTime);
  }, breatheTime);
}
breatheAnimation();
setInterval(breatheAnimation, totalTime);
完美! 一切順利的運作,雖然簡單但看起來卻挺有一回事的對吧?
今天我們做了一個很簡單的冥想專案,重點反而在css的處理上(垂直置中的部分我強烈新手們去研究一下,方法真的非常多,而且很常考),js的部分唯一複雜的地方是setTimeout & setInterval的使用,實際上js的部分是可以再做優化的,setTimeout這樣層層包裝其實可讀性並不高,是有辦法透過promise & async/await去改寫的,但這就超出這系列文的範圍,就交給有心人自己研究囉!
文章中的範例程式碼可以在這邊取得,歡迎自行取用
Danny,我在面試過程中不免俗地被問到期待的薪水,這種問題我該怎麼回答啊?
來了,難度最高的問題之一,確實是個好問題!這邊僅是我個人主觀的想法,你可以作為參考但不要當作教科書來用 :D 首先你必須先知道市場上的行情,這點在一開始的文章中我們就有提到目前jr市場的薪資區間,有這樣的基本概念後我們再來談薪水。
我個人在談薪水的時候會習慣以年薪為單位去跟對方談,畢竟有不少公司是月薪普普但靠獎金跟年終把整包薪資撐起來的,用年薪會更方便對方去操作,也比較不會因為月薪的期待差太多而錯過一些機會。接著就是要喊多少年薪的問題,我知道新人喊價是很困難的事情,但你可以在心裡根據求職地區以及個人期待先規劃一個可接受的區間,比方說年薪60~80,雖然大概率對方會直接用你的底線區間做核薪的依據,但畢竟也是你能接受的數字,之後再透過面試的過程去調整你的區間,尤其當你實際拿到數份offer後你會更理解自己的價碼,可能會覺得之前預設的數字太低/太高,這時候就可以在之後的面試過程去調整。
薪資協議是一種談判,手持offer的情況要談判自然會更輕鬆一點,你也可以用既有的offer去做competitive offer強制拉高對方的價碼,但我會建議一切誠實,不要為了把價錢提高而虛報數字,弄得不好最後兩邊都打水漂就好笑了,這是一門藝術,我自己也還在學習!
不實際出去面試你很難知道自己現在到底值什麼價碼,就放膽去試吧! 真的沒有正確的答案,一切因人因地而異!
本文章同步發布於個人部落格,有興趣的朋友也可以來逛逛~!