Vue.js 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/category/tutorial/vue-js/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Sun, 08 May 2022 09:56:14 +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 Vue.js 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/category/tutorial/vue-js/ 32 32 【Vue.js入門】一小時學會 Vue.component,完成動態飯店房間清單 https://creativecoding.in/2022/05/17/vue-js-hotel-room-list/ Tue, 17 May 2022 02:51:00 +0000 https://creativecoding.in/?p=2593 我們會從 Vue.js 基本概念開始講起,非常適合剛開始接觸 Vue 的朋友們,接著我們用 Vue component 實作一個動態的飯店編輯頁面,你會學到:Vue.js 基本概念、Vue.js 語法指令及深入操作 Vue.component。

這篇文章 【Vue.js入門】一小時學會 Vue.component,完成動態飯店房間清單 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
假如今天我有很多不同的商品要賣,網站上有很多商品的細節需要編輯、計算,一個一個改實在太麻煩了,如果一個輸入錯誤,可能還會造成虧本,這樣可不行啊!因此我們今天練習用 Vue.js 做一個「牽一髮動全身」的動態編輯頁面,只要改一個地方,用到這筆資料的所有地方都會自動變更,想想真是太棒了!

這篇文章裡,我們會從 Vue.js 基本概念開始講起,非常適合剛開始接觸 Vue 的朋友們,接著我們用 Vue component 實作一個動態的飯店編輯頁面,跟著這篇文章,你會學到:

  1. Vue.js 基本概念:了解 Vue.js 是什麼、為什麼工程師要使用 Vue.js 操作資料。
  2. Vue.js 語法指令:學習如何使用 Vue.js 帶入資料、怎麼使用 v-bind、v-model 等 Vue 語法。
  3. 深入操作 Vue.component:用 Vue 元件實作一個能夠動態改變資料的編輯頁面,將 Vue 的基本語法與元件合併使用,透過元件的實際操作理解元件間的溝通傳遞。

我們會用飯店房間當作範例資料來練習,並在 Codepen 上實作,Codepen 是一個讓我們在編輯程式碼的同時能夠馬上看到結果的線上程式碼編輯器,註冊完就能夠使用了,那就讓我們開始吧!

如果想跟著影片一起動手做的話,請到這邊

Vue.js 基本概念

什麼是 Vue.js?Vue.js 其實是一套 JavaScript 的程式庫,負責把資料轉為網頁呈現。過去假如我們要寫一個飯店的網頁,我們要在 HTML 中一行行寫出飯店的房間標題、房型描述等等,假如要修改資料,就要在茫茫的 HTML 海中一筆筆修改;但是利用 Vue.js,我們不再需要一個個把資料寫死,而是可以先寫好一個模板,然後將裡面的資料用變數的方式代入。

例如,今天我們有一份自我介紹的模板:

「哈囉,我的名字是___,來自風非常大的地區___,喜歡___、___。」

我可以依據自己的個人資料來填空,這些填入的資料就是「變數」。Vue.js 中,變數可以不是單一的值,例如這裡的興趣以陣列(Array)的形式儲存,因此我們可以用索引(index)的方式來取出陣列裡的所有資料。

填空後的自我介紹就變成了:

「哈囉,我的名字是吳哲宇,來自風非常大的地區新竹,喜歡聽音樂、畫圖。」

但 Vue.js 的功能不止於此,它還有許多方便的功能。

v-for:迴圈,取出清單裡面所有資料

假設在「興趣」項目裡有一百筆資料,我們可以利用 v-for 抓取這一百筆資料,用一行 v-for=” hobby in hobbies” 自動重複標籤一百遍,列出這一百項資料。

  <ul>
    <li v-for="hobby in hobbies">
      {{ hobby }}
    </li>
  </ul>

// Vue.js 資料
  { 
    hobbies: ["聽音樂","畫圖"] 
  }

computed:前處理,先行運算

例如一個商品的價格是「原始價格x折扣數」,在 Vue.js 中,我可以不用每一次都自己運算,而是利用 computed 功能,創造「final-price 」這個變數,定義好最終價格等於原始價格乘以折扣數,這樣我們就可以直接使用 final-price 這個變數。

<h5> 折扣後的價格為 {{ final_price }} 元 </h5> 

// Vue.js 資料
  computed: {
    final_price: function() {
      return price * discount
    }
  }

v-bind:屬性綁定

在沒有使用 Vue.js 的時候,我們會使用 CSS 去改變網頁物件的顏色、框線或內容等,而 Vue 讓我們可以直接根據資料自動產生 CSS 帶入網頁。利用 v-bind:style 後面給予一個物件,物件裡則是一般 CSS 的寫法,Vue 就會幫我們自動產生 CSS 套在元件上,讓我們在資料裡就能夠定義或抽換元件的 CSS 樣式。

<div class="cover" v-bind:style="color_css"> 

// Vue.js 資料
  {
    color_css: {
      "background-image": "網址"
    }
  }

看到這裡,有沒有感覺得 Vue.js 好像一個個搬運工呢?是的,Vue.js 用起來就像是挖空格,我們規定好模板跟資料後,讓 Vue 物件依照要求把資料填空進去。所以使用 Vue.js 不可少的三元素就出現了:

  1. 模板:資料呈現的規則。
  2. 資料:要被填入的內容。
  3. Vue 物件:我們必須要新增一個 Vue 物件 ,讓它幫我們把資料呈現出來,也就是幫我們把資料搬到模板裡面的搬運工。

Vue 基本概念實作練習

看到這邊,是不是了解了 Vue 的基本概念了呢?我們用 Codepen 做一個小小的範例,讓我們更熟悉 Vue.js 吧。

首先,在這份範例中,我們會用到的是一份房間資料格式。這份資料的最外層用大括號(curly bracket)包起來,顯示它是一個物件,裡面記錄了房間的名稱、價格、設備等資料。

{
    "name": "經濟雙人房",
    "eng": "Economy Double Room",
    "price": 7000,
    "amount": 0,
    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room%20(1).jpg",
    "discount": 0.9,
    "equipment": {
      "wifi": false,
      "bathtub": true,
      "breakfast": true
    }
},

接下來我們在 Codepen 裡開啟一個 new pen,同時在設定裡將 HTML 選擇為 Pug,CSS 設定為 Sass,在 JS 的部分用 CDN 載入 Vue.js,存檔後我們的基礎設定就完成了。

Codepen裡的設定
Codepen裡的設定

我們先將剛才的房間資料存進 Javascript 裡,並且宣告資料名稱為 roomdata:

// JavaScript
  var roomdata = {
    "name": "經濟雙人房",
    "eng": "Economy Double Room",
    "price": 7000,
    "amount": 0,
    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room%20(1).jpg",
    "discount": 0.9,
    "equipment": {
      "wifi": false,
      "bathtub": true,
      "breakfast": true
    }
  };

接著在 HTML 裡寫入要呈現的內容

h1 房間的資料
  h2 名稱 {{ roomdata.name }}
  h2 價錢 {{ roomdata.price }}

現在,我們要在 JS 裡宣告宣告 Vue 實例,並且在 HTML 裡利用 #app 指定作用區域,這個作用區域表示我們要讓 Vue 在特定的區域檢查是否有要求代換的資料,如果我們把 #app 用原始 HTML 語法來看,代表的是:

<div id="app">

同時我們在 JS 裡也將宣告的 Vue 裡寫進入定義的 el 屬性,el (element) 代表的是作用的元件區域,值寫入 “#app”。

new Vue({
  el: "#app",
  data: {
    roomdata: {
      name: "經濟雙人房",
      eng: "Economy Double Room",
      price: 7000,
      amount: 0,
      cover: "http://bosscode.monoame.com/20170323_vue_comp/img/room%20(1).jpg",
      discount: 0.9,
      equipment: {
        wifi: false,
        bathtub: true,
        breakfast: true
      }
    }
  }
});

這樣,Vue 就能夠用變數自動代換指定的資料,接下來,我們練習讓 Vue 做計算。

computed:前處理、先行運算

將 HTML 中的最後的價錢指向變數 {{ final_price }} ,但我們的資料裡並沒有一筆資料名稱叫做「final_price」呀?沒關係,我們接著要自行定義它。

在 Vue 實例中,新增一個 computed (計算屬性),computed 的值必須是一個物件,裡面可以定義不同的運算式。我們現在需要的是 final_price,final_price 的值必須是一個函式 (function),在函式裡要求回傳「價格x折扣」。到這邊,{{ final_price }} 已經能夠幫我們回傳每間房間的價格了!

定義final_price算法
定義final_price算法

v-model:資料雙向綁定

下一個我們要練習的功能是 v-model,v-model 能夠幫我們做雙向的綁定,也就是當我們指定好 v-model 的兩端時,只要更動其中一處,另一端也會同步做更動。

觀看範例會更容易明白,我們在剛才寫好的 HTML 中增加兩行程式碼:

label 價錢
input(v-model="roomdata.price")

將 input 與資料中的 price 用 v-model 做綁定,對應的是 roomdata.price,這時畫面上便產生了 input 輸入框,框內就是 roomdata.price 的值 7000。這時如果我們改變了輸入值,資料就會同時做改變;反之如果改變了資料,輸入也會同步變化。

無論數值怎麼變,都可以套用折扣算式得到最終價錢
無論數值怎麼變,都可以套用折扣算式得到最終價錢

v-for 迴圈

接下來,我們用 v-for 取出清單內的所有資料,我們在 data 裡增加 rooms,值則是一個陣列,陣列裡包括 room1、room2、room3。

data: {
  rooms: [
    { name: "room1" }, 
    { name: "room2" }, 
    { name: "room3" }],
  ...
}

接著,我們在 HTML 裡用 v-for 把資料取出來。

h1 房間列表
  ul
    li(v-for="room in rooms") {{ room.name }}

在標籤 li 後面加上(v-for=”room in rooms”),這裡的 rooms 是 JavaScript 裡 data 中的 rooms,而 rooms 裡面的資料我們用 room 來命名(當然也可以換成其他名字),而我們指定好要以迴圈取出的資料範圍後,再以 {{ room.name }} 指定我們要取的是每一筆 room 的 name。

開始建置多間房間的列表
開始建置多間房間的列表

到這邊,我們練習了怎麼使用 Vue 物件呈現資料、也用了 computed、v-bind、v-for 做前運算、雙向綁定以及迴圈。附上完整的練習程式碼給大家參考。

// HTML
#app
  label 名稱
  input(v-model="roomdata.name")
  label 價錢
  input(v-model="roomdata.price")
  h1 房間的資料
    h2 名稱 {{ roomdata.name }}
    h2 價錢 {{ roomdata.price }}
    h2 最後的價錢 {{ final_price }}

  h1 房間列表
    ul
      li(v-for="room in rooms") {{ room.name }}

// JavaScript
new Vue({
  el: "#app",
  data: {
    rooms: [{ name: "room1" }, { name: "room2" }, { name: "room3" }],
    roomdata: {
      name: "經濟雙人房",
      eng: "Economy Double Room",
      price: 3000,
      amount: 0,
      cover: "http://bosscode.monoame.com/20170323_vue_comp/img/room%20(1).jpg",
      discount: 0.9,
      equipment: {
        wifi: false,
        bathtub: true,
        breakfast: true
      }
    }
  },
  computed: {
    final_price: function () {
      return this.roomdata.price * this.roomdata.discount;
    }
  }
});

接下來我們就要進入這次的主題: component 元件。

為什麼要使用 Vue 元件呢?因為我們在架設網站的時候,很有可能會面對一份非常龐大的資料!資料庫中可能有上百筆資料、有不同的邏輯、許多複雜的呈現規則等等,因此工程師們想出的解決辦法就是把龐大的資料拆成不同的 Vue 物件,每一個物件除了負責自己的資料外,也能夠繼承資料以及繼承方法。

「繼承資料」指的是能夠接收源數據的資料,例如飯店裡每一間房間都要打九折,我們可以在每間房間裡指定繼承源數據的折扣數 0.9,這樣就不用在每一間的資料中寫上一行「discount = 0.9」。

而「繼承方法」則好比開放權限,一個 Vue 物件除了能夠有自己指定的方法外,也能夠接收源數據規定好的方法,甚至能夠用接收到的方法回頭套用到源數據上。

接下來,我們就來一步步實作吧。

Vue Component 實作練習

我們開啟一個新的 Pen,並且一樣在設定裡將 HTML 選擇 Pug,CSS 設定為 Sass,同時引入 Bootstrap 的 CDN 方便我們之後做排版,在 JS 的部份載入 Vue.js,這樣基礎設定就完成了。接著將我們需要用到的資料複製到 JavaScript 裡,並且宣告資料的名稱為 rooms。

// JavaScript
	var rooms = [
	  {
	    "name": "經濟雙人房",
	    "eng": "Economy Double Room",
	    "price": 7000,
	    "amount": 0,
	    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room%20(1).jpg",
	    "discount": 0.9,
	    "equipment": {
	      "wifi": false,
	      "bathtub": true,
	      "breakfast": true
	    }
	  },
	  {
	    "name": "海景三人房",
	    "eng": "Sea view triple Room",
	    "price": 7800,
	    "amount": 0,
	    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room%20(2).jpg",
	    "discount": 0.8,
	    "equipment": {
	      "wifi": true,
	      "bathtub": true,
	      "breakfast": false
	    }
	  },
	  {
	    "name": "典雅景觀房",
	    "eng": "Elegant landscape Room",
	    "price": 5400,
	    "amount": 0,
	    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room%20(3).jpg",
	    "discount": 0.85,
	    "equipment": {
	      "wifi": false,
	      "bathtub": true,
	      "breakfast": true
	    }
	  },
	  {
	    "name": "尊享豪華房",
	    "eng": "Exclusive Deluxe Room",
	    "price": 9800,
	    "amount": 0,
	    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room%20(4).jpg",
	    "discount": 0.8,
	    "equipment": {
	      "wifi": true,
	      "bathtub": false,
	      "breakfast": true
	    }
	  },
	  {
	    "name": "商務雙人房",
	    "eng": "Business Double Room",
	    "price": 5600,
	    "amount": 0,
	    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room (5).jpg",
	    "discount": 0.9,
	    "equipment": {
	      "wifi": true,
	      "bathtub": false,
	      "breakfast": false
	    }
	  },
	  {
	    "name": "溫泉雙人房",
	    "eng": "Hot spring double Room",
	    "price": 8400,
	    "amount": 0,
	    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room (6).jpg",
	    "discount": 0.6,
	    "equipment": {
	      "wifi": true,
	      "bathtub": true,
	      "breakfast": true
	    }
	  },
	  {
	    "name": "總統套房",
	    "eng": "Presidential Suite",
	    "price": 23000,
	    "amount": 0,
	    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room (7).jpg",
	    "discount": 0.75,
	    "equipment": {
	      "wifi": true,
	      "bathtub": true,
	      "breakfast": true
	    }
	  },
	  {
	    "name": "奢華四人房",
	    "eng": "Luxury four Room",
	    "price": 8500,
	    "amount": 0,
	    "cover": "http://bosscode.monoame.com/20170323_vue_comp/img/room (8).jpg",
	    "discount": 0.7,
	    "equipment": {
	      "wifi": true,
	      "bathtub": true,
	      "breakfast": false
	    }
	  }
	];

先看一下我們的目標設計稿和需求:

  1. 瀏覽區塊中:每個房型會以卡片的樣式呈現,內容包括房間的圖片、名稱、價格和設備等資料。
  2. 編輯區塊中:可以編輯飯店折扣數、服務費、各個房間的資料細節。
  3. 在編輯區塊中修改的內容會即時跟瀏覽區塊的資料做同步,只要調整一次折扣數或服務費,所有房間的資訊都能夠繼承變動後的數值自動做計算,也能夠針對單一項目做調整。
最終完成品
最終完成品

房型一覽區塊基本結構:利用 v-for 迴圈重複架構

我們先在 HTML 中將畫面右側的房型一覽基本架構寫出來,並利用 Bootstrap 做排版。

#app
  .container
    .row
      .col-sm-9
        .row
          .col-sm-4(v-for="myroom in rooms")
            h3 {{ room.name }}
            .info
              h5 {{ room.eng }}
使用boostrap排版
使用boostrap排版

定義 Vue.component 並繼承資料

接著,我們可以先把房型切成元件(component)。我們先在 HTML 中新增一個 template,並且給它一個 id 叫做 room。template 裡面就是我們的呈現規則,要注意的是 template 裡面需要一個總體物件,這是因為 template 裡面根源只能有一個物件,不能是複數的物件,因此我們用一個大的 div 標籤來包裹裡面的資料,這個 div 我們也給它一個 class=”room_col”。

#app
  .container
    .row
      .col-sm-9
        .row
          .col-sm-4(v-for="myroom in rooms")


template#room
  .room_col
    h3 {{ room.name }}
    .info
      h5 {{ room.eng }}

接下來,我們就要在 JS 裡新增一個 Vue 元件。新增的方式是 Vue.component()。括弧裡第一個變數是元件的名稱,我們定義它為 “room”,接著第二個變數我們給它一個物件 { },代表所有要初始化的設定。

第一個我們設定 template: “#app”,告訴元件呈現的規則是 HTML 裡的 template#room。接著我們設定 props (繼承),因為我們元件的資料是從源數據繼承的,我們將繼承的資料命名為 props: [“room_data”]。

Vue.component("room", {
  template: "#room",
  props: ["room_data"]
});

在 HTML 中呼叫繼承的資料

Vue 元件設定好了,我們要回到 HTML 裡使用剛剛設定的資料。在 .col-sm-4(v-for=”myroom in rooms”) 底下我們給予一個 room 標籤,然後使用 v-bind 跟 room_data 做綁定 (v-bind:room_data=”myroom”),同時因為我們在 Vue compoenent 中 props 定義的名稱是 “room_data“,因此在 HTML 裡 template 中我們要呼叫的就是 room_data 裡的 name 跟 eng。

#app
  .container
    .row
      .col-sm-9
        .row
          // 為了能夠區分對應的資料,這邊把原來的 room 更名成 myroom
          .col-sm-4(v-for="myroom in rooms") 
             room(v-bind:room_data="myroom") 

template#room
  .room_col
    h3 {{ room_data.name }}
    .info
      h5 {{ room_data.eng }}
定義 Vue.component ,繼承並在HTML裡呼叫資料

接著,我們要來計算房間的價格。假設每一間房間有自己的定價和折扣數,另外整間飯店也有一個總體折扣數,我們的房價計算方式應該是:房價=定價x房間折扣x飯店折扣。為了讓大家能看清楚過程,我們先把每一個呼叫拆開寫出來。

飯店折扣我們在 vm 實例中先定義為 0.9。

var vm = new Vue({
  el: "#app",
  data: {
    rooms: rooms,
    discount: 0.9    // 新增飯店折扣數
  }
});

定價x房間折扣我們已經知道怎麼寫了,也就是 {{ room_data.price }}*{{ room_data.discount }}。接著,我們要在元件的繼承屬性中增加折扣 props: [“room_data”, “hotel_discount”]。

Vue.component("room", {
  template: "#room",
  props: ["room_data", "hotel_discount"]  // 增加繼承 hotel_discount
});

接著,我們在 HTML 中,先前已經綁定 v-bind:room_data=”myroom” 的 room 標籤後面,綁定 v-bind:hotel_discount=”discount”。

#app
  .container
    .row
      .col-sm-9
        .row
          .col-sm-4(v-for="myroom in rooms")
            // v-bind:room_data 可以簡寫為 :room_data
            room(:room_data="myroom", :hotel_discount="discount")

最後,我們需要在 template 中呼叫 hotel_discount。

template#room
  .room_col
    h3 {{ room_data.name }}
    .info
      h5 {{ room_data.eng }}
     h5 {{ room_data.price }}*{{ room_data.discount }}*{{ hotel_discount }} 

到這邊,房價的計算過程 定價x房間折扣x飯店折扣 已經可以正確地顯示出來了。

顯示房價的計算過程
顯示房價的計算過程

定義 computed 屬性做前運算

不過我們不能讓客人自己按計算機計算價錢,所以我們要讓 Vue 幫我們做運算。最單純的做法是我們把需要計算的過程寫在一個大括號裡:{{ room_data.price * room_data.discount * hotel_discount }},不過這樣顯然是一個很冗長的程式碼,因此我們定義一個 final_price 變數,再用 computed 幫我們做前運算。

這邊有一個小地方需要注意,如果運算後的結果是浮點數的話,Vue 並不會自動幫我們轉為整數,因此我們可以用 parseInt() 將計算結果先轉為整數後,再回傳。

// HTML
template#room
  .room_col
    h3 {{ room_data.name }}
    .info
      h5 {{ room_data.eng }}
      h5 {{ final_price }}
	
// JavaScript
Vue.component("room", {
  template: "#room",
  props: ["room_data", "hotel_discount"],
  computed: {
    final_price: function () {
      return parseInt(
        this.room_data.price * this.room_data.discount * this.hotel_discount
      );
    }
  }
});

這邊大家容易混淆的是 this 到底指向哪裡?好消息是,在 Vue 元件中,this 永遠會指向元件本身。而我們在 computed 中定義的 final_price 也可以在元件中使用 this 呼叫來做其他的運算。

因為畫面設計稿上我們希望呈現出房間原價、總折數、以及折扣後的房價,因此我們可以在 computed 中定義我們需要的運算式,然後在 HTML 裡呼叫變數,同時我們增加 .cover 方便未來增加房間圖片。

/ HTML
template#room
  .room_col
    .cover
      h3 {{ room_data.name }}
    .info
      h5 {{ room_data.eng }}
      h5 {{ room_data.discount }}*{{ hotel_discount }} = {{ final_discount_show }}折
      h4 TWD
         {{ room_data.price }}
        .final_price {{ final_price }}


// JavaScript
Vue.component("room", {
  template: "#room",
  props: ["room_data", "hotel_discount"],
  computed: {
    final_discount: function () {
      return this.room_data.discount * this.hotel_discount;
    },
    final_discount_show: function () {
      return parseInt(this.final_discount * 100);
    },
    final_price: function () {
      return parseInt(this.room_data.price * this.final_discount);
    }
  }
});
呈現出房間原價、總折數、以及折扣後的房價
呈現出房間原價、總折數、以及折扣後的房價

增添 CSS 美化版面

我們可以調整 CSS 的樣式,讓畫面變得更美觀。除了調整元素的邊距、顏色、字體大小外,我們用了 position: relative / position: absolute(相對位置/絕對位置)以及浮動元素 float 做位置的排版,並且用偽類 pseudo class 增加價格最後的 $ 字號。

// CSS
$color_red: #DB4343 

*
  border: 1px solid #666    // 排版的時候為了方便觀看,我們增加邊框的樣式

.room_col 
  padding: 20px
  .cover
    height: 150px
    background-color: #eee
    position: relative
    h3
      position: absolute
      bottom: 10px
      font-size: 20px
      padding: 5px 15px
      background-color: #fff
  .info
    padding: 10px
    h5
      font-size: 12px
    .final_price
      float: right
      color: $color_red
      &:after
        content: "$"
調整CSS 美化版面
調整CSS 美化版面

computed 也能夠用來做 CSS 屬性運算

接下來,我們來做最好玩的部分,利用 v-bind 把房間圖片放進去。background-image 因為是一個 CSS 屬性,所以我們要讓 Vue 元件幫我們計算這個 CSS,我們在 computed 裡加上 bg_css 並且給它一個 function,在 function 裡計算完後再將結果回傳。CSS 中的背景圖片的語法是 background-image: “url(‘一段網址’)”,因此 function 裡也是如此,同時網址的部分呼叫繼承的資料 this.room_data.cover,最後別忘了要將結果回傳,回傳資料需要用大括弧 { } 包起來。

// JavaScript
  computed: {
    // ...
    bg_css: function () {       // 增加 background-image CSS 屬性
      return {
        "background-image": "url('" + this.room_data.cover + "')"
      };
    }
  }

接著,在 HTML 裡我們要利用 v-bind 操作 CSS,在 .cover 後指定 v-bind:style 並且指定 bg_css 物件。這時畫面上已經能載入圖片了。我們再使用 CSS 調整圖片的位置讓圖片,加上 background-size: cover 讓圖片縮放到封面大小、加上 background-position: center center 設定圖片初始位置為水平置中和垂直置中。

到這邊,輔助用的外框線我們就讓它功成身退吧。

// HTML
  .cover(v-bind:style="bg_css")

// CSS
  .cover
    height: 150px
    position: relative
    background-size: cover
    background-position: center center
載入圖片,位置都沒問題後將框線移除
載入圖片,位置都沒問題後將框線移除

最後我們要來做一點畫面上細緻度的調整。

首先,我們在將每一個房型的資料最外層的 div 標籤加上 class=”col-room”,並且給予內距 padding: 20px。避免混淆,將原來 template 最外層的 class 改成 “room-container”,加上陰影 box-shadow: 0px 0px 10px rgba(0,0,0,0.3)。這時,畫面上的房型一覽是不是很像用一張張卡片呈現,一目了然呢?

每張房型卡片都加上外框陰影
每張房型卡片都加上外框陰影

增加飯店資訊編輯區塊

到這裡為止,我們已經將設計稿上右側呈現的部分做好了,接著我們要來做左邊的編輯區塊。再看一眼我們的設計稿吧。

最終成品參考圖
最終成品參考圖

房型一覽的部分剛才我們已經用了 CSS grid 指定了 col-sm-9,因此,編輯區塊自然就是 col-sm-3 了(想了解更多格線佈局可參考:MDN)。我們為兩邊的畫面分別加上標題及分隔線,並且為畫面整體加上一點內距,讓視覺看起來比較美觀。

// HTML
#app
  .container
    .row
      .col-sm-3
        h1 飯店資料
        hr
      .col-sm-9
        h1 房間列表
        hr
        .row

// CSS
body
  padding: 20px
h1
  font-size: 30px

增加房間總折數編輯欄位

編輯區域第一個顯示的是飯店的總折數,我們加上 label 總折數,並且將 input 用 v-model 與 discount 做雙向綁定。

// HTML
#app
  .container
    .row
      .col-sm-3
        h1 飯店資料
        hr
        label 總折數
        // .form-control 是 Bootstrap 的標籤,我們用來美化視覺
        input.form-control(v-model="discount")   

// JavaScript
var vm = new Vue({
  el: "#app",
  data: {
    rooms: rooms,
    discount: 0.9
  }
});

到這邊,厲害的事情發生了!我們在 input 中改變折扣數的話,可以看見房型一覽中的價格也會立即變化,這就是 Vue.js 厲害的地方!資料綁定的好處就在於一旦一方改變了,所有相依的資料也會同步改變。

編輯左側欄位,加上總折扣數
編輯左側欄位,加上總折扣數

增加服務費編輯欄位

接著我們在每一筆房間訂單加上服務費。我們先將要繼承的資料都定義好,這裡可以拆成三個步驟:

  1. 增加需要繼承的資料:我們先在 vm data 中加上 service_fee: 200。
  2. 在 HTML 中告訴元件 room 要繼承 service_fee,並且將元件中的服務費重新命名為 hotel_fee。
  3. 在元件 Vue.component 中的 prop 屬性中指定繼承 hotel_fee。

這樣我們就寫完了繼承的資料,接著我們要在計算中加上服務費,我們用 this.hotel_fee 呼叫。需要注意的是,透過繼承而來的資料,子元件會收到的是「純文字字串」,而不是外層元件的狀態內容,因此我們利用 JavaScript 強制轉型的特性,將 this.hotel_fee 乘上 1.0 轉為數字型別。

// HTML 
label 服務費
// 服務費用 v-model 來綁定
input.form-control(v-model="service_fee")
...
.col-sm-4.col-room(v-for="myroom in rooms")
  room(
    :room_data="myroom",
    :hotel_discount="discount",
    // 告訴元件 room 要繼承 service_fee,並且將元件中的服務費重新命名為 hotel_fee
    :hotel_fee="service_fee" 
  )

// JavaScript
// Vm 實例
var vm = new Vue({
  el: "#app",
  data: {
    rooms: rooms,
    discount: 0.9,
    service_fee: 200,    // 增加服務費 200 元
  }
});

// Vue 元件
Vue.component("room", {
  template: "#room",
  // 增加繼承 hotel_fee
  props: ["room_data", "hotel_discount", "hotel_fee"],
  computed: {
    // 在 final_price 計算中加上服務費,需要乘上 1.0 是為了將 hotel_fee 的型別強制轉為數字
    final_price: function () {
      return (
        parseInt(this.room_data.price * this.final_discount) + this.hotel_fee * 1.0
      );
    },  
  }
});
加上服務費計算連動
加上服務費計算連動

增加各個房間編輯區塊

接著我們來製作各個房間的編輯區塊。首先增加標題 h1 房間編輯 及水平分隔線 hr 與上面的區塊做區分,接著我們增加一個 div 標籤並給予 room_edit 的 class,然後利用 v-for=”room in rooms” 做重複結構。room_edit 底下我們利用 input 跟 v-model 做雙向資料的綁定,分別綁定房間的名稱、價格、折扣數、英文名稱以及圖片網址。

// HTML
h1 房間編輯
hr
.room_edit(v-for="room in rooms")     // 利用 v-for 做重複結構
	h4 {{ room.name }}
	label 房間名稱
	input.form-control(v-model="room.name") 
                        // 利用 v-model 做雙向綁定
	label 價格
	input.form-control(v-model="room.price")
	label 折價
	input.form-control(v-model="room.discount")
	label 英文名稱
	input.form-control(v-model="room.eng")
	label 圖片網址
	input.form-control(v-model="room.cover")

當我們想要調整視覺的時候,隨時可以利用 Bootstrap 跟 CSS 做微調。例如我們給予每一個 input form-coontrol 的 class,讓它取得 Bootstrap 的預設樣式。

我們也能夠自行設定 CSS,例如將左側編輯區域的高度固定,超出的部分使用卷軸滾動的效果,也將每一個 room_edit 區塊利用 margin-top 做出間隔。

#app
  .container
    .row
      .col-sm-3.col-edit

//CSS
.col-edit
  height: 100vh
  overflow-y: scroll

.room_edit 
  margin-top:30px

到這邊,我們已經能在每一個房間的編輯區塊做個別房間的調整,例如將雙人房改為單人房、改變折扣數或價格等等。

增加各個房間編輯區塊
增加各個房間編輯區塊

增加房型的按鈕功能:v-on 事件處理

假如我們要新增一組房型的話怎麼辦呢?這時我們就可以用到 Vue 的 methods(方法)。我們在房間編輯區域最下面再增加一組 .room_edit + 新增房間,同時加上 @click 表示在點擊的時候要觸發指定的事件,這邊我們指定點擊時觸發 addroom 。觸發的事件我們則要寫進 Vue 物件裡,告訴它這個事件要做什麼。

// HTML
.room_edit(@click="addroom") + 新增房間

接著我們在 data 之後增加 methods,methods 的值是一個物件,因為裡面可以有許多不同的 method。這邊我們要增加的是 addroom。同時 addroom 的值是一個函式,我們希望 addroom 將資料推進 rooms 裡。要推的資料我們從上面房型資料中複製一組下來,並且將內容編輯一下。

現在,我們如果點擊「+ 增加房間」,Vue 就會幫我們增加一組房間的卡片,並且左邊也有對應的編輯區域,我們可以在編輯區直接修改房間資料,這樣是不是非常方便呢!

var vm = new Vue({
  el: "#app",
  data: {
    rooms: rooms,
    service_fee: 200,
    discount: 0.9
  },
  methods: {                    // 增加 methods,值是一個物件
    addroom: function () {      // 增加 addroom,值是一個 function
      this.rooms.push({         // 將資料推進 rooms 裡
        name: "新房間",
        eng: "new Room",
        price: 0,
        amount: 0,
        cover: "",
        discount: 0,
        equipment: {
          wifi: true,
          bathtub: true,
          breakfast: false
        }
      });
    }
  }
})
新增房間卡片並可以直接編輯資料
新增房間卡片並可以直接編輯資料

在房型卡片上顯示房間設備圖示,利用 v-if 做條件顯示

接下來我們要放入房間設備的小圖示,我們會用到 Font Awesome 這個好用的字型圖示工具,因此我們到 Setting 的 CSS 載入 font-awesome 的 CDN。接著,我們將房間介紹的模板 template#room 裡面加上一組 icons,icons 裡有三個圖示分別對應的是房間設備資料中的早餐、浴缸以及 wifi。

// HTML
  template#room
    // ...
    .info
      h5 {{ room_data.eng }}
        .icons   // 增加三個圖示並用一個 div 包起來
          span
            i.fa.fa-coffee
          span
            i.fa.fa-bath
          span
	      i.fa.fa-wifi

// JavaScript
    name: "經濟雙人房",
    // ...
    equipment: {       // 三個圖示要對應的是房間資料中 equipment 的三個屬性
      wifi: false,
      bathtub: true,
      breakfast: true
    }

我們希望當設備裡屬性是 true 時顯示圖示,而 false 時隱藏圖示,要怎麼做呢?這時候可以使用條件渲染 v-if,v-if 的意思是當條件為 true 時,瀏覽器便會幫我們渲染出來,如果是 false,瀏覽器則會忽略該元素。

// HTML
	.icons
		span(v-if="room_data.equipment.breakfast")
			i.fa.fa-coffee
		span(v-if="room_data.equipment.bathtub")
			i.fa.fa-bath
		span(v-if="room_data.equipment.wifi")
			i.fa.fa-wifi

這時,房間列表中,每個房間設備的對應圖示已經可以顯示出來了。

利用 v-if 做條件顯示,在房型卡片加上房間設備圖示
利用 v-if 做條件顯示,在房型卡片加上房間設備圖示

增加房間設備圖示的編輯欄位

接著,我們要在編輯區域中增加房間設備的區塊。一樣用 v-model 綁定 room.equipment 對應的設備,但我們要使用核取方塊(☑︎)來編輯設備的有無,因此在 input 加上 type=”checkbox”,並且加上 form-check-input 的 class 來套上 Bootstrap 樣式。最後我們加上一些 CSS 效果讓視覺美觀一點。

// HTML
	label 房間設備
      label 早餐
	  input.form-check-input(
          type="checkbox",
          v-model="room.equipment.breakfast"
        )
      label 浴缸
        input.form-check-input(
          type="checkbox",
          v-model="room.equipment.bathtub"
        )
      label wifi
        input.form-check-input(
          type="checkbox",
          v-model="room.equipment.wifi"
        )

// CSS
  .info
    // ...
    .icons                    // 增加 icons 的 CSS
      display: inline-block
      margin-left: 10px
      span
        margin-right: 5px
        opacity: 0.6
增加房間設備編輯欄位
增加房間設備編輯欄位

增加刪除功能

接著我們要增加刪除房型的功能,包括在編輯區域刪除某個房型,以及在瀏覽區域也能點擊刪除。我們先來看看編輯區域的刪除功能怎麼做吧。

我們先利用 Font Awesome 增加一個垃圾桶的圖示 i.fa.fa-trash,跟新增房間一樣,我們要加上點擊功能,所以加上 (@click=”delete_room(id)”)。delete_room 是一個 method,我們指定它要刪除指定的 id 房型。不過我們的資料中沒有 id,所以我們需要 Vue 幫我們在抓資料的時候幫我們把房型的 index 也抓出來,以 index 當作每個房型的 id,因此我們在 v-for 中加上 v-for=”(room, id) in rooms”。( v-for 語法可參考 Vue 官方文件。)

取得 id 後,別忘了在 vm 實例中增加這個 method,在 delete_room 中,我們呼叫 this.rooms 陣列,然後用 splice 刪除從指定的 index 中刪除 1 筆資料。最後,利用 CSS 讓游標滑過垃圾桶圖示的時候顯示可點擊圖示,優化一點使用者體驗,到這裡,編輯區塊的刪除功能就完成了!

// HTML
      // 在跑迴圈的時候讓 Vue 也幫我們取得 id (這邊的 id 就是 index)
	.room_edit(v-for="(room, id) in rooms")  
	  h4 {{ id + 1 }}{{ room.name }}
	  label 房間名稱
      i.fa.fa-trash.cursor_pointer(@click="delete_room(id)")

// CSS
	.cursor_pointer 
	  cursor: pointer

// JavaScript
	methods: {
	// ...
		delete_room: function (id) {
	      this.rooms.splice(id, 1);
	    }
	}
增加刪除房間的功能
增加刪除房間的功能

接著,我們來增加房型一覽中的刪除功能。

我們要讓每個房型卡片的右上角有個 ✘ 圖示,點擊圖示可以刪除,跟剛剛是不是很像呢?我們一步步完成它,先利用 Font Awesome 加入圖示,接著利用 CSS 的絕對定位調整位置,然後加上顏色變化。

// HTML
	template#room
	  .room_container
	    .cover(v-bind:style="bg_css")
	      h3 {{ room_data.name }}
	      i.fa.fa-times               // 增加圖示

// CSS
	i.fa.fa-times
      position: absolute
      top: 10px
      right: 10px
      color: white
      cursor: pointer
      transition: 0.5s
      &:hover
        color: $color_red

接著,我們讓 Vue 元件繼承 id 和 delete_room method,然後將刪除圖示也加上點擊刪除的功能 (@click=”delete_room(id)”),這樣,兩邊的刪除功能都完成囉!

// HTML
	.col-sm-9
    h1 房間列表
    hr
	    .row
                                     // 一樣讓 Vue 幫我們取得 id
          .col-sm-4.col-room(v-for="(myroom, id) in rooms") 
          room(
            :room_data="myroom",
            :id="id"               // 這邊的 id 會抓取迴圈跑出來的 id
            :hotel_discount="discount",
            :hotel_fee="service_fee",
            :delete_room="delete_room",    // 繼承的 delete_room
          )
	// ...

	template#room
	  .room_container
	    .cover(v-bind:style="bg_css")
	      h3 {{ room_data.name }}
	      i.fa.fa-times(@click="delete_room(id)")    // 加上點擊刪除功能


// JavaScript
	Vue.component("room", {
	  template: "#room",
	  props: ["room_data", "hotel_discount", "hotel_fee", "delete_room", "id"]   
		// 增加繼承 delete_room 以及 id
	}
房間卡片右上角的x刪除功能也完成了
房間卡片右上角的x刪除功能也完成了

收整房間編輯區塊

最後,左邊的編輯區塊看起來落落長的,我們把目前不需要用到的資料收整起來,只顯示要編輯的房間資料區塊就好了。

要收整所有房間到下拉式選單裡,我們增加一個 select 標籤,並且在 select 中設定 option 標籤,標籤我們要綁定房間的 id 以及顯示房間名稱,一樣用到的是 v-for,到這裡你是不是已經對 v-for 很熟悉了呢!因此我們在 option 後面加上 (v-for=”(r, id) in rooms”, :value=”id”) {{ r.name }}。這樣我們的下拉選單就做好了!

// HTML
h1 房間編輯
select.form-control(v-model="edit_id")
  option(v-for="(r, id) in rooms", :value="id") {{ r.name }}
收整左側房間編輯區塊
收整左側房間編輯區塊

接著,我們要讓編輯區塊在我們新增房間的時候,同時跳轉到新房間的編輯畫面。另外我們要把刪除房間按鈕綁定的 id 改成編輯中的房間 id 。

// HTML
	hr
	// 原來是 .room_edit(v-for="(room, id) in rooms")
	.room_edit(v-for="(room, id) in [rooms[edit_id]]")
	  h4 {{ room.name }}
        label 房間名稱
	//原來是 i.fa.fa-trash.cursor_pointer(@click="delete_room(id)") 
          i.fa.fa-trash.cursor_pointer(@click="delete_room(edit_id)")

/// JavaScript
  methods: {
    addroom: function () {
      this.rooms.push({
	  // ...
      });
      // 當 addrom 時,同時讓 edit_id = 最後一間房間
      this.edit_id = this.rooms.length - 1 },

最後,我們把「+ 新增房間」的功能按鈕位置調整一下,並且加上 Bootstrap 的 class 修改它的視覺效果,畫面是不是更美觀了呢。到這邊,我們的 Vue component 實作練習就大功告成囉!

// HTML
	h1 房間編輯
	  select.form-control(v-model="edit_id")
	    option(v-for="(r, id) in rooms", :value="id") {{ r.name }}
        // 移動「+ 新增房間」按鈕位置,並加上 Bootstrap class
	  button.btn.btn-secondary.room_edit(@click="addroom") + 新增房間 
調整編輯欄的視覺,完成此次練習
調整編輯欄的視覺,完成此次練習

總結

這次的練習真是段漫長的旅程啊,最後一起回顧一下我們完成了什麼吧:

  1. 學習 Vue.js 的基本概念及語法:我們認識了 Vue.js 是什麼,並且學習 v-for、v-bind 等 Vue.js 的基本指令。
  2. 實作練習 Vue.js 的基本語法:我們用一組簡單的飯店資料練習如何實際使用 v-model, computed 等 Vue 的指令來帶入資料,透過資料來驅動畫面。
  3. 實作完成一個動態飯店清單:我們利用 Vue 元件及 v-for 迴圈製作房型卡片,並且運用元件的繼承屬性以及繼承方法等特性,透過 v-model 做雙向綁定,讓資料能在一端修改時同步更動所有相依資料。

看見完成品你是不是也有滿滿的成就感呢?如果想看完整的程式碼,可以參考老闆的 Codepen。如果想跟 Vue 更熟悉,很推薦你實際看一下 Vue.js 的官方文件,透過一次次的查找資料跟練習,你也一定能將 Vue 用得像呼吸一樣自然!那我們下次見啦。 ₍₍ ◝( ゚∀ ゚ )◟⁾⁾


老闆的工商時間

想了解更多如何寫出漂亮清晰的網頁嗎?老闆在 Hahow 的教學課程 動畫互動網頁程式入門(HTML/CSS/JS) 用平易近人的語言,用簡單的方式帶你作出不簡單的網頁。已經有網頁程式基礎了嗎?進階課程 動畫互動網頁特效入門(JS/CANVAS) 能讓你紮實掌握 JavaScript 程式與動畫基礎以及進階互動,整合應用掌控資料與顯示的Vue.js前端框架,完成具有設計感的互動網站。

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

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 【Vue.js入門】一小時學會 Vue.component,完成動態飯店房間清單 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
Vue.js入門:英文2000字互動遊戲網頁 https://creativecoding.in/2021/08/26/vue-js-%e8%8b%b1%e6%96%872000%e5%ad%97%e4%ba%92%e5%8b%95%e9%81%8a%e6%88%b2%e7%b6%b2%e9%a0%81/ Thu, 26 Aug 2021 01:38:00 +0000 https://creativecoding.in/?p=1397 你多久沒鍛鍊英文了呢?這次直播使用HTML(Pug)CSS(Sass)以及Javascript(Vue.js)來完成一個練習2000字英文單字的小互動遊戲網頁,難度適中,尤其適合剛開始學習Vue.js的新手,無論是跟著步驟逐步操作,或是聽老闆的影片都能快速完成。

這篇文章 Vue.js入門:英文2000字互動遊戲網頁 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
英文2000字選擇題互動網頁完成圖
英文2000字選擇題互動網頁完成圖

這次直播的主題來自於老闆在2018年印象清華-物聯網科技藝術展中創作的展品〈英文8-2〉,其概念為十根代表清大不同學院的光柱,使用者只要掃描光柱上的QR Code就會跳出互動式的英文單字題目,只要答對題數越多、分數就越多,累積的分數便會即時地投射到光柱上,形成高高低低、動態交錯的有趣光景。

在這次直播中會來聊些這個專案內使用Vue的相關經驗,聊聊製作互動裝置藝術實作時整合的各種辛酸血淚史,以及如何快速地解析別人資料,利用Vue框架製作成幫你找回國中逝去英文能力的遊戲。

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

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

前期準備

在開始之前我們根據概念來進行規劃,想像一個英文單字的互動答題App需要哪些東西:

  1. 整理網路上現成的單字表,把資料變成符合我們條件的JSON格式物件,單字表必須同時具備英文、中文與詞性(今天借用的是109英文銜接教材2000單字
  2. 產生隨機的英文題目,並利用整理後的物件選出正確答案和其他類似的詞當作選項,並判斷答題者的正確與否
  3. 如果答題者正確,跟後端同步狀態累加分數

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

程式環境設定
程式環境設定

接著把單字表上的文字複製下來貼到Javascript中,var一個a,並且用ES6的頓號 ` 把文字包起來。

//Javascript
var a = `
A
able adj. 有能力的
about prep. 有關
above adv. 在上方
`

註:考慮篇幅關係這邊只貼上部分A字首的單字,實際資料請參考單字表。

我們快速分析一下他的架構組成,參考字首A的部分得知單字表主要可以分為:字首的段落開頭、英文、詞性、中文,也就是──只要是沒有英文單字的那一行就不會有「.」,如果說我們今天要把單字整理成一個一個的物件時,可以把每一行先分割出來、把含有「.」的留下,再分別拆解成英文、詞性、中文,這就是我們所需的資料。

1. 拆解單字表

利用語法split以空行來做分隔,再用語法filter把含有.的行過濾保留下來,利用list.lengthlist[n]在console查看過濾後的listlist2數量上的差異,代表我們的資料越來越乾淨了。利用語法map把原先陣列的一行一行轉化成一個一個,再存成另一個陣列,轉換的條件為用空格分割,console會發現list3裡面裝著一坨拉庫的[object Array](3)。再把list3拆分成wordcatatrans,分別對應英文、詞性、中文的物件。

//Javascript
var list = a.split("\n") //分割空行
var list2 = list.filter(item=>item.indexOf(".")!=-1) //過濾沒有.
var list3 = list2.map(item=>item.split(" ")) //單行轉單個
var list4 = list3.map(item=>({
  english: item[0],
  cata: item[1],
  trans: item[2]
})) //拆分成英文、詞性、中文

資料搬運小幫手Vue

Vue的特色在於資料雙向綁定,相較於jQuery需要選取物件、重新定義、再塞回去以直接操作 DOM 物件為主的方式,利用Vue的同步更新渲染資料可以幫助我們節省不少時間。Vue的寫法為在JS透過 new Vue建立作用範圍和待即時同步的資料,同時在HTML以{{}}包裹被更新的變數。以下為官方網站所舉的範例,Vue會將大括號{{}}的內容對應到message狀態,並且將之即時渲染至畫面上,也就是所指的「宣告式渲染」。

//HTML
<div id="app">
  {{ message }}
</div>
//Javascript
var app = new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

延伸閱讀: 「Vue.js 學習筆記 Day1」- 建立 Vue 應用程式 重新認識 Vue.js | Kuro Hsu

2. 實際運用Vue-單字小卡

運用Vue和剛剛整理好的單字表資料來試做一些英文小卡吧!取出list4中倒數20個單字用v-for迭代陣列中的物件,指定其資料種類並渲染在li的span,再給一些CSS的參數後就可以看到一張張排列整齊的你國中的惡夢單字小卡。透過Vue的幫忙,我們不用自己產生元件跟呈現,只需要確定資料是否正確即可。

//HTML
#app 
    h2 我的名字是{{name}}
    ul
      li(v-for="word in words")
        span {{word.english}} {{word.cata}} {{word.trans}} //指定word中的種類
//CSS
html,body
  background-color: #222

ul
  li
    background-color: #fff
    padding: 20px
    display: inline-block
    margin: 20px
    width: 200px
//Javascript
var vm = new Vue({
  el: '#app',
  data: {
    name: "Frank",
    catas: ["a","b","c","d"],
    words: list4.slice(-20) //負號代表從後面數來的20個單字
  }
})
英文2000字選擇題互動網頁:步驟二,製作出單字卡
英文2000字選擇題互動網頁:步驟二,製作出單字卡

3. 製作答題選項

製作完單字小卡有沒有覺得長得很像我們的答題選項呢?在Vue中我們定義methods為操作不同 DOM 元素的方法,這邊需要綁定幾個動作:

  1. click DOM元素根據滑鼠點擊的動作,在console回傳所點擊的單字。
  2. getOptions挑選同詞性、同字首的隨機四個單字。用filter過濾word.cata == question.cata也就是詞性需與答案相同,過濾第二次word.english[0] == question.english[0]英文單字的字首(第0個字)需相同,過濾第三次word.english !== question.english確認答案不會等於題目。
  3. 使用sort把陣列的順序打亂:.sort((a,b)⇒a-b)是將大的往後排,但sort((a,b)=>Math.random())則是隨機取值,再扣掉比較函數0.5後成為真正隨機排序的陣列,加上.slice(0,4)限縮在一次只取四個單字。
  4. .sort前面加入第二個.slice()把原本的元素複製一份成新的陣列,避免影響到既有陣列的順序,雖然針對新的陣列動屬性依然會影響原先的物件,但兩個陣列的順序是分開的。
  5. 亂數打亂正確答案的位置,目前都是把正確答案推到最前面,取得result後用concat連接question這個陣列,再打亂一次排序,成為result2
//HTML
#app 
  h2 我的名字是{{name}}
  ul
    li(v-for="word in words", v-on:click="click(word.english)") //讓console顯示出滑鼠點擊到哪個英文單字
      span {{word.english}} {{word.cata}} {{word.trans}}
//Javascript
var vm = new Vue({
  el: '#app',
  data: {
    name: "Frank",
    catas: ["a","b","c","d"],
    words: list4
  },
  methods:{
    click(word){
      console.log("click",word)
    },
    getOptions(question){
      let result = this.words.filter(
      word => word.cata == question.cata).filter(
      word => word.english[0] == question.english[0]).filter(
      word => word.english !== question.english
      ).slice().sort((a,b)=>Math.random()-0.5).slice(0,4)
      let result2 = result.concat([question]).slice().sort((a,b)=>Math.random()-0.5)
      return result2
    }
  }
})

註:concat只能做陣列與陣列的連接。

4. 建立一個出題目按鈕,產生新題目

methods新增pick(),在data中定義還沒開始之前question: null,用this存取本身的資料屬性,隨機選取陣列裡的其中一個字,記得因為index須為整數所以加上parseInt

//HTML
#app 
  button(@click="pick") 出題囉
  h2(v-if="question") {{ question.english }}
//CSS
html,body
  background-color: #222
  color: #fff
//Javascript
var vm = new Vue({
  el: '#app',
  data: {
    ...
    question: null
  },
  methods:{
    click(word){
      console.log("click",word)
    },
    pick(){
      this.question = this.words[parseInt(Math.random()*this.words.length)]
    },
    getOptions(question){
      ...
    }
  }
})
英文2000字選擇題互動網頁:步驟四,出題按鈕
英文2000字選擇題互動網頁:步驟四,出題按鈕

5. 建立選項

把單字的中文抓出來印成題目,同時也把選項抓出來存取,一開始會是空的陣列所以在data定義options: [],再在pick()中多加一行程式碼把產生的新題目裝回去。

//HTML
#app 
  button(@click="pick") 出題囉
  h2(v-if="question") {{ question.trans }}
    ul
      li(v-for="option in options") {{option.english}}
//CSS中要先把li的樣式暫時註解掉
//Javascript
var vm = new Vue({
  el: '#app',
  data: {
    ...
    question: null,
    options: []
  },
  methods:{
    click(word){
      ...
    },
    pick(){
      this.question = this.words[parseInt(Math.random()*this.words.length)]
      this.options=this.getOptions(this.question)
    },
    ...
  }
})
英文2000字選擇題互動網頁:步驟五,建立選項
英文2000字選擇題互動網頁:步驟五,建立選項

6. 判斷答案正確與否

比較簡單的做法是在產生資料時同時附加他是否正確的資訊在其中,我們複製一份新的question避免影響原本的,在let result2前面加上let questionClone = JSON.parse(JSON.stringify(question)),再把帶有正確與否屬性的物件混到原有的選項中,把questionClone作為判斷的正確答案,而result2中原本的question也要記得替換成questionClone

methods新增check(option),點擊選項時如果正確,console印出correct、不正確則印出wrong,回答完後再重新出題this.pick()

//HTML
...
ul
  li(v-for="option in options",
     @click="check(option)") {{option.english}}
//Javascript
...
  methods:{
    click(word){
      console.log("click",word)
    },
    check(option){
      if (option.correct){
        console.log("correct")
      }else{
        console.log("wrong")
      }
      this.pick() //點選答案無論對錯都會換下一題
    },
    pick(){
      ...
    },
    getOptions(question){
      let result = this.words.filter(
      word => word.cata == question.cata).filter(
      word => word.english[0] == question.english[0]).filter(
      word => word.english !== question.english
      ).slice().sort((a,b)=>Math.random()-0.5).slice(0,4)
      let questionClone = JSON.parse(JSON.stringify(question))
      questionClone.correct=true
      let result2 = result.concat([questionClone]).slice().sort((a,b)=>Math.random()-0.5)
      return result2
    }
  }
})
英文2000字選擇題互動網頁:步驟六,console顯示出選擇了正確或錯誤答案
英文2000字選擇題互動網頁:步驟六,console顯示出選擇了正確或錯誤答案

7. 增加答題分數計算機制以及顯示正確或錯誤

要增加答題分數grade的計算機制,首先在data中定義grade: 0,在check(option)中如果答對了就加一分this.grade++,並在HTML中顯示。

//HTML
#app 
  h3 Score:{{grade}}
  ...
//Javascript
...
  data: {
    ...
    grade: 0
  },
  methods:{
    click(word){
      ...
    },
    check(option){
      if (option.correct){
        console.log("correct")
        this.grade++
      }else{
        console.log("wrong")
      }
      this.pick()
    },
    pick(){
      ...
    }
  }

目前答題正確與否只能靠分數是否有增加得知,要改成更直觀一點,點擊選項時如果正確,在題目右邊會印出correct並累加分數this.grade++、不正確則印出wrong,我們使用一個預設是空字串的變數status去儲存這個資訊,status設定過1秒後消失,回答完後再重新出題this.pick()

通常使用者在進到介面時題目已經出好了,答題後會自動更新,所以頁面剛載入時便自動執行一次pickmounted代表Vue已經準備好可以幫忙計算資料了,所以跟methodsdatael在同一層級。

//HTML
#app 
  .container
    .row
      .col-sm-12
        h3 Score:{{grade}}
        h2(v-if="question") Q: {{question.trans}}
          .status {{status}}
        ul 
          li(v-for="option in options", @click="check(option)") {{option.english}}
//Javascript
var vm = new Vue({
  el: '#app',
  data: {
    name: "Frank",
    catas: ["a","b","c","d"],
    words: list4,
    question: null,
    options: [],
    status: "",
    grade: 0
  },
  mounted(){
    this.pick()
  },
  methods:{
    click(word){
      console.log("click",word)
    },
    check(option){
      if (option.correct){
        this.status =("correct")
        this.grade++
      }else{
        this.status = ("wrong")
      }
      setTimeout(()=>{
        this.status=""
        this.pick()
      },1000)
      this.pick()
    },
    pick(){
      this.question = this.words[parseInt(Math.random()*this.words.length)]
      this.options = this.getOptions(this.question)
    },
    getOptions(question){
      let result = this.words.filter(
      word => word.cata == question.cata).filter(
      word => word.english[0] == question.english[0]).filter(word => word.english !== question.english).slice().sort((a,b)=>Math.random()-0.5).slice(0,4)
      let questionClone = JSON.parse(JSON.stringify(question))
      questionClone.correct = true
      let result2 = result.concat([questionClone]).sort((a,b)=>Math.random()-0.5)
      return result2
    }
  }
})
英文2000字選擇題互動網頁:步驟七,增加答題對錯的顏色回饋

8. 設計畫面及加入動畫

完成骨幹後我們來稍微美化一下吧!在codepen設定CSS的地方引入Bootstrap和Animate.css,把DOM元素放到container內,讓版面豐富一些可以加入hover的滑鼠互動效果和答對或答錯時相對應的變色效果。

變色效果我們可以利用Vue的特性來製作──透過判斷式給予class,也就是判斷當前的status為correct或wrong,如果是correct則顯示綠色、wrong則顯示橘色,必須注意的是由於其他四個錯誤答案的status同時都會是wrong,所以要多下一個判斷點currentOption記錄只有點選的這個選項是wrong時才顯示橘色。在data定義currentOption: {}methodscheck(option)的加入條件this.currentOption = option

英文2000字選擇題互動網頁:步驟八,增加答題對錯的顏色回饋
英文2000字選擇題互動網頁:步驟八,增加答題對錯的顏色回饋

淡入的效果我們則透過Animate.css和Vue的key來製作,每次物件重新產生時都帶有新的english值,所以我們給予的key值也會不一樣,這樣他的animated.fadeIn效果就會重新被載入。

//HTML
#app 
  .container
    .row
      .col-sm-12
        h3 Score:{{grade}}
        h2.animated.fadeIn(v-if="question",:key="question.english") Q: {{question.trans}}
          .status {{status}}
        ul.animated.fadeIn(:key="question.english")
          li(v-for="option in options", @click="check(option)",:class="{correct: status=='correct'&& option.correct, error:status=='wrong' && currentOption.english==option.english}") {{option.english}}
//CSS
ul
  list-style: none
  padding: 0
  li
    padding: 10px
    margin-top: 20px
    border: 1px solid white
    cursor: pointer
    font-size: 30px
    transition: .5s
    &:hover
      background-color: rgba(white,0.1)
    &.correct
      background-color: #38d138
    &.error
      background-color: #ff7332
      
.status 
  float: right
//Javascript
...
var vm = new Vue({
  el: '#app',
  data: {
    name: "Frank",
    catas: ["a","b","c","d"],
    words: list4,
    question: null,
    currentOption: {}, //給予{}以防報錯
    options: [],
    status: "",
    grade: 0
  },
  mounted(){
    this.pick()
  },
  methods:{
    click(word){
      console.log("click",word)
    },
    check(option){
      this.currentOption = option //加入條件
      if (option.correct){
        this.status =("correct")
        this.grade++
      }else{
        this.status = ("wrong")
      }
      setTimeout(()=>{
        this.status=""
        this.pick()
      },1000)
    },
    pick(){
      this.question = this.words[parseInt(Math.random()*this.words.length)]
      this.options = this.getOptions(this.question)
    },
    getOptions(question){
      let result = this.words.filter(
      word => word.cata == question.cata).filter(
      word => word.english[0] == question.english[0]).filter(word => word.english !== question.english).slice().sort((a,b)=>Math.random()-0.5).slice(0,4)
      let questionClone = JSON.parse(JSON.stringify(question))
      questionClone.correct = true
      let result2 = result.concat([questionClone]).sort((a,b)=>Math.random()-0.5)
      return result2
    }
  }
})

以上就是這次的英文2000字即時互動小遊戲網頁的製作介紹拉,這次講解了許多Vue的基礎概念與用法,如果是剛開始接觸Vue的朋友很適合拿來小練身手唷!我們下次再見啦~

成品請參考這邊 👉🏻 https://codepen.io/frank890417/pen/RygVde

英文2000字選擇題互動網頁成果

重點回顧:

  1. 利用filter()、sort()、slice()整理與排序資料
  2. 宣告式渲染的使用方式,資料與function的對應關係
  3. 動態判斷class製作css animation效果

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

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

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

墨雨設計banner

這篇文章 Vue.js入門:英文2000字互動遊戲網頁 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
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 - 互動程式創作台灣站

]]>
用Vue.js做快速換色與整理的便利貼牆吧!(下)(直播筆記) https://creativecoding.in/2021/07/09/%e7%94%a8vue-js%e5%81%9a%e5%bf%ab%e9%80%9f%e6%8f%9b%e8%89%b2%e8%88%87%e6%95%b4%e7%90%86%e7%9a%84%e4%be%bf%e5%88%a9%e8%b2%bc%e7%89%86%e5%90%a7-%e4%b8%8b/ Fri, 09 Jul 2021 02:31:00 +0000 https://creativecoding.in/?p=1178 需要發想靈感、紀錄個人代辦清單,或和他人討論嗎?製作一個能夠自由編輯、增刪、變色、拖曳編排的便利貼牆網頁,多個願望一次滿足。下集將便利貼牆美化、功能變得更完善,更連結firebase即時資料庫,再多張便利貼也不怕。

這篇文章 用Vue.js做快速換色與整理的便利貼牆吧!(下)(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
上一篇我們製作了可以新增、拖曳、換顏色的簡易版本便利貼,這次除了調整細節與動畫外,也會串接Firebase資料庫,讓便利貼牆可以多人同時編輯與更新。

用Vue.js做快速換色與整理的便利貼牆吧!(下)完成圖
用Vue.js做快速換色與整理的便利貼牆吧!(下)完成圖

這次的教學將有以下幾個重點,主要聚焦在既有功能的優化與新功能的添加:

  1. 點擊便利貼文字時因滑鼠與左上角距離設定的關係,會造成距離差而產生的跳動
  2. 加入刪除便利貼的功能
  3. 加入新增或刪除便利貼時,放大縮小的transition
  4. 修改顏色的控制列需和正在使用中的便利貼位置相對應(上次我們處理的方式是都先暫時放在畫面右手邊,這次把它修改得人性化一些)
  5. 編輯文字和多行文字時呈現的大小
  6. 串接Firebase保存資料,即時整理與更新

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

如果想搭配直播影片一起實作,請往這邊走

修正點擊便利貼文字造成的距離差

首先fork一份上一次的檔案,fork就像是複製,這樣接續修改也不會動到最初的原始檔唷。

接下來我們在CSS的.postit裡面加上.textpointer-events: none,讓他停止觸發任何的點選事件,在製作滿版圖面但不希望阻礙滑鼠事件時,可以使用這種處理方式(比如:市面網站常見透過hamburger選單收合的滿版menu)。

修正點擊便利貼文字造成的距離差
修正點擊便利貼文字造成的距離差

加入刪除便利貼功能

刪除的概念可以想成「從包著便利貼id的陣列中,運用語法 splice() 切掉該張便利貼的id」。在colorList中新增刪除按鈕如下:

button.btn(@click="postits.splice(pid,1)") 刪除

新增 / 刪除的transition

接著加入新增和刪除的動畫,我們使用vue的transition group處理。在使用上有以下幾點特性:

  1. 在HTML新增,transition-group(name="fade"),注意transition Group需要包成一個div使用,這邊用tag="ul"處理,而便利貼為li
  2. Group內的每個物件都需要名字,才知道控制動畫的元件範圍,給他:key="pid",每張便利貼都有個獨一無二的id

利用vue官方提供的效果fade稍微調整一下語法,加入scale讓便利貼有從小變大、長出來的效果。

//HTML
#app
  transition-group(name="fade", tag="ul")
    li.postit(v-for="(p,pid) in postits",
            //為每張便利貼加上一個id
            :key="pid",
            :style="postitCss(p)",
            @mousedown="selectId($event,pid)")
      .text {{p.text}}
//CSS
.fade-enter-active, .fade-leave-active 
  transition: .5s

.fade-enter, .fade-leave-to 
  opacity: 0
  transform: scale(0.1)
新增 / 刪除的transition
新增 / 刪除的transition

調整顏色控制列的位置

為了讓修改顏色的功能更人性化一些,我們把.colorList整包移到.postit裡面,用position: absolute定位在便利貼下方。這時你會發現熟悉的點擊距離差問題又回來了,但因爲修改顏色和刪除的機制也是透過滑鼠,所以無法使用之前的方法來解決。

先前拖曳功能的設定為——點擊便利貼時,將該張便利貼設定id為0,當滑鼠移動時同步更新設定。現在把控制列跟便利貼拆成不同部分,也就是說點擊控制列時不進行id的設定。

selectId的event中判斷source element是否含有blockbtn,如果沒有,則進行id的設定,如果有,則nowId=-1

//CSS
.colorList
  position: absolute
  bottom: -80px
  display: flex
  flex-direction: row
  .block
    margin-right: 10px
//JavaScript
selectId(evt,id){
  console.log(evt)
  let isBlock = evt.srcElement.classList.contains('block')
  let isBtn = evt.srcElement.classList.contains('btn')
  if (!isBlock && !isBtn ){
    this.nowId=id //滑鼠點下去
    this.startMousePos = {
      x: evt.offsetX,
      y: evt.offsetY
    }
  }else{
    this.nowId=-1
  }
}
調整顏色控制列的位置
調整顏色控制列的位置

文字編輯和多行文字時呈現的大小

如同調整顏色的控制列,我們希望在編輯文字部分可以有更好的使用者體驗,透過點擊該張便利的「編輯」按鈕即可修改。在Vue裡增加 setText 這個方法,利用語法 prompt() 跳出修改視窗,並在input欄位顯示原始的文字(透過抓取pid知道是哪張便利貼、上面有甚麼文字):

//HTML
//新增編輯按鈕
button.btn(@click="setText(pid)") 編輯
//JavaScript
methods:{
  ...
  setText(pid){
    //彈出視窗修改文字
    let text = prompt("請輸入新的文字", this.postits[pid].text)
    //送出之後再便利貼上更新文字
    if (text){
      this.postits[pid].text=text
    }
  }
}
文字編輯和多行文字時呈現的大小
文字編輯和多行文字時呈現的大小

連接Firebase資料庫

接下來進入今天的重頭戲——串接Firebase資料庫。Firebase是Google提供的雲端開發平台,協助 開發者在雲端快速建置後端服務,提供即時資料庫。這種noSQL(非關聯式)類型的資料後端平台可能是未來的趨勢,noSQL代表你不會用像select all member這種特殊的資料查詢語法,他就是一張樹狀圖,把所有東西塞進去,所以你可以看到便利貼在頁面上即時地移動與資料修改,在Firebase資料庫裡也可以看到頁面上的改動。

首先前往Firebase的控制台,新增一個for便利貼的專案。

接著選擇Realtime Database,在專案中新增child如下:

tips: 右邊的值必須要先輸入一個default數值,之後有資料存入時便會被取代掉了。

接著引入我們的codepen網頁,在codepen引進CDN,再初始化資料庫。

Step 1:進到Overview,點選「網頁」。

Step 1:進到Overview,點選「網頁」。
Step 1:進到Overview,點選「網頁」。

Step 2:將Firebase新增至codepen。CDN為第一個script內的src,初始化config為下方的firebaseConfig。

Step 2:將Firebase新增至codepen,初始化config為下方的firebaseConfig。

Step 3:引入CDN,第一個script內的src貼入codepen settings。

Step 3:引入CDN,第一個script內的src貼入codepen settings。
Step 3:引入CDN,第一個script內的src貼入codepen settings。

Step 4:初始化資料庫,將Step 2裡面第二個script中的程式碼複製貼在我們JS程式碼的最上方。

Step 4:初始化資料庫,將Step 2裡面第二個script中的程式碼複製貼在我們JS程式碼的最上方。
Step 4:初始化資料庫,將Step 2裡面第二個script中的程式碼複製貼在我們JS程式碼的最上方。

接著透過Firebase手動新增一張便利貼如下,注意資料的層級,尤其是代表便利貼位置的x, y是在pos下面。

透過Firebase手動新增一張便利貼
透過Firebase手動新增一張便利貼

再把codepen跟建立好的firebase資料庫串接,並監聽他的value做即時更新,可以看到我們剛剛手動在資料庫新增的便利貼。Firebase語法可參考官方文件

//JavaScript
var postitsRef = firebase.database().ref("postits2"); //建立連結
  postitsRef.on('value', (snapshot)=>{
   vm.postits = snapshot.val() //即時更新
  })

註:firebase串接語法已更新成firebase.database().ref(),直播內容的firebase.database.ref()為舊版。

把codepen跟建立好的firebase資料庫串接,並監聽他的value做即時更新
把codepen跟建立好的firebase資料庫串接,並監聽他的value做即時更新

遠端新增 / 刪除便利貼

可以透過資料庫新增並呈現在頁面上後,我們這邊試試透過codepen push便利貼進去資料庫,修改methods addPostits的地方,讓他不是新增在vm這邊而是postitsRef

//JavaScript
addPostits(){
  postitsRef.push(
    {
      text: "文字",
      color: "yellow",
      pos: {x: 200+Math.random()*100, y: 200+Math.random()*100 }
    }

我們希望能刪除特定便利貼的節點,也就是postitsRef下的子結點,記得也需修改HTML刪除按鈕的語法為@click="deletePostit(pid)",並在JS新增以下methods:

deletePostit(pid){
  postitsRef.child(pid).remove();
}

同步顏色 / 文字 / 拖移位置

除了更新頁面上便利貼的位置,也同步更新遠端資料庫的位置,所以在mousePos抓這張便利貼this.nowId,設定set更新遠端資料庫的值。

//JavaScript
postitsRef.child(this.nowId).set(this.postits[this.nowId])

同步文字的邏輯也是類似的,在setText做完本地更新後,也一起更改資料庫的文字。

//JavaScript
postitsRef.child(pid).set(this.postits[pid])

最後一個則是顏色,我們原先是讓便利貼的顏色等於顏色的名字p.color=color.name,現在為了更新遠端資料 ,我們把它包成一個function叫setColor。在methods定義setColor的作用,概念和setText類似。

//HTML
.colorList
  .block(v-for="color in colorList",
     :style="{backgroundColor: color.color}",
         //修改成成為setColor function
     @click="setColor(pid, color.name)")
//JavaScript
setColor(pid,colorname){
  this.postits[pid].color=colorname
  postitsRef.child(pid).set(this.postits[pid])
},

以上就是這次的直播內容,主要聚焦於功能的優化與資料庫的串接,後面firebase的部分對於初次接觸的人可能會需要一段時間的摸索,但只要成功串接起來、再多研究一下文件,就會比較好入手。

步驟總結

這次的直播內容比較複雜,所以在最後來個總重點整理一下。在上篇我們先建立便利貼的基礎,從樣式雛形到基本資料處理,可以分為以下幾個重點:

1. 便利貼樣式與資料處理 – 建立便利貼架構,色票、文字樣式和文字位置
2. 加上滑鼠互動事件 – 紀錄滑鼠移動的位置並儲存在evt,運用nowId判斷滑鼠在哪一張便利貼的範圍裡面
3. 新增便利貼與修改顏色 – 做一個button點擊觸發function addPostits,在addPostits推入新的陣列

下篇的部分我們著重在既有功能的微調、更精緻化,還有資料庫的串接:

1. 修正點擊便利貼文字造成的距離差 – pointer-events: none停止觸發任何的點選事件
2. 刪除便利貼功能 – 運用語法splice()切掉該張便利貼的id
3. 新增 / 刪除的transition – 使用vue的原生transition group處理,transition-group(name=”fade”)
4. 調整顏色控制列的位置 – 把colorList整包移postit裡面,用position: absolute定位在便利貼下方
5. 文字編輯和多行文字時呈現的大小 – 利用語法prompt()跳出修改視窗,優化文字編輯的使用者體驗
6. 連接Firebase資料庫 – 在Firebase建立專案與Realtime Database,與Codepen資料連接
7. 同步顏色 / 文字 / 拖移位置 – 在mousePos抓特定便利貼this.nowId,設定set更新遠端資料庫的值

課程推薦

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

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

那我們下次再見啦👋👋👋

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

墨雨設計banner

這篇文章 用Vue.js做快速換色與整理的便利貼牆吧!(下)(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
用Vue.js做快速換色與整理的便利貼牆吧!(上)(直播筆記) https://creativecoding.in/2021/07/05/%e7%94%a8vue-js%e5%81%9a%e5%bf%ab%e9%80%9f%e6%8f%9b%e8%89%b2%e8%88%87%e6%95%b4%e7%90%86%e7%9a%84%e4%be%bf%e5%88%a9%e8%b2%bc%e7%89%86%e5%90%a7-%e4%b8%8a/ Mon, 05 Jul 2021 01:31:00 +0000 https://creativecoding.in/?p=1154 需要發想靈感、紀錄個人代辦清單,或和他人討論嗎?製作一個能夠自由編輯、增刪、變色、拖曳編排的便利貼牆網頁,多個願望一次滿足。上集我們使用Pug、Sass及Vue.js刻出便利貼的基本功能。

這篇文章 用Vue.js做快速換色與整理的便利貼牆吧!(上)(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
在需要發想靈感的職業日常中,便利貼常常作為靈感紀錄與討論道具,有著不可取代的重要性,但困擾的是常常黏性一過,原先貼得整整齊齊的便利貼只要一有風吹草動,就如同十二月的雪一樣翩翩飛舞。如果可以把這些小傢伙電子化,豈不是美事一樁?今天我們就要用Vue.js來實作可以快速地新增、輸入內容、換色、刪除甚至拖曳編排的便利貼牆。這一次的案例因為比較複雜,所以切成兩篇來做,請上下兩篇搭配一起服用唷~

用Vue.js做換色與快速整理的便利貼牆吧!(上)成品圖
用Vue.js做換色與快速整理的便利貼牆吧!(上)成品圖

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

如果想搭配直播影片一起實作,請👉🏻往這邊走

了解架構

在開始之前我們先來概念性的發想,便利貼的結構設計可分為以下這些方向:

  1. 儲存的資料種類—文字(顏色名稱)和色票
  2. 文字呈現方式—文字在便利貼上應該滿版呈現,隨著文字多寡而動態調整大小
  3. 拖曳功能—滑鼠點擊在便利貼上時紀錄初始座標,到結束時動態計算中間的滑動呈現
  4. 小功能—切換文字、刪除等功能

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

準備好Code Pen中的環境
準備好Code Pen中的環境

便利貼樣式與資料處理

接著我們開始從第一張便利貼刻起,便利貼的結構簡單來說就是一個裝有文字的框框,文字資料我們先暫時寫死,稍後再用Vue.js動態更新。

在HTML給他一個容器名為postit裡面裝一些文字text,接著在CSS設定他的樣式如下,如次便能得到一個文字在中間的基礎白色便利貼:

@import url('https://fonts.googleapis.com/css2?family=Noto+Sans&display=swap')
*, *:before, *:after
  border: solid 1px //之後再把邊線取消掉
  font-family: 'Noto Sans', sans-serif
@mixin size($w, $h:$w)
  width: $w
  height: $h

$colorBlack:#3E3A39

html, body
  // background-color: $colorBlack //背景色,設定樣式時先拿掉方便識別
  
.postit
  +size(240px)
  font-size: calc( 240px / 4 - 5px ) //便利貼寬÷字數-預留空隙
  display: flex
  justify-content: center
  align-items: center
第一章便利貼

接下來替便利貼上一些活潑的顏色,讓他變得好玩一些,同時調整字的顏色跟上一些陰影來增加設計的細緻度,我們的第一張便利貼就完成了。使用的色票和陰影處理如下,大家也可以自由選擇喜歡的顏色唷:

//修改字體顏色不要太死黑
color: #44403F

//便利貼顏色
$colorYellow: #FFEB67
$colorBlue: #A5D8D6
$colorRed: #EF898C
$colorGreen: #CBE196

.postit
	background-color: $colorYellow
  letter-spacing: 5px
  font-weight: 500
  box-shadow: 15px 10px 40px rgba(black,0.4)
調整便利貼的顏色、陰影
調整便利貼的顏色、陰影

接下來進行資料的處理,在JavaScript裡給予一組陣列名為postits,裡面裝便利貼會用到的參數textcolorposition。接著新增一個new Vue並指定作用範圍為#app,抓出物件便利貼跟他是第幾張(p,pid) in postits,將文字套進{{p.text}}。用左下角的Console檢查資料是否連接好,先輸入vm之後,再輸入vm.$data.postits[0].text="temp"資料,如果出現”temp”替換掉原先的文字「都市更新」就代表成功。

//Vue.js
var vm = new Vue({
  el: '#app',
  data: {
    postits: [
      {
        text: "都市更新",
        color: "yellow",
        pos: {x:20, y:0}
      }
    ]
  }
})
可替換內文的便利貼
可替換內文的便利貼

加上滑鼠互動事件

為了增加自由度,我們來替便利貼加上拖曳的功能。拖曳代表著我們必須在螢幕上做絕對定位,當在拖動物件時,左上角的距離不斷地重複更新,要做到這項事情,首先我們必須將資料與定位綁定起來。

在CSS的.postit裡加上position: absolute,有時在Vue我們會動態地加上style,但這容易造成HTML程式碼裡拖了一長串反而不好閱讀,所以像這種共用性高的style可以考慮直接在CSS做設定。

接著在JS設定style的[methods](<https://cythilya.github.io/2017/04/17/vue-methods-and-event-handling/>)如下,然後帶入HTML的postit後,可在Console透過剛剛上面的方法改變文字來測試字型大小的調整是否成功。

methods:{
  postitCss(p){
    return {
      left: p.pos.x+"px", //動態地更新便利貼的位置
      top: p.pos.y+"px",
      'font-size': ((240-30) / p.text.length) +'px' //根據文字長度動態設定大小
    }
  }
}
修改CSS與JS綁定文字資料及位置,以便加上拖曳功能
修改CSS與JS綁定文字資料及位置,以便加上拖曳功能

顏色的設定跟文字的方法差不多,在JS的data裡面新增一個名為colorList的陣列,裡面塞入我們剛剛的色票與相對應的名字:

colorList: [
  {
    name:"yellow",
    color: "#FFEB67"
  },{
    name:"blue",
    color: "#A5D8D6"
  },{
    name:"red",
    color: "#EF898C"
  },{
    name:"green",
    color: "#CBE196"
  },{
    name:"black",
    color: "#3E3A39"
  }
],

接著在下方的methods裡return他的值。在Vue裡面你要抓他的值可以直接用this指向,定義條件用find過濾符合的資料。

//JavaScript
'background-color': this.colorList.find(o=>o.name==p.color).color

這時我們可以製作一個control pannel來快速調整便利貼內的文字跟顏色,省去一直打開Console輸入指令測試的重複步驟。在HTML新增一個ul放入li和輸入欄位inputinput分別對應到p.text抓取輸入文字內容和p.color選擇便利貼顏色,再設定他的css。

//HTML
ul.datalist
  li(v-for="(p,pid) in postits")
    input(v-model="p.text")
    input(v-model="p.color")
//css
.datalist
  position: fixed
  right: 20px
  top: 20px
  width: 30%
設定便利貼顏色
設定便利貼顏色

前面做了資料和定位的綁定後滑鼠靜止的部分搞定,接下來要做滑鼠移動時的行為,這部分比較複雜可能需要多一點時間理解唷。我們在整個畫面上紀錄滑鼠移動的位置並儲存在evt內,右鍵檢查裡面有一個參數offset代表滑鼠距離左上角的相對位置。

小筆記:evt代表event,每次滑鼠移動時會觸發的一連串事件,包括滑鼠位置、點擊放開等,都會儲存在這裏面。

//JavaScript
window.onmousemove = (evt)=>{
  //滑鼠移動時,將最新位置記錄到vue中
  // console.log(evt)
  vm.postits[0].pos.x=evt.pageX //設定第一張便利貼的x距等於滑鼠在頁面上的x距
  vm.postits[0].pos.y=evt.pageY //設定第一張便利貼的y距等於滑鼠在頁面上的y距
}
滑鼠移動更新其在Vue.js內的位置

現在便利貼會跟著滑鼠移動,但我們希望移動是滑鼠點擊觸發之後才發生的。所以需要在data的地方多儲存一個nowId: -1nowId代表滑鼠點擊但還沒放開時,滑鼠在哪一張便利貼的範圍裡面。

我們在HTML裡加上@mousedown="selectId(pid)"並綁定點擊事件放在methods裡面,新增滑鼠移開的事件。為了方便識別可以把nowId印在畫面上。

//JavaScript
var vm = new Vue({
  ...	
  data: {
    ...
    nowId: -1
  },
  methods: {
    ...
    selectId(id){
      console.log(id)
      this.nowId=id //滑鼠點擊時選擇該張便利貼
    }
  }
})

//滑鼠移動時,將最新位置記錄到vue中
window.onmousemove = (evt)=>{
  // console.log(evt)
  if (vm.nowId!= -1){
    vm.postits[vm.nowId].pos.x=evt.pageX //抓取第nowId張便利貼的x距等於滑鼠在頁面上的x距
    vm.postits[vm.nowId].pos.y=evt.pageY //抓取第nowId張便利貼的y距等於滑鼠在頁面上的y距
  }
}

window.onmouseup = (evt)=>{
  vm.nowId = -1 //滑鼠未點擊時沒有選擇任何便利貼
}
//HTML
#app
  .postit(v-for="(p,pid) in postits",
          :style="postitCss(p)",
          @mousedown="selectId(pid)")
    .text {{p.text}}


ul.datalist
//把nowId印在畫面上
  li
    h1(style="color: white") {{nowId}}
  ...
設定滑鼠點擊與未點擊時的判別與動作
設定滑鼠點擊與未點擊時的判別與動作

單用window.onmousemove = (evt)監測時可能會有些狀況,我們希望當滑鼠有變動時,就在Vue裡偵測資料的變動並針對位置做更新,所以我們新增watch偵測是否有選擇便利貼,如果有就把這張便利貼抓出來。這張便利貼會等於所有便利貼的第nowId個,知道第幾張後就去設定他的位置。

var vm = new Vue({
  data: {
    ...
    mousePos: {
      x:0, y:0
    }
  },	
  watch: {
    mousePos(){
      if (this.nowId!= -1){
        let nowPostit = this.postits[this.nowId]
        nowPostit.pos.x = this.mousePos.x
        nowPostit.pos.y = this.mousePos.y
      }
      console.log(this.mousePos)
    },
  ...
})

window.onmousemove = (evt)=>{
  // console.log(evt)
  vm.mousePos = {x: evt.pageX, y: evt.pageY}
  //if (vm.nowId!= -1){
    //vm.postits[vm.nowId].pos.x=evt.pageX
    //vm.postits[vm.nowId].pos.y=evt.pageY
  //}
  
}

記得把作用範圍撐開跟window一樣大,不然會無法運作。

//css
html, body, #app
  background-color: $colorBlack
  padding: 0
  margin: 0
  overflow: hidden
  +size(100%)

拖曳的功能就完成了,但這時點擊便利貼的右下角時會有一些偏移、跳一下,我們可以記錄這個偏移量並加上點擊的位置,這樣一減一加之後我們就可以讓他乖乖待在位置上。在HTML的selectId新增$event,也記錄第一個點下去的位置startMousePos

@mousedown="selectId($event,pid)"
var vm =new Vue({
  ...
  data: {
    ...
    startMousePos: {
      x: 0,
      y: 0
    }
  }
  ...
  methods: {
    ...
    selectId(evt,id){
      console.log(id)
      this.nowId=id //滑鼠點下去
      this.startMousePos = {
        x: evt.offsetX,
        y: evt.offsetY
      }
    }
  }
})

watch減掉偏移量,有滑鼠移動而且我們判斷當下那張便利貼存在的時候。這樣拖曳功能就大功告成啦。

watch: {
  mousePos(){
    if (this.nowId!= -1){
      let nowPostit = this.postits[this.nowId]
      nowPostit.pos.x = this.mousePos.x-this.startMousePos.x
      nowPostit.pos.y = this.mousePos.y-this.startMousePos.y
    }
    console.log(this.mousePos)
  }

如果想要將滑鼠拖曳的效果變得更滑順,可以在CSS的.postit裡面加上cursor: pointer試試效果。

新增與修改顏色

接下來我們來做「新增」的功能,做一個button點擊觸發functionaddPostits。在addPostits推入新的陣列,位置給予亂數Math.random才不會覆蓋在原有的便利貼上。

新增便利貼功能
新增便利貼功能
//HTML
  ul.datalist
    ...
    button(@click="addPostits") +新增便利貼

最後做個改顏色的功能,透過點擊動態渲染的小方塊來改變便利貼的顏色,參考如下。

ul.datalist
  li
    ...
    .colorList
      .block(v-for="color in colorList",:style="{backgroundColor: color.color}", @click="p.color=color.name")
//css
.block
  +size(30px)
  background-color: #fff
  display: inline-block
//JavaScript
methods: {
  //直接從現有顏色清單裡選取一顏色
  getColor(name){
    return this.colorList.find( o=>o.name==name)  
  }
  ...
}
製作顏色小方框,方便直接更換便利貼顏色
製作顏色小方框,方便直接更換便利貼顏色

總結

以上就是前半段的便利貼教學,我們先建立便利貼的基礎,從樣式雛形到基本資料處理,可以分為以下幾個重點:

1. 便利貼樣式與資料處理 – 建立便利貼架構,色票、文字樣式和文字位置

2. 加上滑鼠互動事件 – 紀錄滑鼠移動的位置並儲存在evt,運用nowId判斷滑鼠在哪一張便利貼的範圍裡面

3. 新增便利貼與修改顏色 – 做一個button點擊觸發function addPostits,在addPostits推入新的陣列

下一次我們會接續進行刪除、背景調整、縮放動畫等的功能,我們下次見啦👋👋👋

老闆的互動網頁課程

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

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

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

墨雨設計banner

這篇文章 用Vue.js做快速換色與整理的便利貼牆吧!(上)(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
使用Vue.js建立一個sass色票卡視覺化網頁(直播筆記) https://creativecoding.in/2021/06/25/%e4%bd%bf%e7%94%a8vue-js%e5%bb%ba%e7%ab%8b%e4%b8%80%e5%80%8bsass%e8%89%b2%e7%a5%a8%e5%8d%a1%e8%a6%96%e8%a6%ba%e5%8c%96%e7%b6%b2%e9%a0%81/ Fri, 25 Jun 2021 02:28:00 +0000 https://creativecoding.in/?p=1124 寫網頁的時候,單看sass變數的程式碼,會不知道色碼是不是合適的嗎?老闆帶你一小時就能利用Vue.js快速建立好視覺化色票卡小工具。文章會從一開始雛型的設計稿發想,一步步教你如何完成工具。

這篇文章 使用Vue.js建立一個sass色票卡視覺化網頁(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
不知道大家有沒有以下經驗?網頁切版時,需要使用 sass 變數所建立的顏色,但是程式裡面只看到一堆色碼,卻沒辦法立即看到自己所選的變數顏色到底是不是需要的顏色。

老闆這次要帶大家做個小工具來解決這個問題,在開發時間有限的狀況下,利用工具輸入顏色變數名稱與色碼,透過產出的每一張色卡便能夠快速選擇需要的變數名稱與色碼。文章會從一開始雛型的設計稿發想,到最後使用 Vue.js 來完成工具。

想看著影片跟著老闆動手做,請到這邊,也附上這次成品

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

  • 開發專案前使用 PS, 與 Adobe XD 雛型發想的訣竅
  • 使用 Vue 進行畫面資料綁定、Vue 的資料再處理
  • 利用 Vue component,製作色卡元件

事前準備

老闆會先帶大家利用 PS 與 Adobe XD 規劃出成品雛型,如果大家沒有這些設計工具,可以使用老闆規劃的雛型進入開發,接著會說明 codepen 開發環境的設定。

規劃 UI

執行專案前,老闆習慣先使用 photoshop 初步構思成品,來做為草稿使用,你也可以隨手拿起一張白紙與一支筆,畫出你的草稿。

這次的畫面主要分為兩個部份,左邊的輸入框提供我們填寫 sass 顏色變數,右邊則為色卡,每一張色卡上面為顏色,下面則是色卡的標題與色號。開發時,就能利用這個工具找到需要的顏色,直接複製顏色變數使用。

Photoshop草稿
Photoshop草稿

下面跟大家介紹一個工具 – Adobe XD,可以快速建立想像的畫面,做雛型時,會希望文字或群組間的間距平均分布, Adobe XD 會貼心地跳出平均分布的提示。另外將圖片文字群組後,可以使用 repeat grid 快速製造多份。此外,拉出來的每個色卡可以各別調整名字,微調色卡文字位置時,其他色卡的文字也會跟著移動。若是一次拖曳多張圖片到 Adobe XD 中,能夠在每張色卡上,顯示不同的圖片。如果有興趣的話大家可以去下載試用版玩玩。

AdobeXD 圖稿
AdobeXD 圖稿

開發環境

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

  • html 使用 pug
  • css
    • 使用 Sass
    • 引用 twitter-bootstrap,要注意引用進來的版本不同,有些語法不通用
  • js 引用 2.1.10/vue.min.js

接下來會使用到的 API:

這次會使用到較多的 api,大家可以跟著老闆操作,比較能夠了解每個 api 使用時機,以下簡單介紹。

Html – bootstrap

由於老闆在這個專案使用到的是第四版的 bootstrap ,大家在使用上要注意自己引用的版本,這邊附上第四版 bootstrap 的文件

  • .container:bootstrap layout 最基本的容器,提供格線系統的操作。
  • .row:bootstrap 中 layout 的行。
  • .col-sm-4 欄:bootstrap 中 layout 的欄。包覆在最內層,在 sm 這個斷點內,這一個位置會占 4/12,12 是 bootstrap預設每行的欄數,那剩下的空間就是 .col-sm-8。

javascript, Vue

  • 使用 Vue 的方式如下,el 為要綁定的範圍,data 內為要綁定的資料們,computed 可以理解為資料再處理,會自動將值重新組裝換算後回傳。
// javascript
var vm = new Vue({
  el: "#app",
  data: {...},
  computed: {...}
})
  • Vue 資料綁定
    • {{name}}:在 html 內容,若是有雙花括的出現,裡面的名字會與 Vue 中 data 對應的 key 值替代。
    • v-model :另一種綁定資料的方式,若是在 input, textarea 等可填寫的 tag 中使用,則可以同步修改 Vue 中綁定的資料。
    • v-bind:使用 v-bind 來將資料綁定到 tag 中的指定屬性上,可以簡寫為 ” : ” 。
  • Vue Component:可以將 html 做成元件 template 重複使用,在 template 上面使用 id=”colorcard” 來宣告,並在 js 中進行註冊,註冊方式先給予元件名稱,再傳入對應的物件值。要使用時只要傳入對應的資料,就能讓元件使用傳入的值 props ,渲染出對應的元件。此外,component 也同樣能使用自己的 computed 屬性。
// html
colorcard(:card="card")

template#colorcard
// javascript
// 註冊元件名稱, 物件
Vue.component('colorcard', {
  // 引用的結構來源
  template: '#colorcard',
  // 傳入的資料
  props: ['card'],
  data: {},
  computed: {}
})
  • js api
    • String.split(‘\n’):使用指定的方式作為規則(‘\n’),切分成許多小字串的物件。連結
    • Array.push(物件):將物件加到 Array 中的最後一個。連結
    • .indexOf(條件):檢查字串或陣列,是否符合條件,如果有就回傳位置,沒有則回傳 -1。連結

跟著老闆開始動手做

基礎 html 結構樣式與 Vue 資料綁定

在資料綁定前,我們先進行畫面的切版。.container 要加上 #app 的原因,是因為後續我們要使用 Vue,這個 id 是為了提供 Vue 去抓作用的範圍。如果在使用 bootstrap 不確定自己的 div 佔據多少位置,可以在 css 內用 * 字號的類別選擇器,為所有 tag 加上 border,以便開發時了解佈局。

// html
.container#app
  h1 sass 變數色卡網頁工具{{name}}
  // hr 水平線
  hr
  .row
    .col-sm-4
      textarea
    .col-sm-8
      .colorcard
// css
*
  border: 1px solid 
textarea
  width: 100%
  height: 400px

新增一個 Vue,監視這裡面有哪些資料是要代換的,寫法如下:

大家可以發現 html 中的 {{name}} 馬上被 data 中的 name 取代,替代成 Codepen了,這就是畫面資料與 Vue 的資料綁定。

// javascript
var vm = new Vue({
  el: "#app",
  data: {
    name: "Codepen"
  }
})

textarea 資料雙向綁定色碼變數

現在遇到一個問題,我們要如何將一長串的色碼變數綁定到 Vue 中呢?

首先我們先在 Vue data 中新增一個 key 值 colorsetting,並將 textarea 的值貼進去,這時候會發現一個問題,程式報錯了。這是因為在 javascript 中,字串直接用換行會出錯,我們只要將複製進來的整串的色碼與色碼之間加上”\n”就可以將 textarea 的值變成一串字串。

這時我們想到,不可能每次都手動將顏色變數名稱和色碼加入到 data 中,希望能達成在畫面中的 textarea 新增資料後會同步到 Vue data 中,這時我們會使用到 Vue 雙向綁定資料方式 v-model。

我們可以先在 html 畫面中新增一個 p tag ,使用雙花括來綁定資料,做為資料內容的顯示。接著將 textarea 後面加上 v-model,這時再去試試看調整 textarea 中的資料,上面 p tag 的內容是不是也跟著變了呢?

// html
...
  .col-sm-4
    //顯示全部字串
    p {{colorsetting}}
    //單一色碼一行顯示
    textarea(v-model="colorsetting")
...
// javascript
var vm = new Vue({
  el: "#app",
  data: {
    name: "Codepen",
    colorsetting: "//顏色變數\n$color_black: #303030\n$color_bg: #F7F0E9\n$color_berry: #D56134\n$color_top: #9C4215\n$color_md_1: #F3D1BA\n$color_md_2: #F7E1CD\n$color_bottom: #773000\n$color_cherry_1: #FF613A\n$color_cherry_2: #DF5333"
  }
})

第一張色卡

在一次製作所有色卡前,我們來製作第一張色卡,來複習前面的資料綁定。首先先在 Vue 裡面新增一組變數 color_card,裡面包含三個內容,顏色變數名稱 name、色碼 colorcode、顏色樣式 colorcss,colorcss 待會會拿來與 .block 綁定樣式,要注意的是,Vue 的 key 值如果要使用到 “-” ,需要用雙引號將這個 key 值包住,直接改成小駝峰的方式也支援。

html 的部份我們為右邊的色卡區快新增了一個 div .colorcard ,裡面包含 .block 顯示顏色,以及 .info 顯示色卡的變數名稱 .name 與色碼 .code 並且分別綁定,這邊使用到雙花括之外,遇到了一個新的綁定方式 v-bind,這是 tag 中屬性的綁定方式,我們這邊用來綁定樣式 style,可以寫成 v-bind:style=””,或是 :style=””。而在css 的內容,因為我們要顯示顏色,所以先將 .block 撐出一個高度。

// javascript
...
data: {
 ...
  color_card: {
    name: '$color_berry',
    colorcode: '#D56134',
    colorcss: {
      "background-color": '#D56134'
      //或另一種寫法 backgroundColor: '#D56134'
    }
  }
}
// html
...
.col-sm-8
  .colorcard
    .block(:style="color_card.colorcss")
    .info
      .name {{color_card.name}}
      .code {{color_card.colorcode}}
// css
.block
  height: 300px

處理 textarea 字串

有了第一個色卡,接下來就要將 textarea 內的字串,變成多組一樣的資料格式,該怎麼處理呢? 在 Vue 中有一個屬性 computed,可以幫我們把資料進行再計算或處理。

讓我們先帶大家做個小模擬了解 computed 的作用,首先我們在 data 中新增一個變數 price,另外使用 computed 新增一個 nameprice,他會回傳一個值是由 price 與 TWD 組成,將這些值綁定到畫面後,結果就會如圖所示,這個後處理除了加字串外,還有很多地方可以應用,例如幫 price 加上折扣數。

// html
.container#app
  .price {{price}}
  .nameprice {{nameprice }}
// javascript
...
data: {
  price: 100
},
computed: {
  nameprice: function () {
    return this.price + "TWD"
  }
}

了解 computed 的作用後,接下來我們使用 computed 來將 textarea 內的 colorsetting 字串變成一組組的 color_card色卡格式,並用陣列存起來。

刪除了剛剛的小模擬之後,首先我們先在 computed 內建立一個新的 key 值,首先建立一個空陣列,先利用將 .split(‘\n’) 將 colorsetting換成一整組陣列。接著用 for 迴圈去針對這個陣列裡面的所有值去處理,使用 .split(‘:’) 取得變數名稱,改用空白去取得色碼,這邊要小心的是,因為 color 是用空白去分割字串,輸入到 textarea 的格式都要是”$變數名稱: 色碼”,冒號與色碼間要保留空白,否則在取得顏色色碼時會出錯。

取到 name 與 color 後,依照我們剛剛第一組色卡的資料格式,組出新的色卡物件後使用 push 加到 result 中。完成後就可以將前面的 html 綁定資料調整一下,色卡們就這樣出現了。

// javascript
data: {
...
},
computed: {
  colorcards: function () {
    var result = []
    var cut_string = this.colorsetting.split('\n')
    for(var i=0; i<cut_string.length; i++){
      var name = cut_string[i].split(':')[0]
      var color = cut_string[i].split(' ')[1]
      result.push({
        name: name,
        colorcode: color,
        colorcss: {
          "background-color": color
        }
      })
    }
    return result
  }
}
// html
...
.col-sm-8
  ul.row
    li.col-sm-3(v-for="card in colorcards")
      .colorcard
        .block(:style="card.colorcss")
        .info
          .name {{card.name}}
          .code {{card.colorcode}}

過濾不符合色卡格式的內容

想必大家已經發現奇怪的地方,為什麼標題也跟著加入了?如果輸入的資料不符合色卡的資料格式怎麼也顯示?沒錯,接下來我們就要再加工,讓 colorcards 回傳的內容能夠先篩掉不符合資料格式的內容。這邊我們示範變數名稱不符合規定要被篩掉的方式,大家如果有想到其他更嚴謹的篩選方式都可以實驗看看。

// javascript
...
colorcards: function () {
  var result = []
  var cut_string = this.colorsetting.split('\n')
  for(var i=0; i<cut_string.length; i++){
    var name = cut_string[i].split(':')[0]
    var color = cut_string[i].split(' ')[1]
    if (name.indexOf('color') != -1) {
      result.push({
        name: name,
        colorcode: color,
        colorcss: {
          "background-color": color
        }
      })
    }
  }
  return result
}

調整樣式

接下來就是收尾階段,將畫面美化,這邊我們總共會調整以下幾點:

html 部分

  • 拿掉綁定的 p {{colorsetting}}
  • 調整每張色卡的 col 欄數為 col-sm-4
// html
.container#app
  h1 sass 變數色卡網頁工具{{name}}
  hr
  .row
    .col-sm-4
      textarea(v-model="colorsetting")
    .col-sm-8
      ul.row
        li.col-sm-4(v-for="card in colorcards")
          .colorcard
            .block(:style="card.colorcss")
            .info
                .name {{card.name}}
                .code {{card.colorcode}}

css 部分 – 以下是老闆調整出來的最終樣式,大家也可以按照自己喜好去做調整。

  • 取消所有的 border
  • 增加 body 的 padding-top
  • 左邊 textarea 增加內距、行高、字距
  • 取消右邊色卡區塊的 ul 內距與 li 的 list-style
  • 每張色卡增加圓角、陰影、超出的部分隱藏
  • 色卡互動,增加 hover 效果、 transition 與游標笑果
  • 色卡內距:要注意調整內距時。寬度增加可以使用 box-sizing: border-box,意思是將框限與內距也算在寬度內。
  • 色卡資訊內距、文字顏色與粗細;色票號碼字級縮小,因為使用這個工具時,主要是要顏色的變數名稱,色碼不是必要的資訊。
// css
body
  padding-top: 50px

textarea
  width: 100%
  height: 400px
  padding: 10px
  line-height: 30px
  letter-spacing: 1px

ul
  list-style: none
  padding: 0
  
.colorcard  
  border-radius: 5px
  background-color: white
  overflow: hidden
  box-shadow: 0px 0px 10px rgba(black,0.2)
  transition: 0.5s
  cursor: pointer
  
  &:hover
    transform: translate(-5px,-5px)
  
  .block
    height: 100px
    
  .name
    font-size: 15px
    font-weight: bold
  .code
    font-size: 12px
    color: rgba(black,0.4)
    
  .info
    padding: 5px

li
  padding: 5px

Vue component

最後老闆帶大家認識一下 Vue強大的功能之一:元件 (Component),它可以將部分模板、程式碼封裝起來,以便開發者維護或重複使用。以這次專案為例子,色卡是重複的元件,所以我們來練習將色卡做為 component。

除了將原本 colorcard 的 html 結構改寫成 template#colorcard 外,我們也需要在 javascript 中註冊 colorcard 的元件,註冊的方式如下,component 也擁有自己的 computed 屬性,大家可以利用這個屬性來做其他的資料再處理。註冊完畢時,在對應的位置使用這個註冊好的元件名稱,傳入對應的資料。

使用 component 來改寫這些程式碼的好處,除了可以再處理資料外,也讓資料綁定畫面時,不用宣告這麼長的名稱 card.name 與 name2 的差異,閱讀上明顯清楚很多。

// html
...
  li.col-sm-4(v-for="card in colorcards")
    // 使用註冊的元件,並傳入所需的值
    colorcard(:card="card")
...      
template#colorcard
  .colorcard
    .block(:style="card.colorcss")
    .info
      .name {{name2}}
      .code {{card.colorcode}}
// javascript

Vue.component('colorcard', {
  template: '#colorcard',
  props: ['card'],
  data: {},
  computed: {
    name2: function () {
      return this.card.name
    }
  }
})

快速回顧

首先讓我們快速回顧一下,使用 Vue.js 開發 sass 色票工具的流程:

  1. 利用 PS 與 Adobe XD 規劃成品雛型
  2. 基礎 html 結構樣式與了解 Vue 資料綁定
  3. textarea 資料雙向綁定色碼變數
  4. 完成第一張色卡後,將資料綁定到色卡中
  5. 利用 computed 製作出所有色卡,並過濾不符合的資料
  6. 調整樣式
  7. 使用Vue component 改寫每一張色卡

萬事起頭難,一個作品不可能一步到位,將最終目標拆分成不同階段任務,從一開始的雛型慢慢開發出每個區塊,最後組裝在一起。透過這次的主題,讓大家能了解 Vue 框架,並發現它畫面資料綁定的方便。完成的作品如果能改善實務工作流程,肯定是一舉兩得,也可以透過自己實際使用後再回頭優化自己的作品。

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

老闆的互動網頁課程

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

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

墨雨設計banner

這篇文章 使用Vue.js建立一個sass色票卡視覺化網頁(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
讓我們來快速寫個漂浮感的輪播吧!動態網頁程式教學(直播筆記) https://creativecoding.in/2021/04/26/%e8%ae%93%e6%88%91%e5%80%91%e4%be%86%e5%bf%ab%e9%80%9f%e5%af%ab%e5%80%8b%e6%bc%82%e6%b5%ae%e6%84%9f%e7%9a%84%e8%bc%aa%e6%92%ad%e5%90%a7%ef%bc%81%ef%bc%88%e7%9b%b4%e6%92%ad%e7%ad%86%e8%a8%98%ef%bc%89/ Mon, 26 Apr 2021 05:36:00 +0000 https://creativecoding.in/?p=626 相信大家對於輪播一定不陌生吧,在各大官網、購物網站的重點區域常常可以看到輪播的蹤跡。除了增加與使用者的互動性以外,對於尺寸日漸縮小的行動裝置來說,輪播很重要的一點是可以在有限版面中增加傳遞的資訊量。今…

這篇文章 讓我們來快速寫個漂浮感的輪播吧!動態網頁程式教學(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
漂浮感輪播教學成果

相信大家對於輪播一定不陌生吧,在各大官網、購物網站的重點區域常常可以看到輪播的蹤跡。除了增加與使用者的互動性以外,對於尺寸日漸縮小的行動裝置來說,輪播很重要的一點是可以在有限版面中增加傳遞的資訊量。今天我們就來實作如何透過Vue.js簡化座標計算跟更新資料,快速打造漂浮效果的輪播功能吧!

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

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

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

輪播中不可缺少的靈魂角色──圖片,這邊介紹一個可供個人也可商用的圖片網站Lorem Picsum(註一),他的特點是圖片是一個個的URL,使用時可直接嵌入網站。除了隨機圖片以外也可以透過/id/{image}指定特定圖片,尺寸大小的部分只要在URL後面加上寬和高就可以囉!另外也有像灰階、模糊等等的參數,可說是練習切版時的必備良藥。

*註一:直播影片中所提的Unsplash it目前已更新成Lorem Picsum,另有一個圖片平台Unsplash提供類似服務,但圖片取得方式為下載。

準備動態網頁的圖片與文案

在JS準備一個陣列works把輪播的材料塞進去,每個輪播分別有標題、敘述和圖片這些素材,在陣列中利用{}區隔。大家可以自由選擇喜歡的圖片跟文案,記得文案跟URL要用雙引號包起來。

套入輪播的材料:標題、敘述和圖片這些素材
套入輪播的材料:標題、敘述和圖片這些素材

接下來用Vue把資料套進來,首先在HTML建立#app,在JS建立一個新的Vue物件,指定Vue的作用範圍為#app並指定data的陣列來源為之前在外定義的works(為了讓Vue能夠使用所以在內部再定義一次)。輪播的原理很像幻燈片,透過指定每一組物件編號、計算他的寬度來達成輪播的效果。既然需要輪流,我們就必須設定起始點為陣列中的第0張now_index:0然後設定每張幻燈片的寬span為930px。

漂浮感輪播教學-設定Vue
設定Vue指定每一組物件編號、計算寬度

動態網頁內幻燈片的輪播是怎麼運作的呢?

把資料都套進來之後接下來要做重複與更新,這邊用以下三個畫面階層來解釋輪播的運作:

  1. Postarea:把元素固定在畫面的中央,有點像是逐格動畫中固定的畫框。
  2. Posts:多個靜態畫面組成的長條畫面。
  3. Postbox:長條畫面中的每一張靜態畫面。

在HTML裡建立上述三個階層的畫面,其中postbox中又包含了cover(圖片)、h1(標題)、h5(敘述)這三個元素。我們直接先用任一張圖片和文案做代表,使用語法v-for迭代陣列中的元素渲染六次模擬效果,之後再把文字和圖片替代進去成變數即可。

使用語法v-for迭代陣列中的元素渲染六次模擬效果,之後再把文字和圖片替代進去成變數即可
#app
  .postarea
    .posts
      .postbox(v-for="index in 6")
        .cover(:style="background-image:url('https://picsum.photos/id/1084/800/600')")
          h1 海象樂園
          h5 趴在冰層上的懶惰動物

有了基礎骨架後來準備拉皮,準備兩組Sass的mixin如下:

  1. flex_center:將物件保持在畫面的垂直水平正中央。
  2. size:快速設定物件的寬和高,考慮到時常會製作正方形的物件,設定成如果沒有額外填寫$h時,自動帶入$w的值。
@mixin flex_center
  display: flex
  justify-content: center
  align-items: center
  
@mixin size($w,$h: $w)
  width: $w
  height: $h

為了不讓畫面隨著播放整個一起移動(scroll),把畫面撐到跟視窗一樣100%大,然後設定overflow:hidden,再把背景設定成深色#1c1c1c。

小心網頁中繼承設定

接下來要設定app、postarea、posts、postbox這四者之間的位置和尺寸,要注意的是CSS觀念中子層如何繼承父層的設定,這樣才會知道在設定這麼多height:100%時到底是吃到誰的高度。先把最外層的#app size撐到100%,然後把現在為div形態的.postbox用語法display:inline-block變成水平(橫向),因為子層寬度大於父層時會自動往下折,所以.postarea需要用語法white-space:nowrap處理,其他寬高設定可以參考以下。

製作幻燈片

先前有說過輪播是透過幻燈片的推移來完成,剛開始我們在Vue有設定過每一張的幻燈片的寬度為930px span:930cover的尺寸為330px,做個小小的數學運算後,可知幻燈片之間的距離應為600px,左右各300px,用語法background-size:cover讓海象完整呈現。

把標題和敘述包在infos中,我們可以觀察到其實文案和圖片只有小部分的重疊,比較偷呷步的做法是把infos包在cover內,用translateX製造偏移。在瀏覽器中h1、h5有預設的margin記得取消,其他參數設定附上詳細的sass給大家參考,這樣單張的幻燈片就做好囉!

sass
.cover
    +size(330px, 100%)
    margin-left: 300px //930px-330px
    margin-right: 300px
    background-size: cover
    +flex_center
    
  .infos
    color: white
    transform: translateX(-200px) //運用X軸偏移製造部份重疊
    text-shadow: 0px 0px 30px rgba(0,0,0,0.3) //避免文案與圖片重疊辨識不清
    *
      margin: 0 //取消預設的h1、h5 margin
    
    h1
      margin-bottom: 10px
      font-size: 50px
      font-weight: 400
      
    h5
      background-color: #fff
      color: #000
      padding: 4px 12px
      font-size: 20px
      font-weight: 300
      box-shadow: 0px 0px 30px rgba(0,0,0,0.3)

準備好幻燈片後,接下來要計算如何播放,在Vue裡面定義一個新的計算屬性computed,內部再包一個會回傳一組CSS動態地套進去的functioncomputed_left。那要如何計算呢?既然.postarea是一個固定的畫框,我們想像.posts這個長條每偏移一次now_index都向左移一個span的寬度,在HTML加上:style ="computed_left",在CSS裡面的.posts加上position: relative才能吃到位置的資訊哦!最剛開始的時候還不用偏移,所以now_index值從0開始。用console.log可以看到偏移的計算結果,記得再return結果。

computed:{
    computed_left(){
      var result={
        "left": (-this.now_index * this.span) + "px"
      };
      console.log(result);
      return result;
    }
  }

動起來了是不是很感動!但你有沒有發現輪播跟已發車的火車一樣一去不回頭,通常我們希望輪播是會循環的,當在最後一張按下下一張的按鈕時,就會自動跳回第一張從頭開始。讓我們稍微思考一下變換的pattern,可以運用餘數和總長度的關係來呈現。

methods: {
    delta(d){
      // 0 1 2 3 4 
      // (-1 + 5) = 4
      // (5 % 5)=0
      // ((id +5) % 5) = 0
      // -1 => ((-1+5) % 5) = 4
      // 1 => ((1+5) % 5) = 1
      // 5 => ((5+5) % 5) = 0
      this.now_index =
        (this.now_index + d +this.works.length) % this.works.length
    }
  }

會循環的輪播才叫輪播

完成了左右切換循環連播的功能,現在來把先前的資料換成變數,原先寫成固定CSS style的cover改寫成Vue的functionbg_css,這樣資料就抽換完成了。

JavaScript

bg_css(url){
      return {
        "background-image": "url("+url+")"
      };
    }

HTML

#app
  .postarea
    .posts(:style="computed_left")
      .postbox(v-for="w in works")
        .cover(:style="bg_css(w.cover)")
          .infos
            h1 {{w.title}}
            h5 {{w.description}}

再加上互動按鈕

有了輪播的圖片,少不了左右切換的按鈕和使用者互動,把font-awesome(一個提供很多icon的免費平台)掛入CSS的CDN。

在font-awesome上選好左右icon後加在HTML內,在CSS設定按鈕的參數。

這邊大家可以根據自己的美感調整,通常在執行動作時都會加上transition讓互動不會那麼生硬,這時可以新增transition的mixin來統一整個網站的互動參數。

@mixin trans($t:0.5s, $td:0s)
  transition: $t $td
//t為變化持續的時間,td為延遲變化的時間

在HTML裡再用click把剛剛寫的function delta綁定在按鈕上,往左往右分別設定成1和-1,可用按鈕操作的輪播就大功告成啦!

最後起鍋前再加點鹽 最後再加一些微互動提升質感,通常互動可以以滑鼠事件為主,當使用者的滑鼠進入到特定區塊時,透過放大或偏移來增加點擊的機率。這邊提供老闆的做法讓大家參考,也可以想想還有什麼其他有趣的互動唷!

SASS

.cover
    +size(330px, 100%)
    margin-left: 300px //930px-330px
    margin-right: 300px
    background-size: cover
    background-position: center center
    +flex_center
    +trans
    cursor: pointer
    
    &:hover
      +size(340px,110%)
      .infos
        transform: translateX(-220px) translateY(-10px)

另外我們也可以透過Vue,在物件符合特定條件下時,才加入指定的動畫。這邊呈現的效果是當幻燈片切入的瞬間,從淺到深的透明度轉換效果。

HTML

當現在呈現的物件的id=now_index時,就會帶入cur_item的效果

.postbox(v-for="(w,id) in works", :class="{cur_item: id==now_index}")

CSS

針對cover設定一組名為fadeIn的keyframe,切換的瞬間會從透明度0變成透明度1,x軸也會稍微偏移,整組動畫的時間為1秒,動態為ease。infos也加上一些x軸偏移的效果增加速度感。

@keyframes fadeIn
  0%
    opacity: 0
    transform: translateX(30px) 
    filter: saturate(0%)
  100% 
    opacity: 1
    transform: translateX(0px) 
    filter: saturate(100%) 
 
@keyframes sliceIn
  0%
    transform: translateX(-50px) 
  100%
    transform: translateX(0px) 

.cur_item
  .cover
    animation: fadeIn 1s ease both
  .infos
    h5
      animation: sliceIn 1s 0.1s ease
```

以上就是這次如何用Vue.js快速做出輪播效果,從最一開始的資料處理、計算輪播距離和計算delta變化量轉換成座標,最後再加上一些玩轉的細節動畫。

希望大家還喜歡這次的小範例,如果對類似的網頁效果有興趣,歡迎來看看我們在Hahow所開設的動畫互動網頁程式入門(HTML/CSS/JS)以及動畫互動網頁特效入門(JS/CANVAS)有更多好玩的網頁動畫教學,那我們下次再見囉。

Codepen實作範例:https://codepen.io/frank890417/pen/bWrKOZ

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

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 讓我們來快速寫個漂浮感的輪播吧!動態網頁程式教學(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
來做SVG動畫讓蔥油餅翻滾吧! (下):讓場景中的元件動起來(直播筆記) https://creativecoding.in/2021/01/15/%e4%be%86%e5%81%9asvg%e5%8b%95%e7%95%ab%e8%ae%93%e8%94%a5%e6%b2%b9%e9%a4%85%e7%bf%bb%e6%bb%be%e5%90%a7-%ef%bc%8d%e4%b8%8b%e7%af%87%ef%bc%9a%e8%ae%93%e5%a0%b4%e6%99%af%e4%b8%ad%e7%9a%84%e5%85%83/ Fri, 15 Jan 2021 10:46:07 +0000 https://creativecoding.in/?p=469 本文翻自 [週四寫程式系列] 來做SVG動畫讓蔥油餅翻滾吧!,若是對文章內容有疑問,或是想要老闆手把手帶你飛,都可以觀看影片詳細內容。 在上一篇中,老闆帶大家從發想素材、將素材引入網頁,到製作場景進出…

這篇文章 來做SVG動畫讓蔥油餅翻滾吧! (下):讓場景中的元件動起來(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

本文翻自 [週四寫程式系列] 來做SVG動畫讓蔥油餅翻滾吧!,若是對文章內容有疑問,或是想要老闆手把手帶你飛,都可以觀看影片詳細內容。

在上一篇中,老闆帶大家從發想素材、將素材引入網頁,到製作場景進出的動畫。接下來我們將要賦予場景中的每個元件生命。

目標

本篇文章中,老闆會帶大家完成蔥油餅小島中角色與物件的動畫,也會提示大家每個部份需要注意的地方,以下動畫的設定數值都都只是參考,開發時大家都能試試看不同的數值。

  • 實際應用﹔讓場景中的元件動起來

讓素材動起來吧

驚嘆號

首先我們從驚嘆號下手,老闆讓驚嘆號分布在場景的各處,用意是要提示使用者這邊有驚喜,引導使用者靠近這個區塊。為了增加提示效果,我們利用左右晃動的動畫效果,固定時間後會再撥放。

這邊使用了新的 css 屬性 animation ,需要搭配 @keyframes 使用,在 keyframes 用百分比去註記不同時間該顯示的樣式,透過 animation 定義動畫總時間,這也代表這個 keyframes 可以提供給不同元件使用,使用的元件賦予不同的時間等屬性,就是一個全新的樣貌。

要注意元件會以左上角為旋轉中心點,適時加上 transform-origin, transform-box 讓動畫更加合理。

[data-name="sign"]{
	// 引用動畫名稱 時間 延遲播放時間 次數
  animation: drag 2s infinite;
  transform-origin: center center;
  transform-box: fill-box;
}

@keyframes drag {
  0%{transform: rotate(0deg);}
  80%{transform: rotate(0deg);}
  85%{transform: rotate(10deg);}
  90%{transform: rotate(-6deg);}
  95%{transform: rotate(3deg);}
  100%{transform: rotate(0deg);}
}

路人 trigger_door_man

利用前面寫過的 keyframes drag 與 data-name 的概念,我們可以快速完成這個部分的動畫。當滑鼠進入感應區塊,路人能夠開始搖擺。

[data-name="trigger_door_man"]{
	&:hover{
	  animation: drag 2s infinite;
	  transform-origin: center bottom;
	  transform-box: fill-box;
	}
}
驚嘆號與路人

阿伯與跳起來的蔥油餅 trigger_cookie

要讓蔥油餅動起來之前,先從阿伯的手開始,結合蔥油餅動畫之後,讓蔥油餅宛如真的被阿伯翻了起來。撰寫這邊的動畫時,要注意調整 transform-origin ,決定動畫旋轉的參考點,阿伯的上半身也是用相同的方式處理,若是動畫沒調好,很有可能瞬間變成凶殺案,阿伯被腰斬的畫面。

透過 animation 內延遲時間設為負值,讓動畫預先開跑到其他時間點,也是讓動畫更加順暢的小訣竅。

[data-name="trigger_cookie"]{
	&:hover {
		[data-name="man_upper"]{
			animation: bake_cookies 2s infinite ;
			transform-origin: center bottom;
  		transform-box: fill-box;
		}
		[data-name="hand"]{
			// 動畫名稱 動畫時間 次數 延遲時間
			animation: drag 2s infinite -0.8s;
			transform-origin: left center;
  		transform-box: fill-box;
		}
	}
}
@keyframes bake_cookies {
	40%{
			transform: rotate(5deg);
	}
	50%{
			transform: rotate(-6deg);
	}
	70%{
			transform: rotate(-10deg);
	}
	90%{
			transform: rotate(-6deg);
	}
	100%{
			transform: rotate(5deg);
	}
}

接著我們要來處理跳起來的蔥油餅了,蔥油餅的思維比較不同,我們要讓蔥油餅分段顯示。 keyframe 的動畫內容改為使用透明度,讓透明度跟著時間改變,營造出分段顯示的效果,利用 animation-delay 來讓四個位置的蔥油餅,在不同的時間點顯示。這邊起始為置的蔥油餅會出現大部分的時間,所以要特別為它寫一個 keyframe。大家也可以試試看,調出順暢有趣的效果。

[data-name="trigger_cookie"] {
	&:hover {
		...
		[data-name="cookie1"] {
			animation: bake_cookies_locus 2s infinite -1s;
		}
		[data-name="cookie2"] {
			animation: bake_cookies_locus 2s infinite -0.8s;
		}
		[data-name="cookie3"] {
			animation: bake_cookies_locus 2s infinite -0.6s;
		}
		[data-name="cookie4"] {
			animation: bake_cookies_locus 2s infinite -0.4s;
		}
		[data-name="now_cookie"] {
			animation: now_cookie 2s infinite;
		}
	}
}
@keyframes bake_cookies {
	40% {
		transform: rotate(5deg);
	}
	50% {
		transform: rotate(-6deg);
	}
	70% {
		transform: rotate(-10deg);
	}
	90% {
		transform: rotate(-6deg);
	}
	100% {
		transform: rotate(5deg);
	}
}
@keyframes now_cookie {
	0% {
		opacity: 1;
	}
	49% {
		opacity: 1;
	}
	50% {
		opacity: 0;
	}
	90% {
		opacity: 0;
	}
	91% {
		opacity: 1;
	}
}

小孩與媽媽 trigger_cookie

不可能整個畫面都使用同樣的搖擺動態吧?所以我們為小孩換一個動態效果,讓他原地跳躍,要達成這個效果,可以改變 y 的值來達成,媽媽則是繼續搖擺效果。

[data-name="trigger_cookie"] {
	&:hover {
		[data-name="child"] {
			animation: jump 2s infinite -1s;
		}
		[data-name="mother"] {
			animation: drag 5s infinite;
			transform-origin: left bottom;
			transform-box: fill-box;
		}
	}
}
@keyframes jump {
	0%{
		transform: translateY(0px);
	}
	50%{
		transform: translateY(0px);
	}
	51%{
		transform: translateY(0px);
	}
	75%{
		transform: translateY(-10px);
	}
	100%{
		transform: translateY(0px);
	}
}
跳動的小孩與搖擺媽媽

結語

跟著操作之後,應該有 svg + css 製作動畫的概念,這時不妨動手試試看,讓其他小島的元件動起來。也可以畫些素材,加上動畫賦予他們生命力。動畫沒有什麼對或錯,可以按照自己喜好,去改變動畫效果或是調整時間,但要注意撰寫動畫時的連續繼承問題,像是阿伯的手(子層)會跟隨著身體(父層)擺動。下面老闆也提供三個議題,供大家在開發時參考。

引用 animate.css

每次都要重新寫 keyframe,若是寫 keyframe 寫累了或是遇到急案,為了加速開發,老闆這邊準備了一些補品,讓你可以快速套用現成的動畫效果。例如:想要實現當滑鼠進入感應區塊時,讓驚嘆號動畫改為閃爍動畫,在這邊我們結合 animate.css 製作閃爍動畫。引用 ainmate.css 後,當 data-name=”trigger ” hover 時,裡面 data-name=”sign”的動畫都改為 animation: flash 2s infinite。( flash 為 animate.css 的動畫名稱)

電腦與手機

由於手機等行動裝置不會有 hover 的行為,記得補上 focus,讓整個網頁能夠更完善。

了解你的使用者

把作品完成後,如果能夠知道自己這些得意之作是不是真的有被使用,可以做 GA 追蹤,老闆也因為 GA 的關係,發現其實使用者不太常使用換頁,導致這些動畫做了卻沒人看,參考數據後做了相對應的處理,增加作品露面的機會。

課程推薦

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

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


墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 來做SVG動畫讓蔥油餅翻滾吧! (下):讓場景中的元件動起來(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>