2021年4月30日 星期五

大城船麵的好滋味

今天晚上與水某, 菁菁去林森二路與復橫 1 路交叉口附近的大城船麵嚐嚐另一種泰式口味, 水某說之前與女醫師來吃過覺得不錯, 所以帶我們來嚐嚐. 











p5.js 學習筆記 (八) : 動態圖形 (中)

由於篇幅太長, 用鍵盤控制動態圖形繪製的測試分割在此篇. 


2. 利用鍵盤控制繪圖 : 

常用與鍵盤輸入有關的 p5.js 內建變數如下表 : 

 keyIsPressed 鍵盤按鍵是否有任何鍵被按住 (true/false)
 key 鍵盤上最近被按下的按鍵
 keyCode 被按下之按鍵之鍵值 (例如 'A' 為 65, 大寫為 CapsLock)


與鍵盤有關的內建函式如下表 : 


 keyPressed() 鍵盤按鍵被按下時呼叫之函式, 其內容需自訂
 keyReleased() 鍵盤按鍵被放開時呼叫之函式, 其內容需自訂
 keyTyed() 鍵盤按鍵有被按過時呼叫之函式, 其內容需自訂
 keyIsDown(code) 檢查鍵盤碼=code 的按鍵是否被按下 (true/false)


在畫布上顯示所按的鍵也會用到 text() 函式. 只要把內建變數 key 傳給 text() 即可, 設定字型大小則可用 textSize(), 例如 :  



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);
      background(1, 70, 100);
      fill('yellow');
      textSize(24);
      }
    function draw() { 
      background(1, 70, 100);
      text(key, 10, 50);   //在畫布座標 (10, 50) 位置顯示最近被按的鍵
      }
  </script>
</body>
</html>

最近被按的鍵會被存在內建變數 key 裡面, 此例直接將此按鍵顯示於畫布上, 結果如下 :



也可以將程式碼寫在 keyPressed() 或 keyTyped() 中, 這樣 draw() 就不需要內容了, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);
      background(1, 70, 100);
      fill('yellow');
      textSize(24);
      }
    function keyPressed() {   //按下按鍵時被呼叫
      background(1, 70, 100);
      text(key, 10, 50);
      }
    function draw() {
      }
  </script>
</body>
</html>

此例將繪製按鍵的程式碼移到 keyPressed() 函式中, draw() 是空的, 每按下按鍵就會呼叫 keyPressed() 將其鍵之值顯示在畫布中, 結果如下 :




改用 keyTyped() 也是 OK 的, 在下面範例中還加上顯示 keyCode, 例如 :


<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);
      background(1, 70, 100);
      fill('yellow');
      textSize(24);
      }
    function keyTyped() { 
      background(1, 70, 100);
      text(key, 10, 30);
      text(keyCode, 10, 50);     //顯示按鍵之編碼 
      }
    function draw() {
      }
  </script>
</body>
</html>

結果如下 : 




在官網教學文件中有一個範例是在 text() 中使用 key 與 keyCode 變數, 再用 print() 函式將值嵌入變數中, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);
      background(1, 70, 100);
      fill('yellow');
      textSize(24);
      }
    function keyTyped() {
      background(1, 70, 100);
      text(`${key} ${keyCode}`, 0, 50);  //嵌入變數
      print(key, ' ', keyCode);
      }
    function draw() { 
      }
  </script>
</body>
</html>

注意, text() 中所嵌入之變數不是一般的單引號, 而是與 ~ 鍵共用的 ` 鍵. 結果如下 :




但這個方式卻不會顯示特殊鍵, why? 

按鍵的編碼其實就是 ASCII 碼, 可在下面的網頁中按鍵盤取得按鍵之編碼資料 :


對於鍵盤上的特殊鍵, p5,js 有為其定義常數來代表其編碼, 如下表所示 :


 特殊鍵 說明
 BACKSPACE 倒退鍵 (8)
 DELETE 刪除鍵 (46)
 ENTER 輸入鍵 (13)
 RETURN 輸入鍵 (13)
 TAB 定位鍵 (9)
 ESCAPE 跳脫鍵 (27)
 SHIFT Shift 鍵 (16)
 CONTROL Control 鍵 (17)
 CAPSLOCK 大小寫切換鍵 (20)
 ALT Alt 鍵 (18)
 UP_ARROW 向上鍵 (38)
 DOWN_ARROW 向上鍵 (40)
 LEFT_ARROW 向上鍵 (37)
 RIGHT_ARROW 向上鍵 (39)


下面範例是利用上下左右鍵來移動原本在畫布中央的圓 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    var x=100;    //圓心 x 座標
    var y=100;    //圓心 y 座標
    var dx=5;      //圓心 x 座標移動量
    var dy=5;      //圓心 y 座標移動量
    function setup() {
      createCanvas(200, 200);
      background(1, 70, 100);
      stroke('yellow');
      strokeWeight(3);
      fill('cyan');
      }
    function keyPressed() {
      if (keyCode===LEFT_ARROW) {x -= dx;}       //按左鍵圓心 x 座標減量
      if (keyCode===RIGHT_ARROW) {x += dx;}   //按右鍵圓心 x 座標增量
      if (keyCode===UP_ARROW) {y -= dy;}           //按上鍵圓心 y 座標減量
      if (keyCode===DOWN_ARROW) {y += dy;}   //按下鍵圓心 y 座標減量
      }
    function draw() {
      background(1, 70, 100);
      circle(x, y, 30, 30);
      }
  </script>
</body>
</html>

此例定義了四個全域變數, (x, y) 是畫布中圓心座標初始值, dx, dy 是每次按上下左右鍵時圓心座標的變化量, 在按這些鍵時會呼叫 keyPressed(), 在此函式中需判斷按下的是上下左右哪一個鍵, 分別用 dx, dy 去調整 x, y 圓心之值, 使原本在畫布心的圓依照按鍵方向移動, 結果如下 :




2021年4月29日 星期四

p5.js 學習筆記 (七) : 動態圖形 (上)

經過前面幾篇測試已熟悉使用 p5.js 繪製靜態圖與重複圖形的方法後, 就可正式進行 p5.js 的互動與動態圖形繪製了, 這部分也是 p5.js 最迷人的地方, 需要一點創意加上數學造詣. 

本系列之前的文章參考 : 


以下測試使用之參考文件 : 

https://p5js.org/reference/ (教學文件)
https://editor.p5js.org (線上編輯器)


1. 利用滑鼠座標移動畫動態圖 : 

製作動態圖形除了會用到 Javascript 的流程控制 (迴圈與分支) 語法以及數學函式外, p5.js 本身也提供了一些與滑鼠座標與按鈕狀態有關的內建變數可資運用 : 


 mouseIsPressed 滑鼠左鍵是否被按下 (true/false)
 mouseButton 被按下的滑鼠按鍵 (LEFT/CENTER/RIGHT)
 mouseX 目前滑鼠的 X 座標
 mouseY 目前滑鼠的 Y 座標
 pmouseX 前一個 frame 之滑鼠 X 座標
 pmouseY 前一個 frame 之滑鼠 Y 座標


常用內建函式如下 (需自行實作函式內容) :


 mouseClicked() 滑鼠按鈕左鍵按下又放開時呼叫之函式, 其內容需自訂
 mousePressed() 滑鼠按鈕左鍵按下時呼叫之函式, 其內容需自訂
 mouserReleased() 滑鼠按鈕左鍵放開時呼叫之函式, 其內容需自訂
 doubleClicked() 滑鼠按鈕左鍵雙擊時呼叫之函式, 其內容需自訂
 mouseMoved() 滑鼠移動時呼叫之函式, 其內容需自訂
 mouseDragged() 滑鼠按鈕左鍵按住拖曳時呼叫之函式, 其內容需自訂


首先來看看 mouseX 與 mouseY, 這兩個變數分別記錄目前滑鼠的 X, Y 座標位置 (px), 可以利用它們來繪製追蹤滑鼠移動之動態圖形, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(200, 200, 200);
      fill('orange');
      }
    function draw() { 
      circle(mouseX, mouseY, 10, 10);   //追蹤滑鼠位置畫圓
      }
  </script>
</body>
</html>

此例直接將目前滑鼠位置傳入 circle() 的 x, y 參數, 當滑鼠在畫布上移動時便會追蹤其位置畫圓, 移動快時圓的間隔大, 反之則間個小, 結果如下 : 




注意, 由於滑鼠座標預設值為 (0, 0), 因此進入 draw() 繪圖迴圈時會固定於左上角畫圓. 要避免這情形可利用 mouseIsPressed 變數偵測滑鼠按鈕狀態來控制繪圖與否. 

內建變數 mouseIsPressed 紀錄目前滑鼠按鈕 (左鍵或右鍵均可) 是否為被按下狀態, 被按下值為 true, 否則為 false, 利用此變數可以控制只有在按住滑鼠按鈕時才追蹤滑鼠位置繪圖, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      fill(230, 30, 90);
      }
    function draw() { 
      if(mouseIsPressed){     //滑鼠按鈕按下時才會畫圓
        circle(mouseX, mouseY, 10, 10);
        }
      }
  </script>
</body>
</html>

此例利用偵測 mouseIsPressed 的狀態只有在滑鼠左鍵或右鍵被按下時才會畫圓, 結果如下 :




可見不論是按下滑鼠左鍵或右鍵均能在畫布上追蹤滑鼠位置畫圖, 如果要限制只能在按下滑鼠左鍵時才繪圖可利用內建變數 mouseButton 來判斷, 其值有 CENTER/LEFT/RIGHT 三個, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      fill(230, 30, 90);
      }
    function draw() { 
      if(mouseIsPressed){
        if(mouseButton == LEFT){    //若被按住之滑鼠按鈕為左鍵才繪圖
          circle(mouseX, mouseY, 10, 10);
          }
        }
      }
  </script>
</body>
</html>

此例在偵測滑鼠按鈕是否被按下的判斷裡添加偵測被按下的是否為左鍵 (LEFT) 之判斷, 這樣只有在按住滑鼠左鍵時才繪圖, 按住右鍵無作用, 結果如下 : 




上面範例中追蹤滑鼠位置所繪製的動態圖形會因為移動太快速而使得線條間隔太大而不連續, 解決此問題的辦法是使用內建變數 pmouseX 與 pmouseY, 它們會記錄前一次圖框的滑鼠 (x, y) 座標位置, 因此若改用 line(x1, y1, x2, y2) 來繪製移動曲線, 傳入pmouseX 與 pmouseY 作為 (x2, y2), 可使線條更具連續性, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      stroke('orange');
      strokeWeight(10);
      }
    function draw() { 
      line(mouseX, mouseY, pmouseX, pmouseY);   //繪製連續性線條
      }
  </script>
</body>
</html>

此例追蹤滑鼠目前位置 mouseX 與 mouseY, 於呼叫 line() 函式時利用 pmoseX 與 pmouseY 將滑鼠目前坐標與前一圖框時之坐標間繪製直線, 從而使線條具有連續性, 結果如下 :




當然也可以加上滑鼠按鈕狀態來控制只有在按下滑鼠左鍵時畫圖, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      stroke('orange');
      strokeWeight(10);
      }
    function draw() { 
      if(mouseIsPressed){
        if(mouseButton == LEFT){  //只有在按住滑鼠左鍵時畫圖
          line(mouseX, mouseY, pmouseX, pmouseY);
          }
        }
      }
  </script>
</body>
</html>

此例用 mouseIsPressed 偵測滑鼠是否有按下, 有的話再用 mouseButton 變數判斷是按下左鍵還是右鍵, 只有在按下左鍵時才畫線, 結果如下 :




上面範例中線條的寬度為固定, 可用內建函式 dist(mouseX, mouseY, pmouseX, pmouseY) 取得前後圖框時間間隔滑鼠移動距離 (px), 用此距離作為線條之寬度, 則可產生移動快時線條變粗, 移動慢時線條變細的效果, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      stroke('orange');
      strokeWeight(10);
      }
    function draw() { 
      d=dist(mouseX, mouseY, pmouseX, pmouseY);   //傳回目前滑鼠座標與前一圖框時之距離
      strokeWeight(d);    //用距離作為畫筆粗細
      line(mouseX, mouseY, pmouseX, pmouseY);
      }
  </script>
</body>
</html>

此例用 dist() 來計算前後圖框時間差內滑鼠移動距離並將其設為畫筆粗細, 結果如下 :




線條細的地方滑鼠移動得慢, 粗的地方則是移動快速. 這樣對畫筆粗細改變速度似乎反應太快, 可以用 easing (緩衝係數) 來將粗細的劇烈變化緩和下來, 這個自訂變數 easing 值在 0~1 之間, 當 easing=1 時為完全緩衝, easing=0 時為無緩衝, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    var easing=0.03;   //緩衝係數 (0~1)
    var x=0;    //調整後的目前滑鼠 x 座標初始值
    var y=0;    //調整後的目前滑鼠 y 座標初始值
    var px=0;   //調整後的前一圖框滑鼠 x 座標初始值
    var py=0;   //調整後的前一圖框滑鼠 y 座標初始值
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      stroke('orange');
      strokeWeight(10);
      }
    function draw() { 
      x += (mouseX - x) * easing;   //利用緩衝係數給 x 座標增量
      y += (mouseY - y) * easing;   //利用緩衝係數給 y 座標增量
      d=dist(x, y, px, py);    //計算調整後滑鼠座標 (x, y) 與前一圖框 (px, py) 座標距離
      strokeWeight(d);    //設定畫筆粗細
      line(x, y, px, py);   //於調整後滑鼠座標與前一圖框座標間畫線
      px=x;   //更新前一圖框滑鼠 x 座標
      py=y;   //更新前一圖框滑鼠 y 座標
      }
  </script>
</body>
</html>

此例以自訂的全域變數 easing 來調整目前滑鼠之座標, 並在結束每次 draw() 圖框迴圈前將其設定為調整後的前一圖框座標, easing 越小緩衝效果越明顯, 亦即滑鼠移動速度對畫筆粗細的影響越小 (越不敏感), 結果如下 : 




接下來測試一個較特別的內建函式 mouseClicked(), 此函式會在按一下滑鼠左鍵時被呼叫, 函式內容須自行定義 :

function mouseClicked() {
  //處理程序
  }

下面範例利用此函式在按下滑鼠左鍵時於畫布上畫四條直線, 第五次清空畫布回到初始狀態 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    var count=0;   //紀錄滑鼠左鍵被按過的次數
    var clicked=false;   //紀錄滑鼠左鍵被按下一次
    function mouseClicked(){   //自訂內建函式內容
      clicked=true;       //紀錄滑鼠左鍵被按下一次
      ++count;      //增量滑鼠被按下次數
      }
    function setup() {
      createCanvas(400, 300);
      background(1, 70, 100);
      strokeWeight(30);
      stroke('orange');
      }
    function draw() { 
      if(clicked){    //滑鼠剛被按過
        switch(count){   //依據累計被按次數畫不同的直線
          case 1 :
            line(0, 0, width, height);      //左上到右下
            break;
          case 2:
            line(width, 0, 0, height);      //右上到左下
            break;
          case 3:
            line(0, height/2, width, height/2);       //中間水平
            break;
          case 4:
            line(width/2, 0, width/2, height);       //中間垂直
            break;
          default:
            background(1, 70, 100);      //超過 4 次畫布初始化計數器歸零
            count=0;
          }
        clicked=false;
        }
      }
  </script>
</body>
</html>

此例自訂兩個全域變數 count 與 clicked, 並實作了內建函式 mouseClicked(), 每按一次滑鼠右鍵 count 會增量 1, 而 clicked 會被設為 true, 表示滑鼠左鍵有被按一次. 在 draw() 迴圈中會先檢查 clicked, 若為 true 表示滑鼠左鍵剛被按了一次, 用 switch case 分支依據 count 的值依序繪製四種不同的直線, 每畫一條線就將 clicked 重設為 false. 若 count 值超過 4, 表示四條線都畫過了, 這時就會進入 default 將畫布背景重刷, 並將計數器歸零, 結果如下 :




2021年4月26日 星期一

向肥大叔學料理

最近訂閱了肥大叔的料理頻道, 他的影片風格很特別, 節奏明快不囉嗦, 而且介紹的都是簡單易做的家常食譜, 所以我打算每個禮拜天都來挑一道肥大叔的食譜進行料理實驗, 精進家常廚藝. 

肥大叔的料理教學從不多說廢話, 吹完哨子立刻進入主題, 只有 "再見什錦炒麵" 這一集例外, 這道菜是回應觀眾留言而做, 留言者的妻子因為車禍離世, 在這之前為家人煮的最後一道菜就是什錦炒麵, 肥大叔根據照片復刻了這道菜, 希望能為這位爸爸加油打氣 :





肥大叔除了廚藝好, 沒想到這一集也有滿滿的溫馨啊!

2021年4月25日 星期日

2021 年第 17 周記事

今天早上被窗外的嗡嗡聲吵醒, 出去一看原來是蜜蜂在蓮霧花上採蜜, 那些花絮不斷地掉落, 敲擊地上的枯葉讓我以為是在下著毛毛雨呢 : 




其實我還可以睡久一點, 但被這些充滿活力的蜜蜂吵醒, 也就睡不下去了. 早上去市集採買完約莫十點天空就開始下起雨來, 這時才突然了解, 原來蜜蜂一早就在忙, 應該是它們知道雨季即將來臨吧?

本周一如上周都在研究 p5.js, 快接近尾聲了, 這兩天就可告一段落, 回到 Python 的學研軌道 (2021 是我的 Python 年, R 排在 2022). 最近有網友在問我 LoRa 的問題, 說實在我只是作了個簡單測試就擱一邊了, 了解真的有限, 雖然這兩年陸續又買了 UART 的 LoRa 板子, 但到現在都還沒拆封測試哩, 是好是壞也不知道. 也很想能有個分身可以進行多工研究, 但人腦畢竟只能單工運作啊. 

"生命最難的地方不是沒人懂你, 而是你不懂你自己" (尼采).

2021年4月24日 星期六

柴山健行記之猴洞探險

昨天老張邀周六早上去爬柴山, 說峰大師這次要帶我們去探訪猴洞, 我想時間 OK 可趕得上下午回鄉下時程便答應了. 早上八點搭阿琪師的車從大師家樓下出發前往元亨寺 (其停車場免費但要八點後才開門), 然後順著佛寺後方的山路登山. 

很久沒爬柴山了 (上次是去走柴山阿朗壹), 我把爬柴山想得太容易, 山雖不高, 但山上曲徑很多, 即使是老手久久爬一次也可能會鬼打牆在山上迷路. 我們取捷徑攀點小路上長春橋後經良友亭前往七蔓站 (那裏有好喝的奉茶), 先到龍穴拍照再前往猴洞探險, 但這邊路就不好走了, 有些要攀繩或落差較大, 過了龍穴後向山友問路才順利找到猴洞. 

猴洞入口前方兩邊是峭壁, 不怕潑猴來搶劫, 於是我們在洞前休息吃午餐, 阿琪師準備的貝果好豐盛, 吃完已吃不下水果矣. 喝完峰大師的咖啡就起身進去一探猴洞. 

猴洞入口很小須將背包取下爬進去, 但入洞必須戴頭燈, 裡面很暗, 進去後發現內部是一個較寬闊的岩洞, 洞頂有石灰岩凝成的鐘乳石, 盡頭有一大一小出口, 我跟老張從大出口出去發現原來外面曾經路過, 卻不知原來這是猴洞出口 :





離開猴洞後就下山, 但那邊的小路都很像, 走了好久居然又走回之前在找猴洞的路 (就是有鐵欄杆圍住一個很深的坑那邊), 只好再往回走, 碰巧遇到也是迷路的兩位山友, 推敲岔路山壁與樹上的小標記, 才終於找到回長春橋的路. 

哇, 我可真小看了柴山, 一個人去爬有可能會在山上迷路, 特別是越接近傍晚大部分山友已下山, 遇不到人問路, 樹上的標記又不容易辨識, 恐怕會一直繞圈子走不出來啊! 所以最好別獨自登山, 更不要午後登山. 

回到元亨寺才一點半, 下山前往北斗街郭家肉粽吃了碗豬腳湯, 喝了杯紅茶四人便快樂賦歸矣. 

2021年4月23日 星期五

p5.js 學習筆記 (六) : 重複的圖形

過去一周為了進入動態圖形測試複習了 Javascript 語法, 用了這麼久的 Javascript 卻從來沒好好整理過筆記, 趁這機會溫習一下也不錯. 本篇是學習 p5.js 動態圖形之前的暖身, 測試如何用迴圈與分支語法來繪製重複的圖形. 

本系列之前的文章參考 : 


以下測試使用之參考文件 : 

https://p5js.org/reference/ (教學文件)
https://editor.p5js.org (線上編輯器)

Javascript 教學文件參考 :


繪製重複的圖形主要是靠迴圈, 最常用的是 for 迴圈語法 :

for (var i=初始值; i 的條件式; 更新 i 的值) {
    //繪圖敘述
    }

例如 :

for (var i=0; i<100; i++) {  //i 遞增 1
    line(i, 0, i, 100);
    }

或者 :

for (var i=0; i<100; i += 10) {  //i 遞增 10
    line(i, 0, i, 100);
    }

迴圈中可用 if~else 語句控制繪製與否. 


1. 繪製重複的線段 : 

之前在畫直線時採用坐標列舉法逐一填入各線段的起始與終點坐標來繪製, 但如果要繪製具有重複現象的線段, 光是確定坐標值就很費功夫了, 其實這種規則性的圖形可利用迴圈來簡化程式碼與提高程式可讀性, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);      
      background(200, 200, 200);
      stroke('blue');
      }
    function draw() { 
      for(var i=0; i<19; i++){   //繪製 19 條垂直線
        line(10 + i*10, 0, 10 + i*10, 100);   //每條線間隔 10 px
        }
      }
  </script>
</body>
</html>

此例在 200*100 的畫布上繪製 19 條垂直線, 因不含兩邊故為 20-1=19, 迴圈從 0 迭代至 18, 第一條線為 (10, 0, 10, 100), 最後一條為 (190, 0, 190, 100), 結果如下 :



 
如果間隔 10px 畫上水平線即可變成方格 (grid), 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);      
      background(200, 200, 200);
      stroke('blue');
      }
    function draw() { 
      for(var i=0; i<19; i++){
        line(10 + i*10, 0, 10 + i*10, 100);    //繪製 19 條垂直線
        if (i<9) {
          line(0, 10 + i*10, 200, 10 + i*10);  //繪製 9 條水平線
          }        
        }
      }
  </script>
</body>
</html>

此例在上面的垂直線基礎上加上一個 if 分支, 當迭代變數 i 小於 9 時繪製水平線, 因為畫布 y 座標最大為 100, 上下邊線不畫則應畫 9 條線 (i=0~8). 水平線基本上就是將垂直線的 (x, y) 對調, 但是終點的 x 座標是 200, 結果如下 :




只要控制 line(x1, y1, x2, y2) 的座標變數便可以繪製斜線, 例如 : 


測試 1-3 : 繪製斜線 (1) [看原始碼]  

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);      
      background(200, 200, 200);
      stroke('blue');
      }
    function draw() { 
      for(var i=20; i<180; i += 20) {  
        line(i, 20, i + 20, 80);    //繪製斜線
        }
      }
  </script>
</body>
</html>

此例透過迴圈變數 i=20~180, 每次遞增 20, 控制線條起始坐標的 x 與終點坐標的 y (向右延伸 20px) 繪製斜線, 結果如下 :




控制坐標增減可繪製左右斜線, 例如 :


測試 1-4 : 繪製斜線 (2) [看原始碼]  

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);      
      background(200, 200, 200);
      stroke('blue');
      }
    function draw() {
      var cnt=0;
      for(var i=20; i<200; i += 20){
        if(cnt < 8) {   //最後一條左斜線不畫
          line(i, 20, i + 20, 80);
          }
        if(cnt > 0 ) {   //第一條右斜線不畫
          line(i, 20, i - 20, 80);
          }
        ++cnt;
        }
      }
  </script>
</body>
</html>

此例透過變數 cnt 控制第一條右斜線與最後一條左斜線不畫繪製柵欄圖形, 結果如下 :




下面是繪製斜率漸變直線的範例 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);      
      background(200, 200, 200);
      stroke('blue');
      }
    function draw() {
      for(var i=0; i<200; i += 10){
        line(i, 0, 1.5*i, 100);      //利用 > 1 倍的終點 x 坐標改變斜率
        }
      }
  </script>
</body>
</html>

此例利用終點 x 坐標的倍數控制直線的斜率, 倍數越大越傾斜, 結果如下 :




下面範例是兩條線段的組合 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 100);      
      background(200, 200, 200);
      stroke('blue');
      }
    function draw() {
      for(var i=0; i<200; i += 10){
        line(i, 0, 1.5*i, 50);          //第一條線段
        line(1.5*i, 50, i, 100);      //第二條線段
        }
      }
  </script>
</body>
</html>

此例兩條線段在畫布中央 (y=50) 相連, 第二條的終點坐標 (i, 100) 向左折回 i 形成 > 的圖形, 調整折回程度 (例如改為 1.2*i, 100) 或中央相接點 (例如 y=40) 都可以改變圖形, 結果如下 : 




2. 繪製重複的圓 : 

在迴圈中繪製重複的圓也可以製作出繁複的圖樣, 主要是利用迴圈不斷移動圓心或直徑, 例如 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 120);      
      background(200, 200, 200);
      noStroke();
      }
    function draw() {
      var r=20;   //半徑
      for(var x=0; x<=width; x += 2*r){  //圓心 x 軸每次向右移一個直徑
        fill('navy');                 //填滿色彩
        circle(x, 0, 2*r);         //y 軸第一個圓
        circle(x, 2*r, 2*r);      //y 軸第二個圓
        circle(x, 4*r, 2*r);      //y 軸第三個圓
        circle(x, 6*r, 2*r);      //y 軸第四個圓
        }
      }
  </script>
</body>
</html>

此例使用一層 for 迴圈循 x 軸向右移動來繪製圓形, 每次迴圈在 y 軸上繪製 4 個圓 (不重疊 y 坐標向下增量一個直徑, 0, 2r, 4r, 6r), 注意, 為了剛好能在 y 軸放入三個圓, 畫布的高度調整為 120px, 結果如下 :




事實上這個圖案也可以用兩層 for 迴圈來做, 程式會更簡潔, 例如 : 



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(200, 120);      
      background(200, 200, 200);
      noStroke();
      }
    function draw() {
      var r=20;
      for(var x=0; x<=width; x += 2*r){     //第一層 : 圓心 x 軸座標
        for(var y=0; y<=height; y +=2*r){   //第二層 : 圓心 y 軸座標
          fill('tomato');
          circle(x, y, 2*r);    //繪製圓形
          }
        }
      }
  </script>
</body>
</html>

此例使用兩層迴圈來繪圖, 迴圈中只要呼叫一次 circle() 即可, 與上例一次畫四個 y 軸方向的圓不同, 此處是先沿著 x 軸一個一個畫, 再沿著 y 軸往下畫, 結果與上例一樣 (僅顏色不同) : 




下面是我在書上看到的一個輻射的圓範例加以改寫 : 


測試 2-3 : 繪製重複的圓 (3) [看原始碼]

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>p5.js test</title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
  <script>
    function setup() {
      createCanvas(400, 300);      
      background('cyan');
      }
    function draw() {
      for(var x=20; x<=width-20; x += 20){
        for(var y=20; y<=height-20; y +=20){
          stroke(102, 102, 102, 50);      //設定線條透明度避免右半面覆蓋圓之效應太明顯
          line(x, y, width/2, height/2)    //畫布中心至圓心之直線
          stroke('black');  
          fill('white');          
          circle(x, y, 10);
          }
        }
      }
  </script>
</body>
</html>

此例使用兩層迴圈在最內層先畫圓再畫一條連接畫布中心至圓心的直線, 由於當 x >= width/2 之後圓會被其他的線覆蓋, 因此設定線的透明度來避免覆蓋太明顯, 結果如下 :




市圖還書 2 本

本周市圖還書 2 本 (都被預約了) : 
蘇松泙的書很多人借, 我買的第一集, 二三集要借都要預約, 不過借來卻沒時間看. No.2 是日本人整理的書, 很不錯, 要再回借. 

2021年4月20日 星期二

用 Anime.js 做網頁動畫

今天在 Youtube 看到 Javascript 動畫函式庫 Anime.js 的介紹 : 









記得十餘年前小狐狸們還在讀小學時我也找到一個同樣名為 Anime 的動畫製作軟體, 那時對動畫興致很高卻沒有時間學習 (買了一些 Flash 的書同樣也是摸幾下就束之高閣), 今天看到這個 Anime.js 真的感到非常親切, 也很感慨, 原來時光飛逝如此之快.  

Anime.js 是一套在網頁上製作動畫的開放原始碼函式庫, 目前已發展到 v3.2.1 版, 參考 :


其函式庫可在 GitHub 的 lib 目錄下載, 原始版 anime.js 約 802 KB, 壓縮版 anime.min.js 約 375 KB, 算是很輕量的函式庫 :


將 anime.min.js 放在專案目錄下, 直接嵌入網頁中即可使用. 網頁中需搭配動畫中欲操作的目標元素 (targets), 可以是下列幾種 :
  • div 元素
  • DOM 節點或其串列
  • CSS 選擇器 (常用 .classname 與 #id)
  • Javascript 物件
  • 以上之陣列組合
這些 targets 通常需要 CSS 配合來製作物件的外觀 (大小, 色彩, 邊框, 外型等), 最重要的是呼叫 anime() 並傳入設定物件進行初始化 :

var animation=anime({
  //properties
  });    

模板網頁如下 :

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Anime.js Test</title>
  <script src="anime.min.js"></script>
  <style>
    div {
      width: 100px;
      height: 100px;
      background-color: cyan;
      }
  </style>
</head>
<body>
  <div></div>
  <script>
    var animation=anime({   //初始化
      targets: 'div',
      //settings
      });
  </script>
</body>
</html>

也可以用 jsdelivr.net 提供的 CDN 服務 :


CDN 模板網頁如下 : 

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Anime.js Test</title>
  <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
  <style>
    div {
      width: 100px;
      height: 100px;
      background-color: green;
      }
  </style>
</head>
<body>
  <div></div>
  <script>
    var animation=anime({
      targets: 'div',
      //settings
      });
  </script>
</body>
</html>

以上模板只是指定網頁中的 div 元素為目標, 並未指定動畫屬性, 因此僅依此 div 元素之 CSS 設定在網頁中顯示一個 100*100 px 的 cyan 顏色方塊. 

Anime.js 的動畫效果主要是靠傳入 anime() 的屬性物件來設定, 除了 targets 屬性外, 還有下列三種屬性 :
  • properties (屬性) :
    與 CSS, Javascript, DOM, SVG 等動畫設定相關之屬性, 例如 translateX, borderRadius 等.
  • property parameters (屬性參數) :
    例如 duration, delay, easing 等
  • animation parameters (動畫參數) :
    例如 direction, loop 等 
參考 : 


例如 : 


測試 1 : 移動的球 [看原始碼]  

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Anime.js Test</title>
  <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
  <style>
    .circle {
      width: 100px;
      height: 100px;
      background-color: green;
      border-radius: 50%;
      }
  </style>
</head>
<body>
  <div class="circle"></div>
  <script>
    var animation=anime({
      targets: '.circle',
      translateX: 300
      });
  </script>
</body>
</html>

此例用 class=circle 的樣式將 div 元素裝扮成一個直徑 100px 的綠色圓球, 透過 translateX 屬性將此球往右 (X 軸) 移動 300 px, 結果如下 : 




此例 taget 為 class, 下面這個範例則用 CSS 選擇器直接指定 div 元素 :



<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="cache-control" content="no-cache">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <title>Anime.js Test</title>
  <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
  <style>
    div {
      width: 100px;
      height: 100px;
      background-color: cyan;
      }
  </style>
</head>
<body>
  <div></div>
  <script>
    var animation=anime({   //初始化
      targets: 'div',                  //目標 div 元素
      translateX: 100,            //移動 100 px
      borderRadius: 50,         //變形邊緣半徑
      duration: 2000,             //動畫時間 (ms)
      easing: 'linear',              //線性變形
      direction: 'alternate'      //返回恢復原狀
      });
  </script>
</body>
</html>

此範例設定了一個為時 2 秒的變形動畫, 100*100 px 的正方形會向右線性移動變成圓形, 再返回原點回復方形, 結果如下 : 




嗯, 看來 Anime.js 也很好玩, 但因為 p5.js 還沒測完, 所以先起個頭就好, 有空再繼續玩唄. 在正式測試之前要先把線上文件看完, 參考 :