pug 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/pug/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Tue, 17 Aug 2021 13:21:46 +0000 zh-TW hourly 1 https://wordpress.org/?v=6.2.2 https://creativecoding.in/wp-content/uploads/2022/03/cropped-cct-logo-icon-2-32x32.png pug 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/pug/ 32 32 Vue.js入門:製作iOS風格的動態月曆與待辦清單網頁 https://creativecoding.in/2021/08/16/vue-js-ios%e9%a2%a8%e6%a0%bc%e5%8b%95%e6%85%8b%e6%9c%88%e6%9b%86%e8%88%87%e5%be%85%e8%be%a6%e6%b8%85%e5%96%ae%e7%b6%b2%e9%a0%81/ Mon, 16 Aug 2021 02:17:00 +0000 https://creativecoding.in/?p=1380 手機裡的動態月曆.只消一指就可以增加或是刪除行程,有想過要怎麼在網頁上實現嗎?老闆利用簡單的動態網頁範例和步驟解說,帶你一步步踏進Vue.js的世界。

這篇文章 Vue.js入門:製作iOS風格的動態月曆與待辦清單網頁 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
使用 vue 製作 ios 動態月曆與待辦清單
使用 vue 製作 ios 動態月曆與待辦清單

本文翻自 [週四寫程式系列] – 來做 IOS 動態月曆結合待辦行程吧!直播影片,若是想要老闆手把手帶你飛可以跟著影片做,這邊也附上這次成品歡迎大家一起動腦動手做。

這次老闆要帶大家來做個 ios 動態月曆與待辦清單,本篇會提到行事曆與待辦清單的畫面切版,並利用這個專案來學習使用 vue 的資料與畫面綁定。

礙於直播時間,這次專案僅會實現以下功能:產出假的月份資料來展示、換算農曆、偏移天數、切換不同天觀看該天工作項目、新增工作、刪除工作、工作項目排序以及 vue 的進出場動畫。

完成這次專案後,大家也可以發想還有什麼功能可以加進來,例如月份時間的切換,不同類別的事件項目…等。如果想要了解更詳細的製作流程和其他創作內容,可以去支持老闆的網頁課程哦!

這次直播筆記會帶大家學會以下內容:

  • sass – mixin:可以將重複使用的 CSS 做成工具,減少重複 css 樣式的撰寫。
  • css – flex 排版
  • vue:資料與畫面綁定、生命週期 mounted

製作動態月曆網頁的事前準備

開發環境

開發使用 codepen 線上撰寫程式碼,大家可以先將環境設定成跟老闆一樣,如果想知道較詳細的設定,可以到成品這邊看老闆的設定。

  • html: Pug
  • css: reset
  • js: vue.js

會使用到的 API:

這次專案會使用以下的 api,先重點整理給大家,不清楚的地方,可以透過後面跟著老闆操作,了解每個 api 使用時機。

vue – javascript

  • el:資料要綁定的區塊
  • data:vue 要綁定的資料放置處
  • mounted: vue 的生命週期, el 被掛載之後會執行裡面的程式碼
  • methods:使用到的 function 放置處
  • computed:計算屬性,會因為 data 內的值改變,而跟著變動
// javascript
var vm = new Vue({
  el: '#app',
  data: {
    text: 'Hello World',
    texts: ['H', 'i']
  },
  mounted () {...},
  methods: {
    changeText () { ... }
  },
  computed: {
    showText () { return ...}
  }
})

vue – html:畫面部分會使用到以下內容

  • {{text}}:將資料綁定到畫面中顯示
  • v-model:將資料綁定到畫面中顯示或修改
  • :class:動態綁定屬性,也等於 v-bind:class=””,簡寫為 :class,也可以綁定其他屬性
  • v-for:讓陣列資料重複產生 dom,可以搭配索引值綁定
  • :key:可以搭配 v-for 使用,提供 vue 識別每個 dom 是不同的,在傳入 v-for 的陣列中,key 要是獨特的值,避免識別上出錯。
  • @click=””:當點擊目標物會觸發傳入 click 的內容
// html
#app
  p {{text}}
  input (v-model="text)
  p(v-model="showText")
  div(:class="")
  div(v-for="(item, idx) in texts", :key="idx") {{item}}
  button(@click="changeText()")

vue – transition-group:

vue 提供給在 dom 要被加入、移除或更新時的動態效果,使用方法會在後面實做中解說。若想要參閱官方說明文件可點此閱讀

Javascript

  • Math.random():會產出一個大於等於 0、小於 1 之間的隨機小數。若想要參閱更詳細的說明文件可點此閱讀
  • sort:對一個陣列的所有元素進行排序,並回傳此陣列,可以自訂規則函式做為參數傳入。若想要參閱說明文件可點此閱讀

Sass

mixin,可以將重複使用的 CSS 做成工具, 也能傳入參數、參數名稱以 $ 開頭。使用方法如下,後面實作中也會再帶大家操作一次。

//CSS
@mixin size($w, $h: $w)
  width: $w
  height: $h

css

這次專案會頻繁使用 flex 來排版,以下介紹常見的樣式,實際操作可以透過提供的遊戲及專案製作的過程了解。

  • display: flex:預設會將子層水平排列不斷行。
  • flex-wrap: nowrap | wrap | wrap-reverse 預設為 nowrap,超過寬度的子元素是否換行。
  • flex-direction: row | row-reverse | column | column-reverse 主軸線的方向,預設為橫向 row。
  • justify-content: 元素在主軸上排列的方式。
  • align-items: 元素在副軸上排列的方式。

下面舉例這段 css 會呈現的畫面,同學也可以嘗試看看換成不同數值,只看文件無法理解的話,可以透過遊戲學習 flex 的系列屬性,網路上有非常多的相關遊戲,這邊提供其中一款遊戲 FLEXBOX FROGGY大家可以挑戰一下!

css的flex相關程式碼說明圖
css的flex相關程式碼說明圖

跟著老闆開始動手做

1. 畫面切版

1-1 mixin

同學們在樣式開發中,肯定遇過重複撰寫相同程式碼的情況,如果有方法可以減少這種情況,除了加速開發外,也能讓程式碼更簡潔。Sass 中有一個工具 mixin,就能滿足我們的需求,不用一直重複造輪子,就可以在需要的位置引入,甚至可以傳入參數,傳入的參數要以 $ 符號為開頭,下面兩個 mixin 分別展示,有沒有傳入參數的差異,並教大家如何引用到 Sass 中。

第一個 mixin 工具為設定長寬,傳入的第一個參數會被 mixin 做為 width 的值,第二個參數則為 height,若是沒有傳入第二個參數,則會將第一個參數做為第二個參數傳入。

第二個 mixin 工具為使用 flex 將內容物垂直置中。

// CSS
@mixin size($w, $h: $w)
  width: $w
  height: $h

@mixin flex_center
  display: flex
  align-items: center
  justify-content: center
  flex-direction: column

.box
  +size(100px, 100px) // 也可寫為 +size(100px)
  +flex_center

1-2 vue 資料畫面綁定

雖然是畫面切版階段,但是老闆先帶大家認識 vue 畫面綁定資料的方式,讓大家不用在 html 中,用土法煉鋼的方式將星期分別輸入。

首先我們先創造一個 vue 實體,裡面的 el 值為資料要綁定的區塊,所以將 #app 填入, 這時html 中 id 為 app 的區塊就能進行畫面與資料綁定,data 內的值為資料,可以將要綁定或操作的資料放在此處。

要實現星期的內容能夠與畫面綁定,先在 data 中新增 key 值 tags,內容為每天的字串,在 html 中可以使用 v-for 將陣列的資料綁入,在 html 中可以看到 .tag 的位置後面有使用 v-for,tag 為每一個 tags 資料內的值,雙花括{{tag}}則可以將資料顯示在畫面中。這邊比較有趣的是,因為資料較單純,也可以將 tags 設定為字串去跑 v-for ,結果也會一樣。

// html
#app
  .phone
    .calender
      .head
        .tag(v-for="tag in tags") {{tag}}
      .body
        .daybox.active(v-for="i in 31")
          .infos
            .num {{i}}
            .lunar 初一
          .eventdot
// javascript
var vm = new Vue({
  el: "#app",
  data: {
    tags: ["日","一","二","三","四","五","六"]
    // tags: "日一二三四五六"
  }
})

1-3 畫面樣式切版

開始套資料前,先將畫面準備好,再來做資料與畫面綁定,利用前面的 mixin,將行事曆水平垂直置中在畫面中間,並將 .phone 設定長寬與背景色。.phone 中我們將行事曆分為兩層, .head 顯示日到六的標題, .body 則顯示各天日期、農曆與當天是否有工作項目。

要怎麼做到七個項目就會斷行呢?之前老闆有帶大家使用過 inline-block,應用在這邊會發現到了第六個項目,就會神奇地斷行,這是因為 inline-block 有一些預設樣式,導致父層寬度不夠,就把第七個項目往下推了。所以我們這邊改在父層使用 flex,flex 預設會將子元件併排在同一行,所以要斷行必須要加上 flex-wrap: wrap,當寬度不夠時,子元件就會換行。

至於 .daybox 中有 &.active 則是用來作為被選定時的樣式,該天被選定後,日期與農曆會被變成黑底白字,這邊我們先模擬此樣式,在後面資料綁定後,就能針對特定項目加上樣式。

//CSS
@mixin size($w, $h:$w)
  width: $w
  height: $h

@mixin flex_center
  display: flex
  justify-content: center
  align-items: center
  
html, body, #app
  +flex_center
  +size(100%)
  background-color: #333
  color: #555

.phone
  +size(360px, 560px)
  background-color: #fff

.head
    display: flex
    padding-top: 50px
    padding-bottom: 5px
    .tag
      width: calc(100% / 7)
      text-align: center
      font-size: 12px

.body
  display: flex
  flex-wrap: wrap
  .daybox
    +flex_center
    flex-direction: column
    width: calc(100% / 7)
    text-align: center
    padding: 5px 0px
    &.active
      .infos
        background-color: #222
        border-radius: 50%
        color: #fff
    .infos
      +size(40px)
      .num
        font-size: 20px
        padding-top: 5px
      .lunar
        font-size: 12px
    .eventdot
      +size(6px)
      background-color: #ddd
      border-radius: 50%
      margin-top: 5px
iOS風格動態月曆與待辦事項網頁:步驟一,畫面切版
iOS風格動態月曆與待辦事項網頁:步驟一,畫面切版

2. 選定日期

有了基本畫面,接著要將畫面綁定資料。前面我們天數是使用 v-for 直接針對數字 1~ 31 跑迴圈,這邊調整成在 mounted 時,創造 31 天的資料後存放到新的資料變數 days 中,代入到畫面。mounted 是 vue 生命週期的其中一個階段,會在元素被掛載且 $el(實體) 建立好的時候執行,可以理解成:當要放置內容的 DOM 產生時,執行 mounted 內的程式碼。

再來,我們要實現只會有一天被選定的功能,也就是只會有一天的 daybox 會有 active 的 class (黑色圓圈背景),使用者的互動會是點選不同天時,當天的樣式就會改變。

先改寫 v-for ,在前面的 day 改為 (day, day_idx),這個值就是 day 在 days 中的索引值。並在 data 新增一個 selected 值,記錄目前被觸發的為何者,接著使用 vue 中的語法 @click,當該 daybox 被按下時,將目前被選定的值改為被選定的 day_idx。結合前面學到的動態綁定 class,來將 active 的 class 賦予給 daybox ,也為 daybox 加上手指游標的樣式。就可以達成「當 selected 的值等於該天的 day_idx 時,就會為其加上 active 的 class」。

// HTML
...
.daybox(v-for="(day, day_idx) in days", @click="selected = day_idx", :class="day_idx === selected ? 'active' : ''")
  .infos
    .num {{day.number}}
    .lunar 初一
  .eventdot
// CSS
.daybox
  ...
  cursor: pointer
// javascript
...
data: {
  tags: ["日","一","二","三","四","五","六"],
  days: [],
  selected: 0 // 目前被選定的值
},
mounted() {
  for(var day=1; day<=31; day++){
    var new_day = {
      number: day
    }
    this.days.push(new_day);
  }
}
...
iOS風格動態月曆與待辦事項網頁:步驟二,選定日期
iOS風格動態月曆與待辦事項網頁:步驟二,選定日期

3. 農曆日期換算與偏移天數

接下來的動作,會使用到 vue 的 methods,可以將 methods 的功用理解成,將需要使用的 function 統一管理,需要使用的時候就能直接呼叫來觸發這些 function。

在農曆的日期換算的部分,雖然我們的月曆是假資料,為了仿真,讓這份月曆第一天的農曆,不是從初一開始,我們在 mounted 的一開始,新增一個 lunar 變數,做為農曆的偏移天數。每次 for 迴圈結束前,將 lunar 變數加一,使農曆一直往前推移,農曆數字轉化為國字的規則如下:

  • 如果傳入的 lunar 參數超過 30,用餘數換算成 0~29,確保農曆永遠從初一到三十中循環
  • lunar 小於等於 10,用”初”為開頭,並從國字字串取出特定位置的值組裝在一起
  • lunar 小於 20 時,用”十”為開頭,並從國字字串取出特定位置的值組裝在一起
  • lunar 等於 20,直接顯示二十
  • lunar 小於 30 時,用”廿”為開頭,並從國字字串取出特定位置的值組裝在一起
  • lunar 等於 30,直接顯示三十

我們也製造一個偏移天數 start_day 變數,讓這份月曆的 1 號,不一定從周日開始排序。利用 get_pen(d) 傳入一個偏移天數,讓第一天的 DOM 產生一個 margin-left 的 style。

// HTML
...
.daybox( v-for="(day, day_idx) in days", @click="selected = day_idx", :class="day_idx === selected ? 'active' : ''", :style="get_pan(day_idx)")
  .infos
    .num {{day.number}}
    .lunar {{lunar(day.lunar)}} //農曆換算
  .eventdot
// javascript
var vm = new Vue({
  data:{
    ...
    start_day: 2
  },
  mounted () {
    var lunar = 6; // 農曆偏移天數
    for(var day=1; day<=31; day++){
      var new_day = {
        number: day,
        lunar: lunar, // 傳入的每天資料新增一個紀錄農曆的數字
        events: [],
      }
      this.days.push(new_day);
      lunar++ // 下一天能拿到新的農曆值
    }
  },
  methods: {
    chinese_num(num) {
      var list = "十一二三四五六七八九";
      return list[num];
    },
    lunar(num) { // 換算農曆
      if (num > 30) num = num % 30;
      if (num <= 10) {
        return "初" + this.chinese_num(num % 10);
      } else if (num < 20) {
        return "十" + this.chinese_num(num % 10);
      } else if (num == 20) {
        return "二十";
      } else if (num < 30) {
        return "廿" + this.chinese_num(num % 10);
      } else if (num == 30) {
        return "三十";
      }
    },
    get_pan(id){ // 第一天的偏移位置
      if (id==0){
        return { "margin-left": "calc( "+this.start_day+" * 100% / 7)"};
      }
    },
  },
  ...
})
iOS風格動態月曆與待辦事項網頁:步驟三,農曆日期換算與偏移天數
iOS風格動態月曆與待辦事項網頁:步驟三,農曆日期換算與偏移天數

4. 產生每天工作項目

工作內容的資料會頻繁使用 js 原生語法 – Math.random() 來製作。Math.random() 在沒有輸入參數時,會隨機產生 0~1之間的小數。利用這點,可以拿來製作當天是否有工作、工作量、工作項目、時間、工作類型。

  1. 是否有工作:新增每天資料時,利用 random 產出的數字,達成機率性決定當天有沒有工作。若設定小於 0.4 ,則代表有 4/10 的機率當天會有工作,繼續跑下面產出工作內容的程式碼,否則 events 就是空陣列。目前階段有沒有工作只會決定該天有沒有小灰點,後面會介紹每天的工作清單如何製作。
  2. 工作量: Math.ranodm() * 3,會產出 0~3間的隨機小數,將產出的數值代入以下的 for 迴圈,就會隨機產出 0~3 項的工作。
  3. 工作項目、工作類型:將所有工作項目的陣列代入,並使用前面的產出的單日工作量的整數值作為 id ,去取得工作項目陣列中對應的工作名稱。這邊要注意,random 值除了要轉為整數外,也要等於工作項目陣列的長度,避免程式碼取到 undefined 的值。
  4. 時間:使用 Math.random() 產出時間字串。時間顯示的格式,老闆設定如下: 小時的區塊為 0~24 小時,中間組合 : 符號,分鐘的部分避免程式碼太過複雜,在前面先創造分鐘變數,利用 parseInt 讓分鐘的值只會有四種 0, 15, 30, 45。要注意的是,如果只有 0,我們希望呈現的會是兩個 0 ,所以在這邊用三元運算子判斷,如果 minute 的值為 0,則在前面多補一個 0。

此時,31 天的資料順利產生,利用 events 是否有內容,判斷要不要呈現小灰點,畫面上也能使用資料來動態綁定 class,當該天存在工作項目時,則為 eventdot 加上 has_eventclass。再開發時,同學也可以使用 codepen 中的 console 工具,檢查該天是不是有工作內容,方法為 vm.days[日期].events

// HTML
...
.body
  .daybox( v-for="(day, day_idx) in days", @click="selected = day_idx", :class="day_idx === selected ? 'active' : ''", :style="get_pan(day_idx)")
    .infos
      .num {{day.number}}
      .lunar {{lunar(day.lunar)}} // 農曆換算
    .eventdot(:class="{'has_event': day.events.length > 0}") // 當天是否有工作
// CSS
...
.eventdot
  +size(6px)
  background-color: #ddd
  border-radius: 50%
  margin-top: 5px
  opacity: 0 // 當天沒工作的樣式
  &.has_event
    opacity: 1 // 當天有工作的樣式
// javascript
...
mounted() {
  var lunar = 6;
  for (var day = 1; day <= 31; day++) {
    var new_day = {
      number: day,
      lunar: lunar,
      events: []
    };

  if (Math.random() < 0.4) { // 機率性決定當天有沒有工作
    var count = parseInt(Math.random() * 3); // 新增0~3項工作
      for (var o = 0; o < count; o++) {
        var minute = parseInt(Math.random() * 4) * 15; // 產出 0, 15, 30, 45
        new_day.events.push({
          title: ["整理房間丟垃圾", "出門參加活動", "打包行李"][parseInt(Math.random() * 3)], // 從工作陣列中,隨機取一個值
          time: parseInt(Math.random() * 24) + ":" + (minute == 0 ? "0" : "") + minute // 如果分鐘數為 0,則補一個 0 為開頭
        });
      }
    }
    this.days.push(new_day);
    lunar++;
  }
},

5. 待辦事項與項目排序

完成日曆的顯示後,要開始製作當天的工作項目清單 todo_list 。我們會使用 vue 的另外一個功能 computedcomputed 無法傳入參數,會因為 data 內資料變動,動態改變回傳的結果,也就是將資料代入後重新計算的屬性。當使用者選擇不同天,當天如果有工作內容,就會顯示該天的工作項目。

使用 data 內的 days 陣列與 selected ,能夠取得目前選擇的當天資料,這邊要注意的是,因為一開始 days 長度是 0,取當天資料會失敗,所以要多使用 if 判斷式,在資料還沒創造完畢前,避免回傳的值出錯。

取得當天資料後,我們想把工作項目依照時間排序,因為創造工作項目時,時間是隨機產生,所以畫面工作項目順序是隨機排列的,利用 js 的原生語法 sort 來進行時間排序。sort 內可以傳入一個函式做為規則,我們傳入一個 function 作為比較的規則,將傳入的兩個值進行比較,將不必要的分號取代成空值後,時間會變成四位數的字串來做比較。

todo 的畫面樣式如下,因為篇幅原因,先不針對不同工作項目給予不同樣式,如果同學想嘗試,做法與前面相同,只要對每一筆工作多設定一個值來記錄工作類別,針對不同的工作類別,給予不同的樣式即可。如果在製作上有問題,可以參考文章中附上老闆的成品,了解詳細的做法。

// HTML
#app
  .phone
    .calender
      .head ...
      .body ...
      .todos
        .item(v-for="(todo,id) in current_items", :key="todo")
          .time {{todo.time}}
          .title {{todo.title}}
// CSS
.head,.body
  border-bottom: solid 1px rgba(black,0.1)
  background-color: #f7f7f7

.body
  padding-bottom: 10px
  ...

.todos
  .item
    padding: 3px 10px
    display: flex
    height: 40px
    border-bottom: solid 1px rgba(0,0,0,0.1)

    .time, .title
      padding: 4px 10px

    .time
      width: 55px
      border-right: solid 2px
      border-color: #3ca9f2
// javascript
...
computed: {
  current_day(){
    return this.days[this.selected];
  },
  current_items(){
    var day=this.current_day;
    if(!day)
      return null;
    else
      return day.events
	        .sort((a,b)=>(parseInt(a.time.replace(":",""))-parseInt(b.time.replace(":","")) ));
  }
}
iOS風格動態月曆與待辦事項網頁:步驟五,待辦事項與項目的排序
iOS風格動態月曆與待辦事項網頁:步驟五,待辦事項與項目的排序

6. todo 新增與移除工作

除了顯示隨機產生的工作項目外,我們希望也能在每天的工作項目中產生新工作,或是刪除不需要的工作。

新增資料的部分,先在畫面產出兩個 input 區塊,來輸入工作名及時間,並新增一個 button 按鈕,來做最後的新增按鈕。在成品中,老闆還有放入工作類型的選擇,大家也可以挑戰看看。

新增資料需要注意的部分是,由於新增的資料是傳物件參考進去,所以要新增新的資料時,已經加進去的也會被移動,所以我們將物件字串化之後,需要再把它轉回物件,確保傳入的資料是一組全新的。

刪除資料部分,我們只要在 .item 中新增一個 div,當點擊它時,會從目前的 now_events,使用 splice 刪除被點選的該筆資料。

// HTML
#app
  ...
  .phone
    .calender
      ul.todos
        .item(v-for="(todo,id) in now_events", :key="todo")
          .time {{todo.time}}
          .title {{todo.title}}
          .close_btn(@click="now_events.splice(id,1)") x // 刪除工作項目按鈕
  .form
    input(name="title" v-model="newtodo.title" placeholder="標題")
    input(name="time"  v-model="newtodo.time" placeholder="時間")
    button(type="submit" @click="add_item") +
// CSS

.form
  box-sizing: border-box
  padding: 10px
  position: absolute
  bottom: 0px
  width: 100%
  left: 0
  +flex_center
  flex-direction: row
  input,select
    box-sizing: border-box
    margin-right: 10px
    padding: 5px 10px
    border-radius: 2px
    min-width: 150px
    height: 30px
    color: white
    background-color: transparent
    border: none
    border: solid 1px white
  input[name='title']
    width: 300px
// javascript
...
data: {
  ...
  newtodo: {
    title: '',
    time: ''
  }
},
methods: {
  ...
  add_item(){
    this.days[this.selected].events.push(JSON.parse(JSON.stringify(this.newtodo)));
  }
},
computed: {
  now_events(){
    var day = this.days[this.selected_day];
    if (day)
      return day.events;
    else
      return [];
    console.log();
  },
  current_day)_{
    ...
  }
}
iOS風格動態月曆與待辦事項網頁:步驟六,待辦事項的新增與移除
iOS風格動態月曆與待辦事項網頁:步驟六,待辦事項的新增與移除

7. 新增刪除工作加上動態

最後一步,要在執行新增或刪除時能加上動態,會使用到 vue 的 transition-group,使用方法如下:

ul.todos 改寫成 transition-group.todos,屬性中 tag=”ul” ,會將這層 DOM 換成 ul 使用。name 為要賦予的動態名稱,mode 為動畫進出方式,這邊使用的是舊的先離開,新的再進來。使用上需要注意,因為 transition-group 跑動態是群組進來群組出去,所以每個子元件都要獨一無二 key,需要在子層加上 key,讓 vue 辨識出每個 DOM 都是獨特的,避免產生奇怪的動畫。

接著來解說 name 裡面填寫的動態要怎麼使用,只是寫上 name 並不會有動態,我們要依照 vue 的規格,在 sass 中加上動畫的名稱,分別會使用到:

  • fade-enter-active, fade-leave-active:準備要進入的時候,準備要離開的時候
  • fade-enter, fade-leave-to:進來之前和離開之後的狀態
// HTML
...
transition-group.todos(tag="ul", name="fade", mode="out-in")
  .item(v-for="(todo,id) in now_events", :key="todo")
    .time {{todo.time}}
    .title {{todo.title}}
    .close_btn(@click="now_events.splice(id,1)") x
// CSS
.fade-enter-active, .fade-leave-active
  transition: 0.5s
.fade-enter, .fade-leave-to
  opacity: 0
iOS風格動態月曆與待辦事項網頁完成圖
iOS風格動態月曆與待辦事項網頁完成圖

老闆來結語

讓我們快速回顧一下製作動態日曆的流程:

  1. 行事曆的靜態資料切版、使用 vue 將星期名稱綁定到畫面中的方式
  2. 選定特定日期後,用 vue 動態改變屬性
  3. 農曆日期換算與偏移、天數的偏移:達成該份資料的第一天不是初一,也不是當月1號就從周日開始排序
  4. 使用 Math.random() 隨機產出每天的工作項目
  5. 待辦事項切版與項目依照時間排序
  6. 新增待辦事項與移除待辦事項
  7. 使用 vue – transition-group 新增與移除動作的動態

萬事起頭難,一個作品不可能一步到位,將最終目標拆分成不同階段任務,從一開始的雛型慢慢開發出每個區塊,最後組裝在一起,也可以加上個人的創意去實現其他功能,讓作品更豐富。

礙於直播時間,老闆沒有將所有功能都實現,但製作方式與前面提到的內容雷同,大家可以挑戰看看,例如工作項目多一個類別屬性,在不同類別時顯示不同的樣式,也可以發想其他功能沒提到的功能,例如月份時間用真實的時間去換算、切換年月份功能等…。

再附上這次範例的成品,讓大家在開發時參考。

如果你喜歡老闆的教學,歡迎加入老闆開的課程中一起學習,順便支持一下老闆,課程會帶你看看不一樣的作品,並引導大家一步步完成作品,透過每次的賞析、實作到修正作品,讓寫 code 不再是這麼困難的一件事情,將這個過程想像成,拿一隻比較難的畫筆在進行創作,如果有機會使用它,便能夠在網頁上做出與眾不同的創作。

動畫互動網頁程式入門(HTML/CSS/JS)以簡單例子帶你入門網站的基礎架構及開發,用素材刻出簡單有趣又美觀的網頁和動畫,享受做出獨一無二的網頁所帶來的成就感,在職場上與設計師和工程師合作無間。

打好基本的互動網頁基礎之後,可以進階動畫互動網頁特效入門(JS/CANVAS),紮實掌握JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。長達3085分鐘,超過60個精緻範例與400張的投影片以上,以及四個加碼單元vue-cli、GSAP、D3、Three.js的投影片,成為hahow上最長的課程。

此篇直播筆記由幫手 H 協助整理

墨雨設計banner

這篇文章 Vue.js入門:製作iOS風格的動態月曆與待辦清單網頁 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
Vue.js入門:完成懷舊的井字圈叉遊戲動態網頁 https://creativecoding.in/2021/08/10/vue-js-%e6%87%b7%e8%88%8a%e7%9a%84%e4%ba%95%e5%ad%97%e5%9c%88%e5%8f%89%e9%81%8a%e6%88%b2%e5%8b%95%e6%85%8b%e7%b6%b2%e9%a0%81/ Tue, 10 Aug 2021 03:33:00 +0000 https://creativecoding.in/?p=1354 小時候都玩過圈圈叉叉的遊戲,隨手畫個井字就可以與玩伴鬥智,這次我們要利用Pug、Sass及Vue.js製作出這款動態小遊戲網頁啦!

這篇文章 Vue.js入門:完成懷舊的井字圈叉遊戲動態網頁 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
想要探索 Vue 前端框架嗎?本次的圈圈叉叉範例可以帶你一同進入 Vue 世界中,認識前端框架的強大與方便之處。圈圈叉叉是我們每個人小時候都有玩過的遊戲,規則簡單易上手,在本次範例中會實作出以下功能:

  1. 透過 Vue 產生九宮格的框框,當玩家點擊時會顯示出圈圈叉叉
  2. 遊戲過程中系統會不斷比對資料,找尋是否有贏家產生,當有贏家產生時會顯示出哪一方勝利
  3. 可將遊戲畫面清除,重新開始進行一局新的遊戲
  4. 顯示當下該輪到哪一位玩家進行出手

在開始製作之前你該知道的 Js 操作與 Css 屬性

Js操作:

  • filter:篩選出符合贏家條件的資料
  • map:處理九宮格資料時的陣列的轉換
  • reduce:可將數字相加後,用於贏家判斷

Css排列版面的利器flex

  • display: flex:預設會將子層水平排列不斷行
  • flex-wrap: nowrap | wrap | wrap-reverse 預設為 nowrap,超過寬度的子元素是否換行
  • flex-direction: row | row-reverse | column | column-reverse 主軸線的元素排列方向,預設為橫向順排 row
  • justify-content: 元素在主軸上排列的方式
  • align-items: 元素在副軸上排列的方式

事前準備

Code Pen上開一個新的pen,將HTML的預處理器設定成Pug、CSS的預處理器設定成Sass、JS的CDN掛入Vue。

1. 用CSS建立外框線以及圈圈圖示

首先建立名稱為 size 的 mixin,可以讓後續在使用上比較方便,像是繪製框線。

//HTML
.block
//CSS
@mixin size($w, $h:$w)
  width: $w
  height: $h

.block
  +size(150px)
  border: solid 1px

接著要來建立框線以及繪製圈圈的圖示,在繪製圈圈圖示上,會使用擬元素 beforeafter,這裡可以嘗試在 content 中加上符號來顯示看看效果。不過如果沒有要顯示字元的話,像是本次範例所要的是畫幾何圖形,一樣需要加上 content,雙引號中間空白無文字,不可直接省略。

//HTML
.block
.block.circle
//CSS
@mixin size($w, $h:$w)
  width: $w
  height: $h

.block
  +size(150px)
  border: solid 1px
  &.circle
    &:after, &:before
      content: ""
      display: block

不管是圈圈與叉叉,都是由 :after:before 這兩個偽元素左組成,在圈圈上,是兩個圓形疊在一起所產生視覺效果,透過將中間的圓圈所設定的顏與背景顏色相同,這樣看上去可產生甜甜圈形狀也就是圓形的效果。

這邊要注意的是,由於在圖層的排列順序上,:after 是在 :before 之上,所以比較大的圓是要寫在 :before 上,而中間的小圓則是寫在 :after 上。

在圓形的置中設定上,先將 :after:before 設定成絕對定位,而由於絕對定位的位置規則是會透過尋找上層非 static 的區塊,當作定位的起始點。為了使 .circle 當成定位起的點,所以在母元素 .circle 加上相對定位 position: relative。接著以左上角為參考點,向右以及向下各移動 50% ,再向上以及向左移動本身一半的長度 (transform: translate(-50%, -50%)),這樣一來就完成置中了。

//CSS
$color_blue: #46f
$color_red: #f35
$color_bg: #222

@mixin size($w, $h:$w)
  width: $w
  height: $h

html, body
  background-color: $color_bg
  margin: 0
  +size(100%)

.block
  +size(150px)
  border: solid 1px

  &.circle
    position: relative
    &:after, &:before
      content: ""
      display: block
      border-radius: 50%
      position: absolute
      left: 50%
      top: 50%
      transform: translate(-50%, -50%)
    &:before
      +size(90%)
      background-color: $color_red
    &:after
      +size(60%)
      background-color: $color_bg
井字圈叉遊戲:步驟一,建立外框線及圈圈
井字圈叉遊戲:步驟一,建立外框線及圈圈

2. 繼續使用CSS完成叉叉圖示

完成圈圈後,接著來做叉叉。

為了測試方便,所以就先以滑鼠移入區域,也就是 :hover 的方式來寫叉叉的效果。叉叉是由兩條長方形所組成的,所以將 size 的比例改成長方形,也將圓角設定變為 0px。

將長方形個別旋轉正負 45 度,形成交錯的叉叉,這邊可以發現,除了旋轉 (rotate) 之外,也保留了在圓形中所設定的移動 (translate)。原因在於,transform 會複寫掉前面的屬性,為了達到與圈圈一樣的效果,所以也必須指定移動的效果。

此時滑鼠移入有叉叉的效果了,但是切換時很生硬,要達成柔順切換的效果,只要在 &:after, &:before 上加上transition: 0.5s 就可以了。

//CSS
&:after, &:before 
  content: ""
  display: block
  position: absolute
  left: 50%
  top: 50%
  transform: translate(-50%, -50%)
  transition: 0.5s   // 圈叉變化時間 0.5秒
&:hover  // 與 .circle 同一層級
  &:after, &:before
    +size(90%, 15%)
    background-color: $color_blue
    border-radius: 0px
  &:before
    transform: translate(-50%, -50%) rotate(45deg)
  &:after
    transform: translate(-50%, -50%) rotate(-45deg)
井字圈叉遊戲:步驟二,滑鼠hover移上會轉成叉叉
井字圈叉遊戲:步驟二,滑鼠hover移上會轉成叉叉

3. 調整圈叉的共用CSS屬性

在叉叉測試測試完畢後,由於剛剛 hover 只是用來測試,使用 hover 模擬沒問題後,就將其改為 .cross ,並在 html 新增 .cross 的元素。

//HTML
.block
.block.circle
.block.cross
//CSS
.block
  +size(150px)
  border: solid 1px
  position: relative
  &:after, &:before //change the after and before position
    content: ""
    display: block
    position: absolute
    left: 50%
    top: 50%
    transform: translate(-50%, -50%)
    transition: 0.5s
  &.circle
    &:after, &:before
      border-radius: 50%
    &:before
      +size(90%)
      background-color: $color_red
    &:after
      +size(60%)
      background-color: $color_bg
  &.cross 
    &:after, &:before
      +size(90%, 15%)
      background-color: $color_blue
      border-radius: 0px
    &:before
      transform: translate(-50%, -50%) rotate(45deg)
    &:after
      transform: translate(-50%, -50%) rotate(-45deg)
井字圈叉遊戲:步驟三,調整圈叉的共用CSS屬性
井字圈叉遊戲:步驟三,調整圈叉的共用CSS屬性

4. 進入 Vue.js 世界,綁定建立的樣式

在正式進入 Vue.js 的世界前,我們先模擬使用 jQuery的情況 ,在Console中嘗試新增以及拿掉元素上的 class。

//Console
// 抓取 .block.circle,有 circle 這個 class 就將其拿掉
$(".block.circle").toggleClass("circle")
// 抓取 .block,無 circle 這個 class 就加上去
$(".block").toggleClass("circle")

假如使用 jQuery 來寫的話,首先會先建立一個長度為 9 的陣列 (var blocks = [1,0,-1] ),裡面放置的數字分別代表每一個格的狀態,比如說 1 代表圈圈、-1 代表叉叉、0 代表空,有了這些一串數字後,再根據數字去新增與刪減 Class。

這樣可以達到效果沒錯,但是如果可以當資料更新的時候,外觀也就自動更新,不需要自己再去新增與刪除 Class 的話,會是更好的方法,而我們接下來要使用的 Vue 便可以完成這部分。

在使用 Vue 的起手式上,首先需要一個溝通橋梁,類似搬運工的角色,名為 vm,接著要定義作用的範圍,設定為 el: "#app",而在 HTML 最外面一層上加上 #app。這樣就連接完畢,接下來在 Vue 中所做的資料更新都會做用到整個 HTML 上。

接下來要做的功能是,根據在 Js 中的資料,來判定在 HTML 上的元素要不要加上相對應的 Class。先處理 Vue 的部分,由於所設定的是資料相關,所以要擺放在 data{} 之中,裡面新增一個物件, key 為 blocks,value 為 { type: -1 }

接著來到 HTML,在切換 Class 上,需要 v-bind 這個標籤,它是用來綁定與參數相關的,像是Class、href 就會使用它。後面雙引號部分則是來決定要加上的 Class 以及可否能加的條件 ("{ Class : 成立條件 }"),這裡的 Class 不僅可設定一個,也可以是多個,只需要以逗號相隔開來即可。所以下方 HTML 中所代表的是,如果 type 是 1 的話,那就加上圈圈,而如果 type 是 -1 的話,那就加上叉叉。大家可以利用Console嘗試更新 blocks: { type: -1 } 中 type 數字(輸入 vm.blocks.type=1或0或-1),來觀察外觀上的變化。

//JS
var vm = new Vue({
  el: "#app",
  data: {
    blocks: { type: -1 },
  }
});
//HTML
#app
  .block(v-bind:class="{ circle: blocks.type == 1, cross: blocks.type == -1 }")
  //刪除先前的.block.circle及.block.cross

5. 用 Vue.js 的 Array 產生九個框框

在前一個步驟,完成了最左上角的單一方塊在外觀與資料的連結,但是圈圈叉叉是九宮格,所以老闆使用 Array.from 來產生九個 { type: 1 } 並存放到 Blocks 中,讓 Blocks 是長度為九的陣列。

接著我們來到了 HTML,回想起當需要存取陣列的每一個元素時,就使用迴圈,而迴圈中所設置的變數可依據需求自行定義,而在 Vue 中也是相同的概念,透過 v-for 來存取陣列中的每一個元素,使用方式為 v-for="自訂義名稱 in 陣列" ,由於 blocks 長度是九,所以 Vue 就會在畫面上產生九個.block元素,在這裡老闆使用了 block 來當作識別的變數,所以在判斷 Class 上名稱就是使用 block.type == 1,來判斷每一個 Block 所設置的 type 是不是等於 1。

由於現在設定上都是圈圈 (type: 1 ),老闆後來改以隨機變數的方式產生 -1、0、1 ( type: 1 - parseInt(Math.random() * 3) ),這樣一來畫面上不同方格就會各自呈現不同的形狀。

//JS
var vm = new Vue({ 
  el: "#app",
  data: {
    blocks: Array.from({ length: 9 }, function () {
      return {
        type: 1 
      }
    })
  }
});
//HTML
#app
  .block(v-for="block in blocks",
         v-bind:class="{circle: block.type == 1, cross:block.type == -1}")

v-for與v-bind的差別:v-for用來產生陣列裡的多個複製物,並存取或綁定陣列中的各個元素,v-bind:屬性可將符合條件的單個元素綁定屬性

井字圈叉遊戲:步驟五,用 Vue.js 的 Array 產生九個框框
井字圈叉遊戲:步驟五,用 Vue.js 的 Array 產生九個框框

6. 調整HTML及CSS排列成為九宮格

在步驟五,透過 v-for 自動產生九個方塊了,在方塊中也隨機擺放著不同的符號。不過在圈圈叉叉的設置上是水平三格與垂直三格所組成的九宮格,因此要在這組 .block上再新增一個母元素取名為 .block_area,用來規範整個九宮格的大小與位置。我們首先在每個框框中加入編號:

//HTML
#app
  .block_area
    .block(v-for="(block,bid) in blocks",
           v-bind:class="{circle: block.type == 1, cross:block.type == -1}")
      .small_number {{ bid+1 }}

在 css 的設定上,由於裡面的框框預設上的寬高都是 150px,所以在外框的寬度上就乘上 3 倍。在排列方式上使用 flex,在預設上是當母元素也就是 .block_area 加上 flex 後,下層的子元素 .block 就會依序由左向右排列且不換行。為了達成換行的效果,會需要加上 flex-wrap: wrap ,這樣當寬度超過時,子元素就會自己自動往下排列了。

//CSS
.block_area
  +size(150px*3)
  display: flex
  flex-wrap: wrap
井字圈叉遊戲:步驟六,調整HTML及CSS排列成為九宮格
井字圈叉遊戲:步驟六,調整HTML及CSS排列成為九宮格

結果此時會發現還是不太對,確實有往下排列了沒錯,但是卻以兩格兩格的方式排列。原因在於,在預設上每一個元素的寬度 = Set width + border + padding,以現在的框框來說就會是 150px +1px*2 = 152px,所以當排列到第三個框框時就會超出範圍,自然地就會向下排來排列。為了要讓指定的寬度 150px 涵蓋 border 的寬度,那就要再加上 box-sizing: border-box 就可以囉。

//CSS
.block
  +size(150px)
  border: solid 1px rgba(white, 0.2)
  position: relative
  box-sizing: border-box
井字圈叉遊戲:步驟六ㄓ之之ㄧ之一之一,調整HTML及CSS排列成為九宮格

7. Vue.js中設定重新開始

要如何設定所有物件重置呢?回想一下在第五步驟中,提到框框中的圖形是根據 type 的數字來做變化的,所以要將畫面清空,僅需要將數值統一設定為零即可。然而將清空畫面屬於動作,是需要視情況不斷執行的,所以要將重置的設定寫在 methods 中。

設定好後,會發現畫面上的方格都消失了,原因在於一開始 Vue 的元件建立後,並不會自動執行寫在 methods 中的函式,為了讓元件建立後就初始化將每個方格方格設定為空,必須在 mounted() 也就是 Vue 初始化剛完成時,執行 restart() 函式。

//JS
var vm = new Vue({
  el: "#app",
  data: {
    blocks: [],
  },
  mounted() {
    this.restart()
  },
  methods: {
    restart() {
      this.blocks = Array.from({ length: 9 }, function () {
        return {
          type: 0
        }
      })
    }
  }
});

8. 點擊後下棋

設定好九宮格初始化的格式後,就要來使用滑鼠點擊。前面有提到,在 Vue.js 中與樣式有關的會使用 v-bind,而點擊是動作,這與事件有關則是使用 v-on。所以要達成當點擊框框內後會呈現圈圈的語法如下 v-on:click="block.type=1",表示當偵測到點擊時,就把 type 設定為 1 ,也就是圈圈的形式。此時會發現不管點擊畫面上哪一位置都會變成圈圈。

//HTML
#app
  .block_area
    .block(v-for="(block,bid) in blocks",
           v-bind:class="{circle: block.type == 1, cross:block.type == -1}"
           v-on:click="block.type=1")
    .small_number {{ bid+1 }}

確定點擊效果沒問題後,接著來製作輪流下棋。輪流就是將 type 一開始設定為 1,再來就是 -1,並接續輪流交替,由於數值的切換是動作,所以透過將 v-on:click 後面包成一個動作 player_go(block),讓設定的細節到 Vue 中函式做處理。每當有方塊偵測到點擊時,就會傳送當下被點擊方塊設定 type,並且會交替的更換 turn 的數值,讓 turn 在 1 與 -1 兩者之間交替。可以注意到,在 data 中新增了資料 turn: 1,代表遊戲開始是由圈圈這方開始,若遊戲要從叉叉開始,那只要改為 turn: -1 就可以囉。

//HTML
#app
  .block_area
    .block(v-for="(block,bid) in blocks",
           v-bind:class="{circle: block.type == 1, cross:block.type == -1}"
           v-on:click="player_go(block)")
      .small_number {{ bid+1 }}
//JS
var vm = new Vue({
  el: "#app",
  data: {
    blocks: [],
    turn: 1
  },
  mounted() {...
  },
  methods: {
    restart() {...
    },
    player_go(block) {
      block.type = this.turn
      this.turn = -this.turn
    }
  }
});

9. 顯示下棋者

在與九宮格同一的層級之下,新增一個 .block.small 的元素,另外會在綁定 vue 中的資料 turn,這樣一來加上相對應的 Class。另外,由於這只是作為提示框而非遊戲操作部分,所以將格子縮小會比較好看一些。

//HTML
#app
  .block_area
    ...
  .block.small(v-bind:class="{circle: turn == 1, cross: turn  == -1}")
//CSS
.block
  +size(150px)
  border: solid 1px rgba(white, 0.2)
  position: relative
  box-sizing: border-box
  &.small //縮小格子
    +size(60px)
井字圈叉遊戲:步驟九,顯示下棋者
井字圈叉遊戲:步驟九,顯示下棋者

10. 理出邏輯,再用Vue.js判斷贏家

本小節是此範例中最核心也是最有挑戰的的地方:要如何判斷有贏家產生,而贏家又是哪一方呢?以下先用圖示講解觀念,再著手進入程式。

圈圈叉叉井字遊戲判斷贏家

在畫面上,每一格都有相對應的數值,圈圈是 1,叉叉是 -1,而都沒有劃記則是 0,那可以得知只要有某一條線的所有數值相加起來為 3 或是 -3 ,這就代表有連成一線。接著要考慮可連成一線的方式有哪些情況,下面以格子的數字做為表示,一共有八種情況,分別是 123 / 456 / 789 / 147 / 258 / 369 / 159 / 357,每當有玩家點選方格時,便會依序檢查上述八種情況的任一是否有連線成功。

接著在進入撰寫連線的程式前,先來認識在 Vue 中新的小夥伴 – computed,它會自動監看在 Vue 中的數值是否有更動了。當有內部有數值更新時,所設定的屬性也會隨之更新。下面以 user() 作為範例,當數值 turn 改變時, user() 所計算過後回傳的值也會改變,而這個值可放在 HTML 中來使用。

//JS
var vm = new Vue({
  ...
  methods: {
    restart(){...
    },
    player_go(block) {...
    },
    computed:{
      user(){
        return this.turn == 1? "O'turn" : "X'turn"
      }
    }
  }
})
//HTML
#app
  h1 {{ user }}
  ...
井字圈叉遊戲:步驟十,顯示出輪到誰下棋
井字圈叉遊戲:步驟十,顯示出輪到誰下棋

接下來就要開來寫連線判定的部分囉,先將前面所提到的連線情況存成一組字串,接著透過字串分割的方式形成陣列,並回傳數值顯示在畫面上:

//JS
computed:{
  pattern_data(){
    var verify_list = "123,456,789,147,258,369,159,357"
    var result = verify_list.split(",")
    return result
  }
}
//HTML
#app
  h1 {{ pattern_data }}
  ...
井字圈叉遊戲:步驟十,寫贏家判斷列
井字圈叉遊戲:步驟十,寫贏家判斷列

為了可以顯示出每一個格子的序號與相對狀態,所以在原本顯示狀態的 type 之上加上屬性 id,由於程式是由 0 開始,格子的序號是由 1 開始,所以 id = i +1。

//JS
restart(){
  this.blocks = Array.from({ length: 9 }, function (d,i) {
    return {
      id: i+1, // 新增序號 
      type: 0
    }
  })
},

這樣子九宮格所呈現的資料會是如下,總共九個物件,每一個物件都會有相對應 id 以及 type

[ { "id": 1, "type": 0 }, { "id": 2, "type": 0 }, { "id": 3, "type": 0 }, { "id": 4, "type": 0 }, { "id": 5, "type": 0 }, { "id": 6, "type": 0 }, { "id": 7, "type": 0 }, { "id": 8, "type": 0 }, { "id": 9, "type": 0 } ]

回到 pattern_data(),依據剛剛所列出的驗證組合,取出相對應的物件,比如說:

[ { "id": 1, "type": 0 }, { "id": 2, "type": 0 }, { "id": 3, "type": 0 }]

在陣列的轉換上使用 map,以 vtext 來代表每一項驗證的組合。由於是要將長度九的陣列依照條件轉換成各個長度為三的陣列,透過使用 filter 來進行來過濾,過濾的條件則是察看序號是否相同,這裡使用到 indexOf,若有相同數值會回傳 1,否則則會回傳 -1 。

取出陣列組合後,可以點擊看看九宮格,確認 type 的是數值是會有變化的,而這個數值就是用來相加並進行判斷的,再次使用 map 來取出陣列中 type 數值的部分,並且透過 reduce 來計算三個數值相加總合。

//JS
var verify_list = "123,456,789,147,258,369,159,357"
var result = verify_list.split(",")
  .map((vtext)=>{
    var add = this.blocks
      .filter((d, i) => vtext.indexOf(i + 1) != -1)
      .map((d) => d.type)
      .reduce((a, b) => (a + b), 0);
    return add;
  })
return result

下方圖片標示出在做完每一項操作後,所產生出的結果。

井字圈叉遊戲:步驟十,.filter() 過濾出驗證的陣列
井字圈叉遊戲:步驟十,.filter() 過濾出驗證的陣列
井字圈叉遊戲:步驟十,.map() 數值 tpye
井字圈叉遊戲:步驟十,.map() 數值 tpye
井字圈叉遊戲:步驟十,.reduce() 相加數值
井字圈叉遊戲:步驟十,.reduce() 相加數值

目前有個每一個驗證規則的數值加總,但是只需要回傳贏家的判斷即可,也就是 3 (三個圈成一線)與 -3 (三個叉成一線),所以使用到 .filter() ,判斷條件為只要絕對值等於 3 就代表有贏家產生了。除了知道有贏家外,也希望可以知道是哪一條線成立的,所以在回傳值的上面也加上了 rule: vtext

//JS
var verify_list = "123,456,789,147,258,369,159,357"
var result = verify_list.split(",")
  .map((vtext)=>{
    var add = this.blocks
      .filter((d, i) => vtext.indexOf(i + 1) != -1)
      .map((d) => d.type)
      .reduce((a, b) => (a + b), 0);
    return { rule: vtext, value: add }
  })
result = result.filter((obj) => Math.abs(obj.value) == 3)
return result
井字圈叉遊戲:步驟十,贏家條件成立時,顯示驗證結果

11. 在畫面上顯示贏家

有了贏家之後,需要顯示在畫面中,我們在 computed:{} 新增另一個 win_text() 的函式。在預設上 winner = -1,代表沒有任何贏家產生,而當 this.pattern_data.length > 0,則表示有贏家產生數值時,將 winner 的值替換,再依據是正 3 還是負 3 顯示贏家。

//HTML
#app
  h1 {{ win_text }}  // win text
  .block_area
  .block(v-for="(block,bid) in blocks",
         v-bind:class="{circle: block.type == 1, cross:block.type == -1}"
         v-on:click="player_go(block)")
    .small_number {{ bid+1 }}
  .block.small(v-bind:class="{circle: turn == 1, cross: turn == -1}")
//JS
win_text() {
  var winner = -1
  if (this.pattern_data.length > 0) {
    winner = this.pattern_data[0].value
  }

  if (winner == 3) {
    return 'O wins'
  } else if (winner == -3) {
    return 'X wins'
  }
  return (this.turn == 1 ? 'X' : 'O') + "' turn"
}

12. Vue.js中加上「防止每格內的重複點擊」的機制

在贏家產生後,照理來說遊戲就該停止了,但是現在遊戲還是可以不斷進行,原因在於還沒有防止重複點選機制。解決方式很簡單,只需要在點選前加上判斷格子是否為空的 block.type == 0 就可以了。

//JS
player_go(block) {
  if (block.type == 0) {
    block.type = this.turn;
    this.turn = -this.turn;
  } else {
    alert("Not allow")
  }
}

13. 增加按鈕重新開始遊戲

在遊戲結束後,需要清空畫面才能將再次進行遊戲,所以在最下方加上重新開始遊戲的按鈕。前面有提到 Click 動作點擊要搭配的前綴字為 v-on:click ,這可以縮寫成 @click,而清空的功能在步驟七的地方已經寫好了,所以這裡只需要呼叫就可以囉。

//HTML
#app
  h1 {{ win_text }}
  .block_area
    .block(v-for="(block,bid) in blocks",
           v-bind:class="{circle: block.type == 1, cross:block.type == -1}"
           v-on:click="player_go(block)") 
      .small_number {{ bid+1 }}
  .block.small(v-bind:class="{circle: turn == 1, cross: turn  == -1}")
   h2(@click="restart") Restart   // Restart 重新遊戲

14. 最後來CSS裡調整畫面

目前元素都靠向左側,我們希望調整到整個畫面的中心位置對齊,會比較好看。除了 htmlbody 要將子元素#app 排列置中, #app 裡的所有元素也必須排列整齊垂直置中,所以在元素設定上也一同加上了 #app

在排列上使用 flex,前面有提到,flex 預設上是橫列的由左向右排列,但是我們要的是由上而下的垂直排列,所以方向設定上改成 flex-direction: column,在主軸 justify-content 以及 交叉軸 align-items 都設定為置中 center。

//CSS
html, body, #app
  background-color: $color_bg
  margin: 0
  color: white
  +size(100%)
  display: flex
  justify-content: center
  align-items: center
  flex-direction: column
//HTML
h2 Player  // 在顯示輪到誰的圖示前面加上文字 Player
.block.small(v-bind:class="{circle: turn == 1, cross: turn  == -1}")
最後調整CSS,完成遊戲

結語

我們總結一下這次的圈圈叉叉遊戲的範例,製作過程可以分為五個部分:

  1. 以CSS擬元素(:before, :after)的方式繪製圈圈叉叉。
  2. 透過 Vue 的 Array 產生九個框框,並且透過綁定資料,讓畫面可以根據資料顯示對應的樣式。
  3. Restart – 重新遊戲,可在遊戲一開始或是遊戲中點擊 Restart 後清空畫面。
  4. 利用 v-on:click 選寫下棋功能。
  5. computed:{}計算出贏家成立的條件以及在畫面上顯示贏家。

這就是我們用 CSS及Vue.js 寫出來的圈圈叉叉遊戲啦!老闆的成品這邊去,也非常歡迎大家到社團裡跟我們分享你們完成的作品。

動畫互動網頁程式入門(HTML/CSS/JS)以簡單例子帶你入門網站的基礎架構及開發,用素材刻出簡單有趣又美觀的網頁和動畫,享受做出獨一無二的網頁所帶來的成就感,在職場上與設計師和工程師合作無間。

打好基本的互動網頁基礎之後,可以進階動畫互動網頁特效入門(JS/CANVAS),紮實掌握JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。長達3085分鐘,超過60個精緻範例與400張的投影片以上,以及四個加碼單元vue-cli、GSAP、D3、Three.js的投影片,成為hahow上最長的課程。

此篇直播筆記由幫手 阮柏燁 協助整理

墨雨設計banner

這篇文章 Vue.js入門:完成懷舊的井字圈叉遊戲動態網頁 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
利用Pug(HTML)、Sass(CSS)及Vue.js製作動態時間軸年表(直播筆記) https://creativecoding.in/2021/08/04/%e5%88%a9%e7%94%a8pug-sass-vue-js%e8%a3%bd%e4%bd%9c%e5%8b%95%e6%85%8b%e6%99%82%e9%96%93%e8%bb%b8%e5%b9%b4%e8%a1%a8/ Wed, 04 Aug 2021 03:36:00 +0000 https://creativecoding.in/?p=1320 不論個人或是品牌,都需要讓網站造訪者快速暸解你的歷史發展,時間軸年表是一個常見且好用的表示方法。利用Pug(HTML)、Sass(CSS)及Vue.js製作出一個屬於你的動態時間軸年表吧!

這篇文章 利用Pug(HTML)、Sass(CSS)及Vue.js製作動態時間軸年表(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

凡走過必留下痕跡,時間軸年表常應用於紀錄故事和里程碑上,更是構築形象的第一步。不論是用於個人或是品牌,都能幫助網站造訪者能快速暸解關於你的歷史發展,一個常見且好用的表示方法。

我們將以Code Pen做為本次實作的平台,這是一個可以在創作的當下即時看到程式碼運作狀況的線上程式碼編輯器,只要簡單註冊就可以使用囉!

如果想搭配直播影片一起實作,請往這邊走 👉🏻 https://www.youtube.com/watch?v=Ib2YMG56_60

在開始製作年表之前我們需要準備以下步驟:

  1. 資料──也就是呈現在年表上的內容,舉凡文案、年份、圖片等資料,一般是儲存在資料庫內
  2. 版面配置──物件位置關係規劃、色票和尺寸library建置
  3. 插入資料──讀取資料的trigger和loading動畫

在Code Pen上開一個新的pen,將HTML的預處理器設定成Pug、CSS的預處理器設定成Sass、JS的CDN掛入Vue。

《動態時間軸年表》開發環境設置
《動態時間軸年表》開發環境設置

資料準備

首先來準備資料,通常年表會包含這些資料,標題、內容、年份、日期,資料儲存的形式會是每一筆都有自己的年月份,再依照日期順序做排列。命名一個陣列為logs在裡面輸入yearcontent等內容,在content內使用另外一個陣列再做資料渲染彈性會比較大,也可以加入不同的tag區分同年份中的不同事件。

資料的樣式可參考以下範例:

//JS
var logs = [
  {
  year: 2014,
  content: [
    {
      tag: "開始接網頁專案",
    },
    {
      tag: "開始接網頁專案2"
    }
  ]
}]

在console裡面輸入logs,如果出現陣列代表資料準備完成。

《動態時間軸年表》陣列資料準備完成
《動態時間軸年表》陣列資料準備完成

接著我們把資料用ul列表的方式印出來,在JavaScript定義一個新的Vue並設定它的作用範圍、套用的資料為logs。在HTML的ul列表內,把logs印成一筆一筆的li,這時會看到我們原先設定的四筆資料,並且分為year、content等細部內容。

//JS
var vm = new Vue({
  el: "#app",
  data: {
  logs: logs
  }
});
//HTML
#app
  ul
    li(v-for="l in logs")
      h3.year {{l.year}}
      ul.content
        li(v-for="c in l.content") {{c.tag}}
《動態時間軸年表》資料顯示
《動態時間軸年表》資料顯示

由於某些網頁的HTML標籤會帶有原生的CSS,比如剛剛使用的ul自帶圓圈和margin的樣式,我們在codepen引入reset CSS做樣式重置。

延伸閱讀:[CSS] 跨瀏覽器的樣式重置 reset.css & normalize.css

版面配置

樣式重置完後我們就可以來做設計發想啦,時間軸年表的排列比較複雜,可以參考下面的簡易草稿來構思物件之間的位置關係。我們用 dialog_wrapperdialog 分別對應下圖的年份與事件,在HTML包覆相對應的資料。

《動態時間軸年表》設計發想
《動態時間軸年表》設計發想
//HTML
#app
  ul
    li.dialog_wrapper(v-for="l in logs")
      .dialog
        h3.year {{l.year}}
        ul.content
          li(v-for="c in l.content") {{c.tag}}

接下來建置色票和常用尺寸的library,本次使用的色票如下,常用到的寬高也建置 mixin 模組方便快速取用:

//CSS
$color_light_blue: #D4EBE8
$color_dark_blue: #4FBDBC
$color_white: white
$color_yellow: #F4DF38
$color_orange: #F4A373

@mixin size($w, $h: $w) //如果寬高數值一樣,取寬
  width: $w
  height: $h

這邊介紹大家一個好用的語法縮寫網站──Sass cheatsheet,熟練這些語法的話就可以增進切版的速度唷!

在版面設計的部分我們給背景壓上一層淡藍色,然後撐開寬高到100%。接著處理dialog的部分,可以切分為以下幾個元件:

//CSS
body
  background-color: $color_light_blue
  +size(100%)

.dialog
  // dialog本體的樣式設定
  background-color: #fff
  padding: 15px 20px
  cursor: pointer
  
  border-radius: 5px
  box-shadow: 15px 15px $color_dark_blue
  width: 250px
  position: relative
  transition: 0.5s // 漸變動畫較柔和
  
  // 裝飾性小方塊設定
  &:before
    content: ""
    display: block
    +size(20px)
    border-radius: 3px
    position: absolute
    right: -10px
    background-color: $color_white
    transform: rotate(45deg)
  //滑鼠移上去時,方塊往左上方、陰影往右下方移動
  &:hover
    transform: translate(-10px, -10px)
    box-shadow: 20px 20px $color_dark_blue
    
  
  // 標題文字設定
  .year
    font-size: 36px
    font-weight: 700
    margin-bottom: 10px
    letter-spacing: 2px

如果對於偽元素的運用不是那麽地熟悉,可以參考這篇文章──CSS 偽元素 ( before 與 after )

接著長出 timeline 讓他在畫面中上下左右置中。根據我們上方的草稿, dialog 的位置其實是由 dialog_wrapper 的相對關係所決定的,所以給予一些高度後在 dialog_wrapper 上增加 position: relative ,在 dialog 上改為 position: absolute

//HTML
#app
  ul.timeline //加個.timeline
    li.dialog_wrapper(v-for="l in logs")
      .dialog
        h3.year {{l.year}}
        ul.content
          li(v-for="c in l.content") {{c.tag}}
//CSS
#app
  display: flex
  align-items: center
  justify-content: center

.timeline
  height: 100vh
  width: 6px
  background-color: rgba($color_white, 0.4)
  padding-top: 50px

.dialog_wrapper
  height: 160px
  position: relative

.dialog
  ...
  position: absolute
《動態時間軸年表》製作時間軸以及事件外框樣式
《動態時間軸年表》製作時間軸以及事件外框樣式

那要如何讓 dialog 左右交錯排列呢?我們可以在 dialog_wrapper 裡面把它分為偶數和單數,用語法 :nth-child 選擇第 2n2n+1 個,可以暫時設定不同的顏色有助於判別。接著調整 dialogtimeline 的距離,記得 left 的值會優先於 right ,所以在偶數排設定 left: initial ,再透過偽元素 &:before 調整偏右對話框的小尾巴。

//CSS
.dialog_wrapper
  ...
    &:nth-child(2n+1)
    background-color: blue
    .dialog
      left: 40px
      &:before
        left: -10px
  &:nth-child(2n)
    background-color: red
    .dialog
      right: 40px
      left: initial

.dialog
  ...
  right: 0
《動態時間軸年表》左右交錯排列
《動態時間軸年表》左右交錯排列

接著利用 dialog_wrapper 的偽元素做出時間軸上的圓圈點,我們的時間軸年表樣式大致上完成囉。

//CSS
.dialog_wrapper
  height: 160px
  position: relative
  &:before
    content: ""
    display: block
    +size(20px)
    border: solid 5px white
    border-radius: 50%
    left: 50%
    transform: translateX(-40%)
    left: 0
    top: 0px
  ...

插入資料

新增用來插入資料的 button ,修改初始時 logs 為空值,定義他的 methodsinitial 時動態等於一開始所定義的 logs

//HTML
#app
  button.initial(@click="initial") 插入資料
  ul.timeline
  ...
//CSS
button.initial
  position: fixed
  right: 50px
  bottom: 50px
  background-color: $color_dark_blue
  color: white
  border: none
  border-radius: 5px
  padding: 5px 10px
  font-size: 16px
  cursor: pointer
//JS
//將JavaScript兩段程式碼濃縮成為一個
var vm = new Vue({
  el: "#app",
  data: {
    logs: []
  },
  methods: {
    initial(){
      this.logs=[];
      this.logs=[
        {
          year: 2014,
          content: [
            {tag: "開始接網頁專案",
            }
          ]
        },
        {
          year: 2015,
          content: [
            {tag: "成立墨雨設計工作室",
            }
          ]
        },
        {
          year: 2016,
          content: [
            {tag: "開設動態互動網頁程式入門",
            }
          ]
        },
        {
          year: 2017,
          content: [
            {tag: "開設動態互動網頁特效入門",
            }
          ]
        }
      ]
    }
  }
  
});
《動態時間軸年表》完成視覺設計
《動態時間軸年表》完成視覺設計

以目前及時出現的效果來說其實有些粗糙,所以我們來加上一些loading時的動畫提升質感吧!這次使用Vue官方的效果Transition Group,使用方法為在HTML套用官方已寫好CSS效果的class就好囉,記得也要把語法在CSS複製貼上唷!這樣點「插入資料」的按鈕,就可以看到進場的動畫了。當然也可以搭配設計的CSS互動動畫,這部分就留給大家發揮空間、腦力激盪一下~

//HTML
#app
  button.initial(@click="initial") 插入資料
  transition-group.timeline(tag="ul",name="fade")
    li.dialog_wrapper(v-for="l in logs", :key="1")
      .dialog
        h3.year {{l.year}}
        ul.content
          li(v-for="c in l.content") {{c.tag}}
//CSS
...
.fade-enter-active, .fade-leave-active
  transition: .5s
  transform: translateY(0px)
  
.fade-enter, .fade-leave-to
  opacity: 0
  transform: translateY(50px) rotate(10deg)
《動態時間軸年表》製作動態
《動態時間軸年表》製作動態

至於一個一個排序進入的動畫,我們同時抓出物品跟現在是第幾個的id,用 transition-delay 加上秒數,動態指定動畫時間delay多久。

//HTML
li.dialog_wrapper(v-for="(l,id) in logs", :key="l", :style="{'transition-delay':id/2+'s'}")

最後撒上如巧克力米般的 deco_bar 妝點整個畫面,然後再加上下雨般的動畫,我們就大功告成啦。

//HTML
#app
  button.initial(@click="initial") 插入資料
  transition-group.timeline(tag="ul",name="fade")
    li.dialog_wrapper(v-for="(l,id) in logs", :key="l", :style="{'transition-delay':id/2+'s'}")
      .dialog
        h3.year {{l.year}}
          .decor_bar
        ul.content
          li(v-for="c in l.content") {{c.tag}}
//CSS
@keyframes rain_in
    0%
      transform: translateY(-50px)
      opacity: 0
    100%
      transform: translateY(0px)
      opacity: 1  
  
.decor_bar
    &,&:before,&:after
      content: ""
      +size(8px,30px)
      background-color: $color_yellow
      border-radius: 5px
      position: absolute
      top: -35px
      left: 30px
      animation: rain_in 0.5s 0.5s both
      
    &:before
      background-color: $color_orange
      top: -30px
      left: -20px
      animation-duration: 1s
      animation-delay: 0.5s
        
    &:after
      background-color: $color_white
      top: -60px
      left: 20px
      animation-duration: 2s
      animation-delay: 0.5s

成品請參考這邊 👉🏻 https://codepen.io/frank890417/pen/rwrZwe?editors=0010

以上就是這次可愛的時間軸年表教學,相較於其他的直播內容,這次講解到CSS相關的切版觀念,讓我們再一次複習運用到哪些重點概念。

觀念大補帖

  1. 物件相對、絕對位置關係──層層相疊的物件如何使用position來呼應位置
  2. Animation的運用──如何使用Vue transition group與撰寫CSS的keyframes
  3. CSS選取器──運用:nth-child等語法選取肚子裡的子層

只要熟悉這些概念,相信成為切版高手的路就不遠啦!那麼,我們下次見啦👋👋👋

動畫互動網頁程式入門(HTML/CSS/JS)以簡單例子帶你入門網站的基礎架構及開發,用素材刻出簡單有趣又美觀的網頁和動畫,享受做出獨一無二的網頁所帶來的成就感,在職場上與設計師和工程師合作無間。

打好基本的互動網頁基礎之後,可以進階動畫互動網頁特效入門(JS/CANVAS),紮實掌握JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。長達3085分鐘,超過60個精緻範例與400張的投影片以上,以及四個加碼單元vue-cli、GSAP、D3、Three.js的投影片,成為hahow上最長的課程。

此篇直播筆記由幫手 Jeudi Kuo 協助整理

墨雨設計banner

這篇文章 利用Pug(HTML)、Sass(CSS)及Vue.js製作動態時間軸年表(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
來用可怕的三角函數做網頁吧! – Part 1 衛星繞月球(直播筆記) https://creativecoding.in/2021/05/10/%e4%be%86%e7%94%a8%e5%8f%af%e6%80%95%e7%9a%84%e4%b8%89%e8%a7%92%e5%87%bd%e6%95%b8%e5%81%9a%e7%b6%b2%e9%a0%81%e5%90%a7-part1-%e8%a1%9b%e6%98%9f%e7%b9%9e%e6%9c%88%e7%90%83/ Mon, 10 May 2021 01:30:00 +0000 https://creativecoding.in/?p=536 在這篇文章中,會先詳細介紹三角函數的計算並轉換為程式語言,再以三角函數的概念,使用 html (pug), css (sass) 與 js ,從零建構出衛星環繞月球的動態網頁效果。 本文翻自[週四寫程…

這篇文章 來用可怕的三角函數做網頁吧! – Part 1 衛星繞月球(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
https://imgur.com/iqS8cqe.gif

在這篇文章中,會先詳細介紹三角函數的計算並轉換為程式語言,再以三角函數的概念,使用 html (pug), css (sass) 與 js ,從零建構出衛星環繞月球的動態網頁效果。

本文翻自[週四寫程式系列] 來用可怕的三角函數做網頁吧! – Part 1,若是對文章內容有疑問,都可以觀看影片詳細內容。

前言

這次要跟大家聊聊三角函數,一聽到「三角函數」,肯定會勾起學生時期回憶,『天啊!老師在說什麼???』,我們這次準備了衛星繞月球的案例,將三角函數結合 CSS,實現衛星在圓周上的位置。也讓大家思考,如何在遊戲中使用三角函數?三角函數還能做出什麼變化?

在開始之前,先和大家分享之前經手的兩個案例 Dyverse Studio 與複雜生活節。在 Dyverse Studio (影片 4:29) 專案中,可以看到主頁裡,背景是由很多條線連結起來的,這個效果不可能手寫網頁結構去達成,直接使用圖片旋轉也欠缺真實感。要達成這些計算藝術的效果,三角函數、座標系、canvas 三者是不可或缺的工具。那這些即時繪圖有什麼好處呢?當使用者跟滑鼠互動或是調整一些參數時,網站能夠即時變化去調整顯示出來的效果,跟一般動畫寫死位移或是旋轉相比,多了更多的互動性。附錄中的 codepen 範例網址中,包括會動的海浪波型、 CD 旋轉軌跡等效果,都是利用三角函數製作出來的效果。由於 Dyverse Studio 主頁已改版,這邊提供影片中提到的畫面連結

Dyverse Studio主頁動畫

從遊戲開始出發

開始製作衛星繞地球前,我們要先了解三角函數。在網頁中要搖操控遊戲中的小精靈移動,就會使用 CSS 的 left, top 屬性,要往右走 left 增加,要往上走 top 減少,以此類推,隨著時間增加,移動的位置也跟著改變,就能營造出小精靈在移動的畫面。

這時問題來了,若是今天我們要讓小精靈斜上(右上45°)移動,該怎麼做呢?相信大家一定非常聰明可以想到,只要在同一時間往上和往右移動相同距離就可以了。

問題又來了,如果要改成往右上30°移動呢?右邊的距離跟上面的距離比例分別為1和√3(學生時期數學課的景象慢慢出現在腦海裡…),先不要恐慌這些數字怎麼來的,只要知道要往右上角30°的目標物移動,x 座標每次都加上√3單位,y 每次都往上加上1單位。

若是想要操作物體移動速度的快慢,只要調整每次加上單位的量(10 * 1 和 10 * √3),就能決定綠色球跑得快或慢。但是這樣的換算方式並不夠理想,每次我們要調整速度,就得再去算固定時間往上或往右移動的量。

版本1:知道 x 及 y 的變量,求得角色的下一個位置。

適用於目標位置不改變。

我們可以發現,在30°橫向距離與直向距離的比值不變,都是 1/√3 ( Δy / Δx, Δ, delta 變動值 ),在比值不變的狀態下,我們只要知道 x 或 y 的其中一邊,就能知道另一者。

x+=Δx
y+=Δy

版本2:知道 x 或 y 其中一者,求得角色的下一個位置。

適用於目標位置不改變。

既然我們知道 x 和 y 成比例關係,那我們就能將 y 每次增加的變量換算成Δx * 比例,反之我們也能將 x 每次增加的變量換算成 Δy * 比例。

x+=Δx
y+=(Δx*比例)

以上兩種版本都是在目標物在特定角度下,我們能夠計算出本體抵達目標物的移動方式,但是如果有更多角度的需求時,只有三種角度的比例肯定就不敷使用了。

版本3:知道斜邊 r 之後,r 乘上對應比例,就可以找到 x, y

這時我們就要感謝數學家們的努力,發明了三角函數,只要我們知道本體和目標物的距離 r 與角度 θ,就能利用 sinθ 或 cosθ 知道對應邊的長度了。

那麼 sinθ 的值怎麼來的呢?假設今天有一個座標軸,上面有一個半徑為1單位的圓,我們將不同角度的三角形放進去,讓所有三角形的 r 都剛好等於半徑1單位,就可以求出 sinθ ( r / y ),例如45°的時候,sin45°=1/√2。

有了這些,我們就可以拿本體到目標位置的距離乘上比例( sinθ ) ,來求出 y。同樣地要求出 x,我們就可以改使用 cosθ。但是這個比例的值到底是什麼呢?讓我們來看看第四個版本

版本4:知道 θ ,就能使用 r 結合三角函數作為比例

比例會根據θ而改變

// r = 5
x+=(5*比例)
y+=(5*比例)

// sinθ = y/r
// cosθ = x/r

x += r * cosθ
y += r * sinθ

衛星繞月球

有了這些三角函數的基本概念後,我們來嘗試做衛星繞月球的畫面,但為了強迫自己使用三角函數,我們要限制自己不能使用 css rotate 的屬性,只能利用 top, left (與畫面上方與左方的距離)結合 transform 來達成。

🔔關於 rotate : css 中有個屬性 transform,裡面有許多種值可以選擇,例如: translate, rotate, skew,其中的 rotate 則是以該物件中心為旋轉基準,根據使用者填入的值做旋轉。了解更多

這邊要注意的是,一般來說,旋轉的角度為逆時針,右邊水平線為 0°,順時針旋轉增加角度(圖左),而網頁座標中旋轉的角度與大家的想像不一樣,這邊在衛星旋轉的部分會透過操作詳細敘述。右圖中可以看到,只要知道綠色衛星與月球中心的距離 r 與 θ,就能利用三角函數換算出 y 與 x 座標。

我們這次用 codepen 來製作這個作品,環境調整為 pug 與 sass,會使用到 jq 來快速選擇兩顆衛星。這邊提供大家一個選顏色的工具 colorhunt ,沒有配色靈感時,能夠直接使用別人推薦的色碼。

場景 html 結構

在畫面中分別有星球背後的光暈(.space)、月亮、月亮上的四個坑洞、兩顆衛星,因此我們整個 html 結構可以寫成下面這樣

.space
.moon
  .hole
  .hole
  .hole
  .hole
.yellow
.blue

Sass 重複使用 – @mixin

首先我們先賦予場景基本的屬性,* 的存在是為了讓我們了解每個元件的外框,在完成作品後可以刪除這個 class 內容。

$color_space: ##2c3d4f

*
  border: solid 1px

html, body
  width: 100%
  height: 100%
  padding: 0
  margin: 0
  background-color: $color_space

// 背景光暈
.space
  width: 700px
  height: 700px
  border-radius: 50%
  background-color: lighten($color_space, 10)
  filter: blur(50px)
  position: absolute
  left: 50%
  top: 50%
  // 偏移處理
  transform: translate(-50%, -50%)

.moon
  background-color: #fff
  width: 200px
  height: 200px
  border-radius: 50%

我們可以發現畫面裡的月球、衛星、坑洞,有太多重複需要使用到圓的東西,我們來試試看怎麼將這些屬性整理在一起,讓這些屬性可以不用一直重複撰寫。

我們會發現 .moon 的寬高與圓角是構成圓形的三個屬性,要如何做才能重複使用這些 css 呢? sass 內有個工具叫做 mixin,可以傳入變數進去,在編譯階段就能產出我們需要的 css 內容,這種方式讓我們能減少撰寫重複的程式碼。宣告與使用的方法如下:

@mixin size($w, $h)
  width: $w
  height: $h

@mixin circle($r)
  +size($r, $r)
  border-radius: 50%

.moon
  background-color: #fff
  +circle(50px)

如果想偷懶一下,讓 +size 內只需傳入一個 $r,可以將 @mixin size 中加入判斷式改寫成如下,在 mixin size 中我們可以看到,當有傳入 $h 時, height 就是使用傳入的第二個參數,若是沒有則直接使用 $w 作為 height;寫法2中則是當 $h 參數沒有傳入時,則預設 $h 為 $w

// 寫法1
@mixin size($w, $h:false)
  width: $w	
  @if ($h)
    height: $h
  @else
    height: $w
// 寫法2
@mixin size($w, $h:$w)
  width: $w	
  height: $h

@mixin circle($r)
  +size($r)
  border-radius: 50%

.moon
  background-color: #fff
  +circle(50px)

這時候我們又發現,專案中頻繁地使用到絕對定位並水平垂直置中,理所當然也可以把這些屬性整理成 mixin:

@mixin ab_center
  position: absolute
  left: 50%
  top: 50%
  transform: translate(-50%, -50%)

製作陰影

我們的月球、月球坑洞與衛星都會用到陰影,css 中的 box-shadow 陰影預設是往外長,我們這邊可以多下一個 inset 值,讓陰影變成內陰影,改成 -20px 便是將亮面往左上移動,知道這個方式之後,讓我們試著做做看衛星與月亮坑洞的陰影,可以試著調整陰影顏色或是偏移量。

.moon
  background-color: #fff
  +circle(400px)
  +ab_center
  box-shadow: -20px -20px darken(#fff, 10) inset

月亮不同坑洞

月亮上四個坑洞的 classname 都是 hole ,我們該如何去客製這四個一模一樣的坑洞,讓它們在基本的屬性上再增加不同的位置或是大小?這邊我們使用到 css 的類別選擇器 nth-child,不僅位置可以客製,每個坑洞的大小也可以透過這種方式去調整。

.hole
  &:nth-child(1)
    left: 120px
    top: 130px

不知道大家在分別寫四個坑洞的位置時,有沒有查覺到我們也可以用剛剛的 mixin 去寫呢?同學可以挑戰看看。

@mixin pos($left, $top)
  top: $top
  left: $left

衛星軌道

這一小節中我們要製作衛星軌道,對好位置後,也能夠確認衛星沒有偏離,這邊只要增加 html 結構,並為它們賦予 css 即可,這邊我們只示範 .trace1 的寫法,要特別注意的就是衛星旋轉的圓型軌跡直徑,就是這個軌道寬度和高度:

HTML

.trace1
.trace2

CSS

.trace1
  width: 500px
  height: 500px
  border-radius: 50%
  border: 1px dashed #fff
  +ab_center

旋轉的衛星

幫衛星賦予樣式,在這邊我們想讓衛星有內陰影,以及淺淺的外層黑色光暈。所以我們在 .red 和 .yellow 的 css 上,加上一些程式碼。這邊也可以看到,我們將常用的顏色做成變數,方便之後快速調整,box-shadow 前面的部分是內陰影,逗號後面的則是外層黑色光暈,大家在寫這一段也要記得加上 z-index

這邊提供大家 .red 的 sass 檔,黃色衛星的內容大家可以挑戰看看。

.red
  $color_red: #f24
  background-color: $color_red
  +circle(50px)
  +ab_center
  box-shadow: -10px -10px darken($color_red, 20) inset, 0px 0px 40px rgba(black, 0.3)
  z-index: 100

接著我們要著手撰寫程式碼,讓程式碼動態修改角度,使衛星們順利動起來。

結合前面所解說的方式,利用三角函數來定義旋轉的位置。angle 為旋轉角度,這邊先釐清前面提到的旋轉方向,一般來說水平線右邊為0°,角度增加的方向為逆時針(左圖);但在網頁中 x, y 的方向有所不同,y 向下才是正值,角度增加旋轉的方向為順時針(右圖)。

我們先做一些測試,確定網頁中旋轉的角度是不是如右圖所示。

下面這段程式碼,angle 是我們要觀察的角度變數,r 為紅色衛星的半徑,x 的部份我們有提到要使用三角函數的 cosθ , y 則是使用 sinθ,那為什麼 θ 的值會使用到這麼大串運算式呢?這邊我們先一一理解整段程式碼,javascript 內要使用到 cos 要使用 API – Math.cos(),javascript 內角度不是直接寫數字,要換算成Math.PI,一圈 360° 為 2PI,所以我們將角度除以 360 後乘上 2PI,就能換算成 js 內的角度。

而最後的 – 25 則是偏移量,因為我們在 mixin ab_center 內有寫到translate(-50%, -50%),這邊我們要將這個偏移量修正回來,才不會讓衛星旋轉偏移。

var angle = 0
var r = 250
var x = r * Math.cos((angle/360)*(Math.PI*2))-25
var y = r * Math.sin((angle/360)*(Math.PI*2))-25
$(".red").css("transform", "translate("+x+"px, "+y+"px)")

大家可以慢慢增加 angle 的量,就能發現紅色衛星隨著角度的增加,從右邊水平線順時針轉。

https://imgur.com/p649GNo.gif

接著我們要讓衛星順暢的旋轉,這邊使用到 setInterval。我們先將剛剛的程式碼包裝成 function update,每隔一段時間就增加 time 的量,並更新畫面就能讓角度增加,使用 setInterval 每 30 毫秒呼叫一次,一個順暢的動態就產出了。

若是覺得衛星轉太慢或太快想調整衛星速度,我們只要把 var angle = time 多乘上一個值即可,聰明的大家應該有發現,如果我們乘上的值是負值,就輕鬆的達成反方向旋轉的功能了。

var time = 0
function update(){
  var r = 250
  // var angle = time
  // var angle = time * 0.2
  var angle = time * -0.5
  var x = r * Math.cos((angle/360)*(Math.PI*2))-25
  var y = r * Math.sin((angle/360)*(Math.PI*2))-25
  $(".red").css("transform", "translate("+x+"px, "+y+"px)")
  time+=1
}

setInterval(update, 30)
https://imgur.com/TGkqKLj.gif

這時候問題來了,紅色衛星完成了,黃色衛星的程式碼也類似紅色程式碼,差別只在旋轉半徑、速度、偏移修正不同,那我們要怎麼將它統一呢?這邊我們使用陣列去管理這兩個衛星物件,在 update 內使用 forEach 來修改兩個衛星的位置就可以了。

var time = 0
var stars=[
  {
    el: ".red",
    r: 250,
    speed: -2,
    width: 50
  },
  {
    el: ".yellow",
    r: 340,
    speed: 1,
    width: 70
  }
]

function update(){
  stars.forEach(function(star){
    var r = star.r
    var angle = time * star.speed
    var x = r * Math.cos((angle/360)*(Math.PI*2))-star.width/2
    var y = r * Math.sin((angle/360)*(Math.PI*2))-star.width/2
    $(star.el).css("transform", "translate("+x+"px, "+y+"px)")
  })
  time+=1
}

setInterval(update, 30)
https://imgur.com/iqS8cqe.gif

結語

這邊讓我們再複習一次整個製作過程

  1. 開始製作前,我們先將基本的結構與樣式完成(陰影、坑洞等),過程中我們發現許多重複使用的樣式,例如絕對定位、圓形等,所以我們學到了第一招 sass 中的 mixin。
  2. 我們利用三角函數模擬衛星繞星球旋轉的定位,並確定衛星旋轉的方向後,讓紅色衛星順利轉起來。
  3. 最後我們將旋轉兩個衛星整理成物件,並改寫 update 函式,讓兩顆衛星能共用相同函式旋轉。

數學裡面有許多奇怪的東西,但也感謝數學家們的努力,讓我們可以應用在遊戲或動態特效等地方,即使理解這些數學理論有些頭疼,但是一切都是為了做遊戲和特效。在 part 2 裡我們會再帶大家使用三角函數來製作其他有趣的東西。

下一篇老闆要繼續使用三角函數打造一個科技感的時鐘,讓我們一鼓作氣學習下去吧!

工商時間:老闆在 Hahow 有一堂課程 – 動畫網頁特效入門,裡面有一些數學的內容,誘使大家跳坑,一起去學這些恐怖的東西,老闆會將課程講解得有趣點,讓大家在比較沒有壓力的狀況下學習這些數學。(笑

此篇直播筆記由幫手 H 協助整理

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 來用可怕的三角函數做網頁吧! – Part 1 衛星繞月球(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
資料視覺化的動態長條圖製作(上):jQuery(直播筆記) https://creativecoding.in/2021/01/06/%e8%b3%87%e6%96%99%e8%a6%96%e8%a6%ba%e5%8c%96%e7%9a%84%e5%8b%95%e6%85%8b%e9%95%b7%e6%a2%9d%e5%9c%96%e8%a3%bd%e4%bd%9c%ef%bc%88%e4%b8%8a%ef%bc%89%ef%bc%9ajquery%ef%bc%88%e7%9b%b4%e6%92%ad%e7%ad%86/ Wed, 06 Jan 2021 11:04:55 +0000 https://creativecoding.in/?p=369 資料視覺化是網頁上常用的媒材,在本篇文章中我們將實際操作在jQuery/ Vue/ D3這三種框架下如何繪製出好用的動態長條圖,同時分析各個框架的優勢與劣勢,讓我們繼續往下看吧! 首先讓我們對長條圖有…

這篇文章 資料視覺化的動態長條圖製作(上):jQuery(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
資料視覺化是網頁上常用的媒材,在本篇文章中我們將實際操作在jQuery/ Vue/ D3這三種框架下如何繪製出好用的動態長條圖,同時分析各個框架的優勢與劣勢,讓我們繼續往下看吧!

https://i.imgur.com/cEEkxZo.gif

首先讓我們對長條圖有個初步了解。長條圖多用於呈現相同性質但不同量級的資料,例如比較美國、台灣、日本這三個國家的人口數。用程式碼撰寫一個長條圖主要分為三個步驟:

  1. 資料整理,通常會用陣列表現相同性質且不斷重複的資料。
  2. 在網頁上產生相對應的物件(長條方塊)。
  3. 針對每個長條方塊設定他的屬性,例如:改變物件的高度、弧度、間距……等屬性。

我們將以Code Pen做為本次實作的平台,這是一個可以在創作的同時即時看到程式碼運作狀況的線上程式碼編輯器,只要簡單註冊就可以使用囉!

主要會用到的知識:

  1. HTML (Pug), CSS (Sass)與JavaScript/ jQuery的基礎觀念
  2. Vue框架概念
  3. D3框架概念

如果想搭配直播影片一起實作,請往這邊走👉 https://www.youtube.com/watch?v=o50XYczmoP0


框架一:jQuery

codepen實作範例:長條圖_jQuery

相信大家對jQuery都滿熟悉的,它的特性就是抓一個特定條件的東西,去修改他特定條件的屬性。

首先,在code pen上開一個新的pen,將HTML的預處理器設定成Pug、CSS的預處理器設定成Sass、jQuery的CDN掛入JS。

https://i.imgur.com/4krJiqp.png

接著來準備一組做為圖表資料的陣列。命名一個陣列為datas,運用JS的for迴圈,設定一個從1-10重複10遍的變數i,同時運用函數Math.random()每次推入一個隨機的變數,用console.log印出datas陣列內的資料。

https://i.imgur.com/hPNse2U.png

要產生圖表,需要製作一個放置長條圖、資料的容器,在HTML新增一個class為graph的div,在CSS針對graph的width, height, border進行屬性的設定增加可視性。

接著做進一步的資料處理,運用JS的forEach將陣列datas內的每一筆資料單獨取出(ES6簡化function為箭頭函式)給他一個參數index,console.log印出index, obj,即可看到陣列內單獨的資料和標號用的index。

https://i.imgur.com/aXgrclM.png

溫馨提醒:ES6中簡化function為箭頭函式()=>{}的寫法不是每個瀏覽器都吃,記得搭配webpack做轉換唷。

為了產生對應資料的長條圖,在forEach的迴圈內我們命名一個變數bar,bar是'<div class="bar"></div>'的HTML(注意單引號與雙引號的使用),用jQuery選取graph,利用語法append()在被選取的元素结尾插入bar的長條圖。

在CSS針對bar做width, border的屬性設定,在graph下屬性display: flex讓每個長條可以排排站。

https://i.imgur.com/gSX3fiW.png

接下來要替他們長不同的身高、上不同的顏色。首先在graph利用CSS flex的屬性align-items: flex-end讓長條方塊置底,在bar給每個長條margin-right: 10px

那要如何指定高度和顏色呢?我們利用jQuery選擇器的特性$(bar)把它命名為變數element,接著修改他CSS的height和background-color。由於obj為1-10的亂數,在高度呈現上的級距不明顯,所以把obj*20讓他的高度凸顯出來。

而CSS的顏色屬性是由rgb數值在0-255之間決定,如果只在1-10之間浮動的話會黑壓壓的一片,所以我們命名一個變數為color_val,利用語法parseInt()將obj轉成整數後再上15,這樣深淺鮮明、高度差異的長條圖就出現啦。

https://i.imgur.com/9KG7iRr.png

完成了靜態的長條圖,離動態更新長條圖就不遠了。由於接下來會比較複雜所以貼出JS的完整code給大家參考,步驟分為以下:

  1. 命名一個新的陣列為elements,用來儲存先前產生出的實體物件(變數element),利用elements.push(element)直接推進去。
  2. 利用語法setInterval()規定每500豪秒更新長條圖一次。
  3. 更新的動作利用函式generateData()來進行,把它想成一組亂數產生器,所以一樣命名一個陣列temp,把亂數塞進去。
  4. 接著讓datas = generateData();亂數更新一坨拉庫資料,有資料後就可以按照資料更新物件(長條方塊),因為剛剛把亂數產生包在陣列裡面了,所以記得先做初始化。運用forEach把每個物件抓出來玩一遍,根據對應的資料更新他的高度和背景顏色所以使用datas[index],第一筆資料對應第一個物件、第二筆資料對應第二個物件。

這樣我們就有一個會變化的長條圖了(撒花),但是否覺得變換瞬間有點卡卡的?只要在bar的CSS設定transition: 0.5s就可以解決囉!

var datas = [];
var elements = [];
function generateData(){
  var temp = [];
  for( var i=0; i<10; i++){
    temp.push(Math.random()*10+5);
  }
  return temp;
}

for( var i=0; i<10; i++){
  datas.push(Math.random()*10+5);
}
datas = generateData(); //function初始化
console.log(datas);

datas.forEach((obj, index)=>{
  console.log(index, obj);
  var bar = '<div class="bar"></div>';
  var color_val = parseInt(obj)*15;
  var element = $(bar);
  element.css("height", obj*20+"px")
         .css("background-color", "rgb("+color_val+","+color_val+","+color_val+")")
  $(".graph").append(element);
  elements.push(element);
});

//更新
setInterval(()=>{
  datas = generateData();
  elements.forEach((element,index)=>{
    var color_val = parseInt(datas[index])*15;
    element.css("height", datas[index]*20+"px")
         .css("background-color", "rgb("+color_val+","+color_val+","+color_val+")")
  });
},500);

最後一步是新增每個長條方塊所對應的數值,最快速的方法就是直接把數值包在長條bar裡面,然後使用CSS position的定位方法將數值定在長條方塊的下方。

把文字包在長條內var bar = '<div class="bar"><div class="text"></div></div>';

利用jQuery children選取element的小孩".text",用語法toFixed(1)取到小數點第1位。在下方setInterval的更新處重複ㄧ樣的步驟,記得把變數名稱obj修改成datas[index]

element.children(".text").text(obj.toFixed(1))
https://i.imgur.com/kE68ek9.png

接著在CSS針對bar和text的定位做設定,就可以看到長條圖表下方出現數值了。

.bar
  position: relative
  
.text
  position: absolute
  bottom: -30px

做到這裡你有沒有發現我們一直在做一件重複的事情?沒錯,就是針對實體物件的數值做修改。用jQuery寫圖表就有點像用一個簡單的錘子蓋房子,從建造到調整都需要針對每個DOM物件去做手動修改,土炮且自立自強。

下一篇文章中我們會講到另外兩個框架:Vue和D3,相較於如此「人工」的jQuery,這兩個框架比較偏自動化,就讓我們下次見囉~

課程推薦

老闆在Hahow好學校開了兩門課,其中,動畫互動網頁程式入門(HTML/CSS/JS)以簡單例子帶你入門網站的基礎架構及開發,用素材刻出簡單有趣又美觀的網頁和動畫,享受做出獨一無二的網頁所帶來的成就感,在職場上與設計師和工程師合作無間。

打好基本的互動網頁基礎之後,可以進階動畫互動網頁特效入門(JS/CANVAS),紮實掌握JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。期待在課程裡見到你!

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 資料視覺化的動態長條圖製作(上):jQuery(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>