Animation 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/category/tutorial/animations/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Tue, 10 Aug 2021 11:37:33 +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 Animation 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/category/tutorial/animations/ 32 32 利用Pug(HTML)、Sass(CSS)及Vue.js製作動態時間軸年表(直播筆記) https://creativecoding.in/2021/08/04/%e5%88%a9%e7%94%a8pug-sass-vue-js%e8%a3%bd%e4%bd%9c%e5%8b%95%e6%85%8b%e6%99%82%e9%96%93%e8%bb%b8%e5%b9%b4%e8%a1%a8/ Wed, 04 Aug 2021 03:36:00 +0000 https://creativecoding.in/?p=1320 不論個人或是品牌,都需要讓網站造訪者快速暸解你的歷史發展,時間軸年表是一個常見且好用的表示方法。利用Pug(HTML)、Sass(CSS)及Vue.js製作出一個屬於你的動態時間軸年表吧!

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

]]>

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

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

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

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

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

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

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

資料準備

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

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

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

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

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

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

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

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

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

版面配置

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

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

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

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

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

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

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

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

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

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

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

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

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

.dialog_wrapper
  height: 160px
  position: relative

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

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

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

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

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

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

插入資料

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

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

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

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

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

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

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

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

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

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

觀念大補帖

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

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

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

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

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

墨雨設計banner

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

]]>
來用可怕的三角函數做網頁吧! -Part 2科幻時鐘(直播筆記) https://creativecoding.in/2021/05/14/%e4%be%86%e7%94%a8%e5%8f%af%e6%80%95%e7%9a%84%e4%b8%89%e8%a7%92%e5%87%bd%e6%95%b8%e5%81%9a%e7%b6%b2%e9%a0%81%e5%90%a7-part2-%e7%a7%91%e5%b9%bb%e6%99%82%e9%90%98/ Fri, 14 May 2021 02:16:00 +0000 https://creativecoding.in/?p=757 上一篇中,老闆利用三角函數幫 DOM 做 CSS 的絕對定位,這次我們雖然也是使用三角函數,但是改使用 canvas 在網頁上繪圖,製作科幻效果的時鐘。

這篇文章 來用可怕的三角函數做網頁吧! -Part 2科幻時鐘(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
利用三角函數與 canvas 製作科幻感時鐘
利用三角函數與 canvas 製作科幻感時鐘

本文翻自[週四寫程式系列] 來用可怕的三角函數做網頁吧! – Part2直播影片,對文章內容有疑問,或是想要老闆手把手帶你飛,都可以觀看影片詳細內容。

上次老闆帶大家利用三角函數製作衛星繞月球,還沒自己動手操作的同學,可以回上一篇挑戰,複習三角函數如何結合到網頁中。雖然數學看起來很可怕,但是它卻可以協助你製作有趣的效果,而數學中的表達式在各個地方都通用,不管是製作特效、資料分析,都可以讓你優雅地描述一件事的方式。

這次要製作的科幻效果,使用 FUI 風格,FUI 設計有許多不同的全名 (fake user interface, fictional user interface, fake user interface, 虛構使用者介面),這些 f 開頭的單字,代表還不存在的人機互動介面,常見於科幻電影場景中主角操作的機器介面,或是鋼鐵人 AI 介面…等,透過數據運算組成各種元件,讓畫面看起來科技感十足。

前情提要

開始製作前,老闆這邊提供這次範例的成果網址,讓大家在實作時可以參考。

上一篇中,老闆利用三角函數幫 DOM 做 CSS 的絕對定位,這次我們雖然也是使用三角函數,但是改使用 canvas 在網頁上繪圖。首先,讓我們快速回顧上一篇中提到的,應用三角函數幫 DOM 做絕對定位,讓遊戲元件在正確位置顯示,以砲台射擊遊戲為例,假設右上方有個目標物,我們要讓射出的砲彈的角度一直更新,才能能夠射到目標物,而每段時間砲彈要前進多少距離,則透過三角函數取得每次要調整的距離 Δy, Δx。

<canvas> 是一個 HTML 元素,我們可以利用程式在這個元素上繪圖(通常是用 JavaScript)- MDN

那麼又該如何知道 Δy, Δx 是多少呢?我們知道常見的三角形(30°, 45°, 60°)各邊的比例,但是當不是這些常見的角度時,就束手無策了。這時候我們就要感謝偉大數學家們的努力,只要知道 r 和 θ,就可以將 Δy 與 Δx 用三角函數去換算成 r * sinθ 與 r * cosθ。

數學家也從不同角度的三角形中發現,sinθ 與 cosθ 是一個規律的波浪圖,這代表著 r, Δy 與 Δx 值是成比例的關係。有了這些知識,就能開始著手製作 FUI 風格的時鐘了。以上我們帶大家快速回顧,要看更詳細的三角函數解說,可以參考 Part 1 影片和文章

畫個圓形吧

畫圖前,老闆先帶大家理解畫圓的觀念如下:從三角形、四角形,慢慢到多邊形,當與中心點距離一樣的點數亮夠多,視覺上就會像是畫出一個圓。首先,我們將中心點作為基準,每一次畫出的點與 r 的距離都相同,以 x 軸上的點做為起始點,將一圈 360° 均分為三點時,可以畫出三角形(左圖),四點的時候是四角形(右圖),以此類推,當點的數量夠多的時候就趨近圓形。

該如何將前面的這段敘述用程式寫出來呢?我們可以理解成將 360° 分成特定的份數,在座標軸上右邊 0° 是起始點,逆時針旋轉增加角度,假想有一個固定單位半徑 r 的圓在這個座標軸上,我們要畫出三角形,首先先將 360° 分成三等份,從 0° 的位置先點上一點,逆時針轉 120° 畫一點,最後再逆時針旋轉 120° 畫一點,三個點互相連接後,就成為了三角形,其他多邊形都能用這種方式去畫。聰明的大家這時候就可以發現,如果這個圓上有無限多個點,連接起來之後就趨近圓形。

那麼又該如何算出下圖中點 2 的座標呢?假設今天我們要畫一個 n 邊形,先將圓平分成 n 個點後,每次增加的角度是 360°/n,第 i 個點的角度就是 i *(360° / n)。用前面提到的三角形來做檢驗,第一個點就是 1 * (360° / 3) = 120°,第二個點就是 2 * (360° / 3) = 240°,第三個點就是 3 * (360° / 3) = 360°,也就回到了原點。第二點的座標算法為:x 座標 r * cosθ,y 則是 r * sinθ,其他點的座標算法一樣。接下來,就讓大家跟著老闆開始畫圖吧。

動手做

在我們要開始動手做之前,有幾個重點:

  1. 不能使用 canvas 中畫圓的函數。
  2. 因為電腦無法解讀 ” 從中心畫線到 30° 的位置 ” 這種口語的陳述,所以我們要去換算每個點的 x 與 y 的座標,並將所需要的點連線,才能畫出我們要的圖。

畫圖

理解畫圓的思維邏輯後,先跟大家介紹網頁畫圖的技術,在網頁畫圖我們會使用 canvas 來畫圖,畫圖的方式跟網頁定義 DOM 位置的不同,需要定義每個點的位置,點相連之後畫出我們需要的圖。

開始畫圖前,大家可以先將 codepen 的開發環境以及程式碼設置成跟老闆一樣,避免操作上的出入:

  • html 使用 pug
  • css 使用 sass
  • js 區塊將 jQuery 引用進來

HTML

canvas#myCanvas

CSS(Sass)

html, body
  width: 100vw
  height: 100vh
  margin: 0
  padding: 0
  overflow: hidden
  background-color: #000
canvas
  background-color: #fff

當我們把上面程式碼輸入後,會發現 canvas 的區塊沒有撐滿整個螢幕(下圖白色部分,這邊設定成白色背景是方便大家了解 canvas 的變化,之後在第5步驟開始畫方塊時,會將 canvas 的背景色拿掉)。

該怎麼調整 canvas 畫布跟螢幕一樣呢?我們在 js 區塊中撰寫以下程式碼,步驟如下:

  1. 利用 id 取得畫布的 DOM
  2. 透過取得的 canvas 來取得繪圖元件
  3. 設定畫布的寬高
    我們希望 canvas 可以撐開整個畫面,透過 outerWidth() 以及 outerHeight() 取得視窗的寬高,並將 canvas 畫布的寬高設定成這些值
    JavaScript
// 1. 取得 canvas 畫布
var c = document.getElementById('myCanvas');
// 2. 畫布繪圖,後面開始繪圖才會使用到這個變數
var ctx = c.getContext("2d");

// 3. canvas 畫布設定
var ww = $(window).outerWidth();
var hh = $(window).outerHeight();
// 3. 將螢幕寬高指定給畫布
c.width = ww;
c.height = hh;

4. 更新畫布大小

這時候若是調整螢幕大小,也就是網頁的寬高改變,會發現畫布沒有跟著撐滿版,所以我們必須監聽畫面的 resize,當畫面縮放的時候,動態地抓取當下畫面的寬高並重新設定 canvas 畫布。所以我們將程式碼調整成:

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");

var ww = $(window).outerWidth();
var wh = $(window).outerHeight();

// 將重複使用的程式碼整理成 function
function getWindowSize() {
  ww = $(window).outerWidth();
  wh = $(window).outerHeight();
  c.width = ww;
  c.height = wh;
}

// 進入網頁後先做一次畫布大小調整
getWindowSize();
// 監聽畫面 resize
$(window).resize(getWindowSize);

5. 開始畫圖 – 方塊

要注意在canvas 中畫圖比較特別的是,我們要定義兩點 a 和 b 才能連成線;若是要畫出一個方塊,可以直接透過 canvas 內的 api 拉出方塊後填色,讓我們利用以下幾個語法,畫出位於 (0, 0) 寬度 50px 的正方形:

  • ctx.fillStyle = “white” 填色顏色為白色
  • ctx.beginPath() 開始繪圖
  • ctx.rect(x, y, width, height) 在座標 (x, y) 上畫一個寬 width 高 height 的矩形
  • ctx.fill() 填色
function draw(){
  ctx.fillStyle = "white";
  ctx.beginPath();
  ctx.rect(0, 0, 50, 50);
  ctx.fill();
}

draw();

6. 隨時更新畫布

我們想讓正方形隨著時間往右移動,所以多加了一個時間變數 time,讓 draw function 被呼叫的時候增加 time 的值,並往右移動,所以我們將原本座標 x 跟著時間改變,就產生出一個白色方塊一直向右移動。

var time = 0;
function draw(){
  ctx.fillStyle = "white";
  ctx.beginPath();
  ctx.rect(time, 0, 50, 50);
  ctx.fill();
  time+=1;
}

setInterval(draw, 10);

7. 清空畫布再更新

這時候我們可以發現,方塊是往右了,但是卻變成一條白色的線,那是因為每次畫上新的方塊時,畫布沒有清空。所以我們在每次要畫方塊前,都先畫一個覆蓋整個畫面的長方形,將原本的畫面蓋掉後,再更新正方形。 若是覆蓋的長方形是半透明的會發生什麼事?沒錯!正方形移動時就會產生殘影,大家可以嘗試不同數值試試看。

...
var time = 0;
function draw(){
	ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
  ctx.beginPath();
  ctx.rect(0, 0, ww , wh);
  ctx.fill();

  ctx.fillStyle = "white";
  ctx.beginPath();
  ctx.rect(time, 0, 50, 50);
  ctx.fill();
	time+=1;
}

setInterval(draw, 10);

座標軸基礎設置

透過以上操作,讓大家了解繪圖的基本操作並讓方塊順利移動後,接著準備進入正題。首先我們要先繪製座標軸,但以網頁來說,左上角為坐標(0,0),而這次專案中我們希望座標軸的中心點(0, 0) 改在正中央,所以我們需要將中心點開始移動。

  1. 使用中心點作為初始值

要達成這個目標,我們直覺地想到把方塊移到中心,所以我們多了一組變數來記錄整個螢幕的中心點,並將這個中心點加到白色方塊的初始位置上。這邊還要注意的是,當螢幕 resize 時,center 值也必須更換,總共有以下三個部分的程式碼要調整。

// 多一組變數記錄中心點
var center = {
  x: 0,
  y: 0
};

function getWindowSize() {
  ww = $(window).outerWidth();
  wh = $(window).outerHeight();
  c.width = ww;
  c.height = wh;
  // 重新計算中心點
  center.x = ww/2;
  center.y = wh/2;
}

function draw(){
  ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
  ctx.beginPath();
  ctx.rect(0, 0, ww, wh);
  ctx.fill();
  
  ctx.fillStyle = "white";
  ctx.beginPath();
  // 每次繪製方形的座標時要加上 center 值
  ctx.rect(time + center.x, 0 + center.y, 50, 50);
  ctx.fill();
  time+=1;
}

2. 更改畫布中心

如果我們每次要繪製任何東西時,都要手動加上這個 center 的值,非常地不方便,甚至還有可能遺漏它。所以這時候我們可以使用 canvas 內建的功能,直接改變畫布位置,就不用每次繪製時都要在該元件多加上偏移的座標。這邊我們將 getWindowSize 調整成:

function getWindowSize() {
  ww = $(window).outerWidth();
  wh = $(window).outerHeight();
  c.width = ww;
  c.height = wh;
  
  center.x = ww/2;
  center.y = wh/2;
  
  ctx.restore();
  // 移動畫布座標
  ctx.translate(center.x, center.y);
}

3. 調整 draw()

完成更改畫布中心之後會發現,方塊沒出現在畫面裡,因為我們除了調整中心的畫布之外,也要將方塊的初始值調整回來。大家可以試著調整 draw 中清畫布的顏色,會發現這塊清除的色塊並沒有覆蓋整個版面,也是從中心點往右下長一個方塊,所以這塊清除的畫布的位置也需要調整。

function draw(){
  // 清畫布方塊顏色
  ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
  ctx.beginPath();
  // 調整清除畫布的初始位置
  ctx.rect(-center.x, -center.y, ww , wh);
  ctx.fill();
  
  ctx.fillStyle = "white";
  ctx.beginPath();
  // 白色方塊初始值
  ctx.rect(time, 0, 50, 50);
  ctx.fill();
  time+=1;
}

座標軸

透過白色方塊了解 canvas 繪圖和畫布座標移動後,總算要進入正題。

我們首先先繪製靜態的畫面,第一個就是我們的座標軸,比較不一樣的是,剛剛都是直接畫一個形狀填色,這次要改成用點連出我們需要的線並填色。會用到的程式碼如下:

  • ctx.stokeStyle = ‘rgba(255, 255, 255, 0.05)’ 畫筆顏色
  • ctx.strokeWidth = 1 畫筆粗度
  • ctx.moveTo(x, y) 將畫筆移到 (x, y)
  • ctx.lineTo(x2, y2) 從上一個位置拉一條直線到 (x2, y2)
  • ctx.stroke() 畫圖
function draw(){
  ...
  
  // 座標軸
  ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
  ctx.lineWidth = 1;
  ctx.beginPath();
  ctx.moveTo(-ww/2, 0);
  ctx.lineTo(ww/2, 0);
  ctx.moveTo(0, -wh/2);
  ctx.lineTo(0, wh/2);
  ctx.stroke();
  
  ...
}

從方形到圓形

有了座標軸後,我們要在上面畫一個圓形,這邊老闆一樣帶大家從四角形開始理解,了解四角形後,只要畫的點增加到一定數量,繪製出來的多邊形就會趨近於圓形。

我們嘗試畫個半徑為 200 的四邊形,首先第一個點為右邊,要注意的是,網頁的座標跟大家認知上的座標 y 軸方向是相反的,往下是正數,所以第二點是下面,第三個點是左邊,逆時針旋轉,以此類推。

另外要注意的是,網頁中畫圓是用弧度來計算,一圈 360° 等於 2PI,所以我們設一個變數來把角度轉換成弧度,若是沒有用弧度繪圖,就會出現右邊圖裡的窘境。這邊大家也可以試試看,當 n 增加到一定的量,畫出的圖就會趨近於圓形。

function draw(){
  ...
  // 每個點與中心的距離
  var r = 200;
  // 角度轉換成弧度
  var deg_to_pi = Math.PI * 2 / 360 ;
  // 畫 4 個點
  var n = 4;
  ctx.beginPath();
  for(var i = 0; i<=n; i++){
    var deg = i * (360 / n) * deg_to_pi;
    ctx.lineTo(
      r * Math.cos(deg),
      r * Math.sin(deg)
    );
  }
  ctx.stroke();
  ...
}

波浪線

那要怎麼畫旋轉的波浪圓形呢?我們可以觀察到,這個波浪圓形上的每一個點,只是在做半徑的調整,所以我們可以在 r 上面做些手腳,讓它成為一個動態的半徑,就能完成波浪的圓形,至於要如何調整才能讓這個點是在一定的範圍內變動,大家是不是想到 sin 和 cos 的圖了呢?沒錯,只要利用它們和 r 做組合,就可以調整出不一樣的波浪圖。老闆這邊提示大家:

  1. 因為 sin 和 cos 的最大到最小值分別是 1, -1,所以可以多乘上一個數,來增加波浪起伏
  2. 角度變化的越快就會讓波讓越密集,所以在代入 sin 的角度中,也多乘上一個數
  3. 波浪沒有完整的接合起來,是因為沒有把角度完整走完一個圓,一個完整的圓角度是 2PI,只要再多乘上 2PI 運算即可。

大家可以嘗試看看不同數值乘線的效果,那又該怎麼讓波浪圓形動起來呢?這邊我們要在 now_r 變數中的角度部份加入會一直變動的數值,才有辦法讓半徑多加上這個 1 單位到 -1 單位的動態半徑,我們可以觀察到, time 會隨著時間一值增加,只要活用 time 就能讓波浪圓形動起來。

function draw () {
  ...
  // 波浪圓形
  var deg_to_pi = Math.PI / 360 * 2;
  // 基礎半徑值 200
  var r = 200;
  // 200 個點的多邊形
  var n = 200;
  ctx.beginPath();
  for(var i = 0; i<=n; i++){
    // 2 * sin() 增加動態半徑的 range
    // 2 * Math.PI 確保畫出完整的圓
    var now_r = r + 2 * Math.sin(2 * Math.PI *  i / 10 + time / 20);
    var deg = i * (360 / n) * deg_to_pi;
    ctx.lineTo(
      now_r * Math.cos(deg),
      now_r * Math.sin(deg)
    );
  }
  ctx.stroke();
  ...
}

刻度

畫刻度的想法又跟畫圓形不太一樣,兩者都需要使用到角度來輔助畫圖,波浪是每畫一點就改變角度,而畫刻度不一樣的地方在於,是固定角度在 r 畫上一點後,改變 r 的長度後再畫一點連起來,完成一個角度後再到下一個角度用相同方式繪製線。

以下圖為例子,假設我們想畫一個角度 θ ,從半徑 3 連到半徑 5 的線,則先將畫筆移到(3cosθ, 3sinθ),再連線到 ((3+2)*cosθ, (3+2)*sinθ)。用基本的 r 加上某個數值,成為下一個要連線的點,這樣做的好處是,如果今天我們想變成比較長的刻度,只要調整加上的值就可以達成。

我們要畫出的圖需求如下:

  1. 畫一圈半徑為 220 共 240 個刻度所繞出的圓。
  2. 每隔10個刻度,就會有一條中間長度的刻度
  3. 上中下右(也就是每隔 240/4 = 60)有一條最長長度的刻度,

總共有三種樣式的刻度,透過三元運算子,可以先將其中一段拆解出來看,活用三元運算子就能用刻度來組出不同的長度,以下面這個三元運算子解讀的方式如下,當判斷式為 true 時,會執行前者 true 的內容,否則就執行後者 false。

判斷式 ? true : false

所以「當 i 除以 10 的餘數為 0 的時候則會使用 4,否則就使用 0」的三元運算子應該這樣寫:

i % 10 == 0 ? 4 : 0

同樣的,我們也可以將透明度結合三元運算子,在畫線前,賦予畫筆顏色不同透明度。接下來就可以設定兩個點(start_r 到 end_r)與角度,記得角度要乘上前面宣告的 deg_to_pi。

萬事俱備,就能開始繪圖了,一樣使用前面提到的 beginPath(), moveTo(), lineTo(), stroke() 來進行繪製。

var r = 220;
var count = 240;
for(var i=0; i<=count; i++){
  // len 為不同刻度有不同長度的增加量
  var len = 4 + (i % 10 == 0 ? 4 : 0) + (i % 60 == 0? 8 : 0);
  var opacity = (len > 4 ? 1 : 0.5);
  var start_r = r;
  var end_r = start_r + len;
  //最基本的角度分佈
  var deg = 360*(i/count)*deg_to_pi;
  ctx.beginPath();
  ctx.moveTo(
    start_r * Math.cos(deg),
    start_r * Math.sin(deg)
  );
  ctx.lineTo(
    end_r * Math.cos(deg),
    end_r * Math.sin(deg)
  );

  // 決定畫筆樣式
  ctx.lineWidth = 1;
  ctx.strokeStyle = "rgba(255, 255, 255, "+opacity+")";

  ctx.stroke();
}

外圈

外圈的作法很簡單,與剛剛畫內圈的方式雷同,調整 r 以及減少點(count)的數量就可以達成,大家可以按照喜好嘗試調整看看。

function draw () {
  ...
  var r = 400;
  // 共畫 60 個刻度
  var count = 60;
  for (var i = 0; i <= count; i++) {
    // 上中下右刻度要不一樣,等於是每十五個刻度就要不同長
    var len = 4 + (i % 15 == 0 ? 8 : 0);
    var opacity = len > 4 ? 1 : 0.5;
    var start_r = r;
    var end_r = start_r + len;
    var deg = 360 * (i / count) * deg_to_pi;
    ctx.beginPath();
    ctx.moveTo(start_r * Math.cos(deg), start_r * Math.sin(deg));
    ctx.lineTo(end_r * Math.cos(deg), end_r * Math.sin(deg));

    ctx.lineWidth = 1;
    ctx.strokeStyle = "rgba(255, 255, 255, " + opacity + ")";
    ctx.stroke();
  }
  ...
}

秒針、分針、時針

要畫秒針、分真或時針,我們需要知道目前時間,獲得目前時間的方式會用到以下程式,我們也利用 jQuery,將時間放在畫面上,方便我們在製作時能夠參照畫面是否和我們期望的相同。

HTML

canvas#myCanvas
.time +00:00:00

CSS(Sass)

canvas
  transform: scaleY(-1)

JavaScript

var nowTime = new Date();
var sec = nowTime.getSeconds();
var min = nowTime.getMinutes();
var hour = nowTime.getHours();
  
$('.time').text('+00:'+hour+':'+min+':'+sec);

先用固定的角度來測試畫出來的圖形是否跟真實的時鐘一樣,我們傳入 45 作為角度參數後發現一些問題,跟著下面的步驟去微調就能解決:

  1. 將角度多乘上畫圓的角度:畫出來的角度不是 45 度。只要跟前面一樣,將角度轉換成畫圓要用的角度即可。
  2. 從正上方旋轉 45 度:給 45 度為什麼是指向右下呢?我們期望會從正上方作為0度開始旋轉,所以 45 度應該要指著右上方,大家還記得在上一篇有提到,網頁中的座標系 y 軸向下才是正值(左圖),老闆這邊使用偷吃步的方式,只要修改畫布將整張畫布垂直翻轉,45度指的方向就對了。
  3. 讓秒針跟著時間旋轉:緊接著,將角度位置改使用 sec 變數讓秒針動起來。對於秒針而言一圈有 60 個刻度,我們先將 0 到 60 個刻度換算成 0 到 1,再換算 0 到 360,所以就等於 sec / 60 * 360,就是秒針每秒要改變的角度。
  4. 調整旋轉方向:我們可以看到現在以逆時針旋轉,所以我們多乘以一個負值,讓旋轉的方向變成順時針轉的方向。
  5. 補上初始值:最後我們察覺到正確的秒針位置總是少了90度,這就是因為我們現在的座標系初始值 0 度是在右邊,只要將初始值的0換到正上方,這邊我們在函式轉換的角度位置中加上初始值(90度)即可。
function drawPointer(r, deg, lineWidth) {
  // 決定畫筆
  ctx.lineWidth = lineWidth;
  ctx.strokeStyle = "rgba(255, 255, 255, .5)";
	
  // 調整角度初始值與轉化成圓的角度
  var now_deg = (deg + 90) * deg_to_pi;
    
  ctx.beginPath();
  ctx.moveTo(0,0);
  ctx.lineTo(r * Math.cos(now_deg), r * Math.sin(now_deg));

  ctx.stroke();
}

// 秒針分針時針畫法
drawPointer(400, -360 * (sec / 60), 1);
drawPointer(210, -360 * (min / 60), 1);
drawPointer(150, -360 * ((hour + min / 60) / 12 ), 4);

要注意的是,時針畫法比較不一樣,我們會希望時針不是單純指在對應的數字上,還要多加上過了幾分鐘的變化,所以先將過了幾分鐘除以 60,獲得目前經過一小時中的幾分鐘後,再加上小時除以12,因為一圈只有12個刻度,就能獲得目前的度數。

結語

老闆這邊附上這次範例的成果網址,讓大家在實作時可以參考。

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

  1. canvas 網頁繪圖:若是要讓畫布和螢幕同樣大小,要監聽 resize 並重新調整畫布大小。
  2. canvas 動態效果:相當於在畫布上一直重新繪圖,要記得清空畫布後再畫上新東西。
  3. 調整 canvas 座標:canvas 預設左上角為 (0, 0),可以利用 ctx.translate(x, y)調整畫布的位置。
  4. 繪製座標軸。
  5. 從四角形到圓形:利用三角函數來畫出不同形狀,當點的數量足夠時,連起來就會像是圓形,要注意要將角度換算成弧度。
  6. 會動的波浪圓形:在畫圓的時候,透過三角函數來製造波浪,並結合時間就能讓波浪圓動起來。
  7. 繪製刻度:這次範例中的內圈與外圈都是用這種方是去完成,在不同角度上點出兩個點連起來就變成刻度,結合三元運算子就能在不同角度時有不同的樣式。
  8. 繪製秒針、分針與時針:類似刻度的畫法,只是這次第一個點是圓心,再將時分秒換算成角度後畫出第二點並連線。

數學裡面有許多奇怪的東西,但也感謝數學家們的努力,讓我們可以應用在遊戲或動態特效等地方。在第一篇我們用三角函數來幫 DOM 做定位,這一篇則是在 canvas 上繪圖,並適時結合三角函數,產出許多有趣的效果,各位同學挑戰完兩篇三角函數教學之後,可以回頭思考看看兩者的差異,期待大家能利用三角函數做出更多有趣的效果。

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

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

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 來用可怕的三角函數做網頁吧! -Part 2科幻時鐘(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

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

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

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

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

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

前言

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

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

Dyverse Studio主頁動畫

從遊戲開始出發

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

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

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

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

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

適用於目標位置不改變。

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

x+=Δx
y+=Δy

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

適用於目標位置不改變。

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

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

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

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

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

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

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

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

比例會根據θ而改變

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

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

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

衛星繞月球

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

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

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

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

場景 html 結構

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

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

Sass 重複使用 – @mixin

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

$color_space: ##2c3d4f

*
  border: solid 1px

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

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

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

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

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

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

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

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

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

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

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

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

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

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

製作陰影

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

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

月亮不同坑洞

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

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

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

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

衛星軌道

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

HTML

.trace1
.trace2

CSS

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

旋轉的衛星

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

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

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

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

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

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

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

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

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

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

https://imgur.com/p649GNo.gif

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

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

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

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

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

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

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

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

結語

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

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

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

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

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

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

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

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

]]>
讓我們來快速寫個漂浮感的輪播吧!動態網頁程式教學(直播筆記) 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 - 互動程式創作台灣站

]]>
圖形繪製與色彩:從點線面開始的世界(互動藝術創作分享教學) https://creativecoding.in/2021/04/21/%e5%9c%96%e5%bd%a2%e7%b9%aa%e8%a3%bd%e8%88%87%e8%89%b2%e5%bd%a9%ef%bc%9a%e5%be%9e%e9%bb%9e%e7%b7%9a%e9%9d%a2%e9%96%8b%e5%a7%8b%e7%9a%84%e4%b8%96%e7%95%8c%ef%bc%88%e5%89%b5%e4%bd%9c%e5%88%86%e4%ba%ab/ Wed, 21 Apr 2021 01:45:00 +0000 https://creativecoding.in/?p=644 如果你跟我(Ju編)一樣是Creative Coding這個領域的菜逼八,可能本身從網頁設計起家、或是從視覺設計半路轉行、甚至只是不小心點到這個網站,對概念還有些模糊但有興趣想多留下來逛一下,那從這篇…

這篇文章 圖形繪製與色彩:從點線面開始的世界(互動藝術創作分享教學) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
如果你跟我(Ju編)一樣是Creative Coding這個領域的菜逼八,可能本身從網頁設計起家、或是從視覺設計半路轉行、甚至只是不小心點到這個網站,對概念還有些模糊但有興趣想多留下來逛一下,那從這篇文章開始或許是個不錯的選擇。

Creative Coding是什麼?

在程式設計這片汪洋大海中,Creative Coding對台灣來說算是相對較新的概念,網站內右上角的關於入門資源提供了一些輪廓,這邊幫大家節錄一些重點:

Creative Coding一種透過程式創作來表達的藝術形式,表現方式包含但不限於視覺、聲音、投影與裝置藝術等…。
顧名思義是結合程式加創意,狹義上看起來是用程式寫一些看起來很有藝術感的圖形,廣義上是可以用程式,將世界上的各種事物賦予互動跟連結起來,創造出新的現象、關聯與有趣的呈現。

由此可見這個領域且深且廣,舉凡美術館展品到路邊的互動式廣告,可以說是包山包海。當然要馬上變成像那些藝術家大大們一樣厲害可能不是那麼容易,但經過不斷地練習與累積,相信有一天我們可以做出自己也覺得很滿意的作品!

既然是從點線面開始的Creative Coding世界,就讓我們時光回溯一下,回到剛開始學畫畫的小時候,懞懂地拿著蠟筆畫著圓形、長方形、三角形,再以這些基礎圖形為出發點,組成各式各樣的圖像作品。創作的題材選擇上可以先從身邊的事物開始,例如:高樓大廈、喜歡的角色、自然景色等,不一定要馬上挑戰構造複雜的圖面。

在本篇文章中選擇的主題是天外奇蹟(UP),藉著最近剛重溫這部電影替他做個極簡主義的電影海報redesign,另外再添加一些滑鼠小互動,那就讓我們開始動手吧!

圖片來源:Wikipedia

今天要使用的是OpenProcessing搭配p5.js函式庫的大禮包組合,如果對這兩個工具還不太熟悉在這篇文章可以看到更多介紹 👉🏻 p5.js 快速上手

首先,在開始設計前可以先打個草稿,既然是電影海報又要是極簡主義(minimalism),那放置在海報上的元素就必須簡單且具有代表性,選用的當然就是貫穿整部電影的繽紛氣球啦 🎈。

在OpenProcessing裡面建立新的專案,可以看到系統預設的專案內容。(如果想要調整成code跟畫布的分割版面,可以在右邊Editor裡的Layout找到)

互動海報製作的基本設定

由於目標是做出直向的海報,所以將setup()中的createCanvas()寬高設定成寬600、高700。接下來我們需要一片台北冬天很少見的藍色天空,在draw()中的background()設定天空藍的色票。在p5.js中顏色的設定可以分為以下五種:

  1. RGB – rgba(88, 178, 220, 1) 第四個值為設定透明度,1為最深0為最淺。
  2. HSB – hsba(160, 100, 50, 0.5)
  3. HSL – hsla(160, 100, 50, 0.5)
  4. 灰階 – color(65)
  5. HEX – #58B2DC

注意因為這些色票或數值在p5.js中不是一個function,所以記得要在顏色外加上單引號,可以搭配fill()、和background()使用,簡單來說就是background(‘你想要的顏色’)。

利用幾何圖形組合出背景雲朵及主角氣球

接著來畫背景的雲朵,我們把大塊的雲朵用基礎圖形切割分析,可以發現它是由五個圓形+一個長方形組成的。這邊要介紹幾個p5.js畫基礎圖型的語法:

  1. ellipse() – 橢圓形,ellipse( 畫布座標x, 畫布座標y, 橢圓的寬, 橢圓的高), 有時會加上第五個值detail,表示線條之間的間距。
  1. circle() – 圓形,circle( 畫布座標x, 畫布座標y, 圓的半徑)。
  2. rect() – 方形,rect(畫布座標x, 畫布座標y, 方形的寬, 方形的高)。

注意圓形和方形在座標定位點上的不同,圓形預設是用圓心、方形預設是用左上第一個角為定位點。可以用rectMode()ellipdeMode()來解決起定位點不一樣的問題。

在畫圖的時候我們可以把當前滑鼠所在的位置顯示在畫布上來幫助我們定位。使用text()裡面填入mouseX和mouseY,把它定位在畫布x距離20、y距離30的左上角,因為只需要大概的位置所以把數值用int()轉換成整數比較好讀,記得分隔用的逗號要加上雙引號。

再加上大大小小、顏色不一樣的氣球,添加一些透明度,模仿氣球的微透明感。

接下來把一顆顆的氣球綁成一束,用line()來畫出線條,用法為line(x1, y1, x2, y2),告訴p5.js兩個定位讓他幫忙連連看。注意程式撰寫後蓋前的特性,前面畫雲和氣球時我們用noStorke()取消了外框線,記得再用storke()把畫筆線條叫回來,粗細可以用strokeWeight()調整。

加上電影的標題和電影台詞,利用textAlign() 的CENTER置中對齊主標題和副標題,一張簡單的海報就完成啦。

再加一點點互動效果

如果覺得靜態的海報有點單調,想要加入滑鼠往下,氣球變大往上飛、雲層往下的幫浦打氣互動效果,可以在參數的地方加入mouseY來讓元素跟著滑鼠做動態的變化。適當加入一些互動效果可以更抓住使用者的目光唷!這邊提供code給大家參考~

// cloud
  fill(255);
  noStroke();
  circle(30, 510+mouseY/20, 200);
  rect(30, 700+mouseY/20, 600, 80);
  circle(120, 600+mouseY/20, 250);
  circle(250, 600+mouseY/20, 100);
  circle(380, 640+mouseY/20, 250);
  circle(556, 680+mouseY/20, 150);

// balloonYellow
fill('rgba(249, 191, 69, 0.95)');
circle(400, 150, 220+mouseY/10);
// balloonGreen1
fill('rgba(27, 129, 62, 0.95)');
circle(180, 240, 110+mouseY/10);
// balloonRed
fill('rgba(203, 64, 66, 0.95)');
circle(270, 210, 180+mouseY/10);
// balloonGreen2
fill('rgba(27, 129, 62, 0.95)');
circle(350, 270, 110+mouseY/10);

// lineGreen1
stroke('Black');
strokeWeight(1.5);
line(208, 287+mouseY/18, 300, 400);
// lineRed
line(281, 300+mouseY/18, 300, 400);
// lineGreen2
line(327, 320+mouseY/18, 300, 400);
// lineYellow
line(345, 325+mouseY/18, 300, 400);
// lineBottom
strokeWeight(1.5);
line(300, 400, 300, 500);

我們就完成了一個天外奇蹟的互動式海報redesign啦~希望大家喜歡這個簡單的小教學,可以試著用p5.js來畫畫看身邊的靜物、喜歡的角色,然後加上有趣的互動效果。我們下次再見啦 🙌🏻

成品請往這邊走 👉🏻 https://openprocessing.org/sketch/1065341

https://imgur.com/Yy3fZoL.gif

語法統整大補帖

圖形相關語法:ellipse()、circle()、rect()
填色相關語法:fill()、background()
線條相關語法:line()、stroke()、noStroke()、strokeWeight()
文字相關語法:text()、textAlign()

下一篇讓我們利用變數概念與程式完成簡單的彩色互動畫作。

想學習更多程式創作嗎?那你必不可以錯過哲宇老師的【互動藝術程式創作入門】課程,快跟著其他一千多名同學開始發揮你無限的創意吧!

作者:Ju編 Jeudi Kuo

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 圖形繪製與色彩:從點線面開始的世界(互動藝術創作分享教學) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js創作教學】電波電路Wave Circuit – 來做個逼哩逼哩送訊號的電路吧!(直播筆記) https://creativecoding.in/2021/04/16/p5-js%e6%95%99%e5%ad%b8%ef%bc%8d%e9%9b%bb%e6%b3%a2%e9%9b%bb%e8%b7%afwave-circuit-%e4%be%86%e5%81%9a%e5%80%8b%e9%80%bc%e5%93%a9%e9%80%bc%e5%93%a9%e9%80%81%e8%a8%8a%e8%99%9f%e7%9a%84%e9%9b%bb%e8%b7%af/ Fri, 16 Apr 2021 02:33:00 +0000 https://creativecoding.in/?p=502 互動藝術程式教學簡介 這次的直播要來製作具有逼哩逼哩效果的電路,當中可以分為三部分內容,分別為背景的格線、連接的線條以及細節上的裝飾,在創作過程中會各自去調整,有可能調整了線條後,再去修改背景,而後又…

這篇文章 【p5.js創作教學】電波電路Wave Circuit – 來做個逼哩逼哩送訊號的電路吧!(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
互動藝術程式教學簡介

這次的直播要來製作具有逼哩逼哩效果的電路,當中可以分為三部分內容,分別為背景的格線連接的線條以及細節上的裝飾,在創作過程中會各自去調整,有可能調整了線條後,再去修改背景,而後又再回去修改線條,透過來來回回的在細節上修正來完成最終的作品。

https://imgur.com/qkrP9yb.gifhttps://imgur.com/Ydt29fT.gif

主要會用到的 API :

還是新手?想要快速上手p5.js請來看p5.js 快速上手:互動網頁教學

1. 建立網格點點

一開始我們這邊先用雙層的 for 迴圈來建立垂直與水平間隔上都是 50 的格線

let ww, hh
function setup() {
  createCanvas(800, 800);
  background(100);
	
  ww = int(width/50)
  hh = int(height/50)
	
}

function draw() {
  for(let i=0; i<ww; i++){
    for(let o=0; o<hh; o++){
      push()
        translate(i*50,o*50)
        rect(0,0,10,10)
      pop()
    }
  }
}
建立網格點點

2. 隨機連彩色的線

  • colors 設定: 在顏色上,雖然可以直接使用指定內建的顏色來使用,但這邊我們使用外部的選色工具來協助。首先先連到 coolors 這個配色的網站,當開始使用的時候,可以發現上面的網址列會呈現像是https://coolors.co/ed6a5a-f4f1bb-9bc1bc-5ca4a9-e6ebe0 ,仔細發現這串網址後面的字串就是顏色的代碼,這時只要將後面的顏色字串複製下來,接著經過字串的處理後就可以變成讓使用的顏色了。
  • 標點建立與連線,分為二個部分
    • 首先是先 Array.from 來建立 50 個隨機的位置點的陣列,如果我們將它列印出來的話,會產生出像是 [start: {x: 13, y:8}, end {x: 3, y:5}] 的陣列
    • 接下來用 forEach 來將 links 這個陣列的每一個元素來去提取出來。不過這裡有一個小問題,就是現在 links 所存的數字是表示方格上的第幾個點,而非實際的位子,所以這邊要使用 getPos() 來將位子的點轉換成實際的位子。舉個例子來說,像是原本點是 (3,5),而這個點在畫布上的實際位子是 (150, 250)。在位子轉換後,就使用 line(st.x, st.y,ed.x, ed.y) 將線條連接起來。

這邊可以建議大家稍微停下來思考一下透過 Array.from() 來建立陣列的細節以及 links.forEach() 提取陣列來做操作的架構,因為在後續我們會增加一些屬性,像是波的波型,亦或是波的振幅都是一樣透過在 Array.from() 中進行屬性的設定,接著在 links.forEach() 中進行實際的繪製。對了,這裡除了畫線條之外,也在後面加上一個黑色的方塊,來讓我們很像電路板的作品跟最後面的背景來做區別

let links = []
let ww, hh
let colors = "fff-437f97-849324-ffb30f-fd151b".split("-").map(a=>"#"+a)
function getPos(gridIndex){
  return createVector(gridIndex.x*50,gridIndex.y*50)
}

function setup() {
  createCanvas(800, 800);
  background(100);
	
  ww = int(width/50)
  hh = int(height/50)
	
  links = Array.from({length: 50}, (d,i) => ({
    start: createVector(int(random(ww)), int(random(hh))),
    end: createVector(int(random(ww)), int(random(hh))),
    color: random(colors)
  }))
	
  print(links)
}

function draw() {
  //黑底
  fill(0)
  rectMode(CORNER)
  rect(0,0,width,height)
  stroke(255)
  strokeWeight(3)
  rectMode(CENTER)
	
  // 網格
  for(let i=0; i<ww; i++){
    for(let o=0; o<hh; o++){
      push()
        translate(i*50,o*50)
        rect(0,0,10,10)
      pop()
    }
  }
	
  stroke(255)
  strokeWeight(5)
	
  //線條
  links.forEach(link=>{
    stroke(link.color)
    let st = getPos(link.start)
    let ed = getPos(link.end)
    line(st.x, st.y,
      ed.x, ed.y)
  })
  noStroke()
}

3. 將線條加上光暈/特定點放大

目前線條看上去有點太硬了,感覺死板板的,所以這裡透過加上模糊的效果來讓整體上看起來柔和一些,使用 drawingContext 來去設定線條模糊的顏色 (當然是跟實體直線的線條顏色一樣) 以及模糊的程度。設定完後就把它加在繪製線條的位子之前

除了調整線條外,我們也調整一下背後的點點。如果點點在垂直以及水平的方向都是五的倍數的話,就將那個點設定的更加明顯一些,這樣子背景看上去就稍微比較有韻律感了。

// 網格
for(let i=0; i<ww; i++){
  for(let o=0; o<hh; o++){
    push()
      translate(i*50,o*50)
      // 這裡透過去看看是不是五的倍數後,也去更動了 rect 的參數數值
      let ww = (i%5==0 && o%5==0) ? 10 : 3
      rect(0,0,ww)
    pop()
  }
}

stroke(255)
strokeWeight(5)

//線條
links.forEach(link=>{
  // 使線條有光暈
  drawingContext.shadowColor = color(link.color);
  drawingContext.shadowBlur = 30;
	
  stroke(link.color)
  let st = getPos(link.start)
  let ed = getPos(link.end)
  line(st.x, st.y,
    ed.x, ed.y)
})

4. 移動的 Siri 波型

這裡要持續的去改變線條,將原本的直線變成波型,同時讓它動起來,看起來更活潑一些。

在波的繪製上,首先要先找出兩個重要的數字來協助我們,一個是距離,另一個則是角度。在距離上是使用 dist() 來取得,而角度稍微比較複雜一些些,必須要先將終點減去起點得到向量後,再用 heading() 來取得角度。這裡有個要注意的是,heading() 它的單位是 radians 弧度,並非一般我們認知的 360度 / 90 度的單位。

有了距離以及角度後,就可以開始畫波浪囉。一開始先將畫筆移動到起始點的位子,並根據所得出的角度旋轉面向終點的位子,接著就開始透過 for 迴圈一小段一小段來畫出現線條了。這裡有個地方要注意的是,由於每次畫線條的時候,都會透過 translate(st.x, st.y) 去移動畫筆,而間接影響到畫布的設定,所以這邊要記得用 push() / pop () 給包起來。

let st = getPos(link.start)
let ed = getPos(link.end)

// 透過一條條短短的區段去畫線

// rr 計算起點與終點的位子 
let rr = st.dist(ed)
// 計算夾角
let ang = ed.copy().sub(st).heading()

// 因為每一次都必須要 translate 到畫線的起點,所以這裡我們要用 Push()/Pop() 包起來
push()
  translate(st.x, st.y)
  rotate(ang)
  beginShape()
  for(var i=0; i<rr; i+=2){
    vertex(i, sin(i/5)*5)
  }
  endShape()
pop()

接著要使靜態的東西動起來,最常使用的小技巧就是 – 加入 frameCount ,因為它是系統上會隨著改變的系統變數,但是全部都只加上 frameCount 的話,這樣就會全部都線條一起同個頻率一起扭動,這樣的畫面有點不太自然,所以加上了 freq 屬性,並且放到 vertex() ,這樣線條就會隨著自己的頻率各自搖擺囉。

// 新增屬性 freq
links = Array.from({length: 50}, (d,i) => ({
  start: createVector(int(random(ww)), int(random(hh))),
  end: createVector(int(random(ww)), int(random(hh))),
  freq: random(1,50),
  color: random(colors)
}))


// 加上 frameCount 與 link.freq 來畫線條,產生動態
push()
  translate(st.x, st.y)
  rotate(ang)
  beginShape()
  for(var i=0; i<rr; i+=2){
    vertex(i, sin((i+frameCount)/link.freq)*5)
  }
  endShape()
pop()

再來就是要浪波看起來就像是 Siri 般,兩邊頭尾的地方相對於中間的地方振福較小,所以增加了變數 ratio 後,再相乘到 vertex 的第二個參數中。一開始你看到可能會想,這個變數看上去也太複雜了吧,不過實際上老闆在製作過程中也是嘗試了非常多不同的組合跟方式才慢慢試出來的。大家也可以玩玩看不同的效果,找到你最喜歡的樣貌。

for(var i=0; i<rr; i+=2){
  // 新增 ratio 後,乘到  vertex,使線條的頭跟尾會的波會變比較小
  let ratio = 5*(rr/2-abs(i-rr/2))/rr
  vertex(i, sin((i+frameCount)/link.freq)*5*ratio)
 }
https://imgur.com/1QmW3pB.gif

5. 外觀調整

目前波的部分處理到一個段落了,接下來要來調整背景,以及替它加上材質。

雖然說先前已經有對後面的點點進行設定了,但是看上去似乎有點不太明顯,所以除了大小,在顏色上也要有所區別。這裡先透過 (i%5==0 && o%5==0) 判定同時垂直與水平都是5倍數的點才是 isGridPoint ,接著在下面設定如果是 isGridPoint 的話,顏色會比較深,反之其餘會比較淡。

// backgroung grid 
for(let i=0; i<ww; i++){
  for(let o=0; o<hh; o++){
    push()
      translate(i*50,o*50)
      // 跟 ww 的概念一樣,判定同時垂直與水平都是5倍數的點才是 isGridPoint
      let isGridPoint = (i%5==0 && o%5==0)
      // 這裡透過去看看是不是五的倍數後,變去更動了 rect 的參數數值
      let ww = (i%5==0 && o%5==0) ? 10 : 3
      // 是 isGridPoint 的話,顏色會比較深,反之其餘會比較淡
      stroke(isGridPoint?255:100)
      rect(0,0,ww)
    pop()
  }
}

在加材質上,可以分為三大區塊,分別為定義材質、設定材質以及使用材質,這三個都是設定在不同地方,這是要特別注意的地方。

// 定義材質,在全域的位子
let overAllTexture

// 設定材質,放在 setup 裡面
overAllTexture=createGraphics(width,height)
overAllTexture.loadPixels()
// noStroke()
for(var i=0;i<width+50;i++){
  for(var o=0;o<height+50;o++){
    overAllTexture.set(i,o,color(100,noise(i/3,o/3,i*o/50)*random([0,50,100])))
  }
}
overAllTexture.updatePixels()

// 使用材質,在 draw 裡面,而且通常是放在最尾巴的地方
// 這邊要注意的是要加上 push 以 pop
push()
  blendMode(MULTIPLY)
  image(overAllTexture,0,0)
pop()
https://imgur.com/93CmVWa.gif

6. 加入不同種類的波形

除了現在的 sin 波外,這裡再來多加上一個方波,這個算是波裡面的一個**屬性,**所以與前面提到的一樣,當想要再多加上一個功能需要屬性時,就加在 Array.from() 中。這裡多加上 type 屬性,並且設定兩種波型,分別是 sin 波以及方波。

設定好了之後,就是到下面 vertex(i, sin((i+frameCount)/link.freq)*5) 的位子來畫波,可以觀察在影響波的形狀是透過設定 y 的數值,現在由於要來畫不同形狀的波,所以這裡把 y 位子的地方抽出來,設定名為 yy變數,接著根據不同的波型去設定 yy 的數值,最後在尾端再將yy 帶入 vertex()中。

links = Array.from({length: 50}, (d,i) => {
  let start = createVector(int(random(ww)), int(random(hh)))
  let end: createVector(int(random(ww)), int(random(hh)))
  return {
    start, end,
    freq: random(1,50),
    color: random(colors),
    type: random(['sine','square'])
  }
})
beginShape()
for(var i=0; i<rr; i+=2){
  let ratio = 5*(rr/2-abs(i-rr/2))/rr
  // yy 抽出來變成變數
  let yy 
  if (link.type=="square"){
    yy=(i+frameCount + mouseY)%100<50?1:-1
  }else{
    yy = sin((i+frameCount + mouseX)/link.freq)
  }
  vertex(i, yy*5*ratio)
}
endShape()
https://imgur.com/jE3bCPn.gif

7. 加入起始/結束點

加入起點與終點的部分不難,要注意的是,這段是要加在 links.forEach()={} 之中。

🔔 在這裡有個蠻重要的地方要提醒大家,由於 canvas 在開發的時候其實蠻吃效能的,所以可以嘗試將效能先關起來,另外也可以將線條的數量暫時調小,從原本的 50 調整成 30,這樣電腦才不會使負荷那麼大。

push()
  strokeWeight(5)
  ellipse(st.x,st.y,20,20)
  ellipse(ed.x,ed.y,20,20)
pop()
https://imgur.com/2XvRtCW.gif

到目前為止,作品上架構都已經完成得差不多了,接下來就是一些細節上的調整,讓整體更加具有科技感

8. 格線與方格

這裡要替背景加上格線,形成九宮格,而這九宮格的線條跟 isGridPoint 一樣,是以五的倍數去做繪製的。而方格部分則是將其旋轉後,製作出一個向外有如呼吸般的方格。

  • 九宮格: 在畫九宮格上,可以細分為畫直線與橫線。這裡比較直觀的地方是畫橫線時是在第一層控制左右寬度的 for 迴圈,而畫橫線時,則是在第二層控制垂直的 for 迴圈中,但有個要注意的是,在第二層中不僅僅限制 o%5==0 ,還多加了 i==0 ,原因在於,如果不加上的話,直線會因為上一層的 for 迴圈關係,而重複畫了好幾次。
  • 方格: 在方格的三個設計 – 位子標示、旋轉以及呼吸燈的效果,製作上都不難,但這邊的前後位子順序相當重要,如果把旋轉擺放到最前面的話,這樣一來就會連同字也一個旋轉了,要避免這樣的狀況,除了可以用最簡單的方式就是把位子標示往前移,或是使用 push() / pop() 來保存與還原畫布的狀態。
// 網格
for(let i=0; i<ww; i++){
  // 畫上橫線
  if (i%5==0){
    push()
      strokeWeight(1)
      stroke(255,100)
      noFill()
      line(0,i*50,(ww-1)*50,i*50)
    pop()
  }
  for(let o=0; o<hh; o++){
    push()
      translate(i*50,o*50)
      let isGridPoint = (i%5==0 && o%5==0)
      let ww = (i%5==0 && o%5==0) ? 10 : 3
      stroke(isGridPoint?255:100)
	//加上位子標示
	if (isGridPoint){
	  push()
	  noStroke()
	  fill(255)
	  textStyle(BOLD)
	  textSize(15)
	  text("("+i+","+o+")",15,10)
	  pop()
	}
		
        // 是 isGridPoint 的話,旋轉
        if(isGridPoint) rotate(PI/4)
        rect(0,0,ww)

        // 是 isGridPoint 的話,向外如呼吸般擴散的方格
        if(isGridPoint){
          noFill()
          stroke(255,100)
          strokeWeight(1)
          rect(0,0,ww*(3+sin((frameCount)/10 + i + o)))
        }
    pop()

    // 畫上直線
    if (i==0 && o%5==0){
      push()
      strokeWeight(1)
      stroke(255,100)
      line(o*50,0,o*50,(hh-1)*50)
      pop()
    }
  }
}
https://imgur.com/142cgOw.gif

9. 加入裝飾用的文字

既然前面都加上格點位子,那再來替線條加上各自的標示,顯示著是甚麼類型的波形以及編號,雖然這不太起眼,人們不一定會去細看,但是當畫面資訊量很多,而且很整齊的時候,那個裝飾加分的效果就出來了。

與先前一樣,要加上屬性就到上面的 links = Array.from({}) 來做設定,由於文字上要做一點加工,所以 type 拉到上面去做宣告,接著在 label 上設定顯示線條的種類與編號。設定好後,就去下面地方寫 label 的樣式,這裡一樣要注意文字放的位子,這裡可以回頭看看第四章移動的 Siri 波型畫上線條的架構, label text 的位子要被放在移動到線條起始點的位子以及旋轉方向之間。

links = Array.from({length: 50}, (d,i) => {
  let start = createVector(int(random(ww)), int(random(hh)))
  let end: createVector(int(random(ww)), int(random(hh)))
  let type = random(['sine','square'])
  return {
    start, end,
    freq: random(1,50),
    color: random(colors),
    label: type + " #"+i,
  }
})
// label text 
push()
  rotate(PI/8)
  rect(20,-5,2,2)
  noStroke()
  fill(255,180)
  text(link.label,15,10)
pop()
// 因為每一次都必須要 translate 到畫線的起點,所以這裡我們要用 Push()/Pop() 包起來
push()
  translate(st.x, st.y)

 // label text 放的位子在這
  rotate(ang)
  beginShape()
  for(var i=0; i<rr; i+=2){
    vertex(i, sin(i/5)*5)
  }
  endShape()
pop()
https://imgur.com/wXY6xuj.gif

這是目前整體看上去的樣子,看上去稍微顯得有點雜亂,線條上比較偏向各自發展,所以接下來會針對波的長度與方向來修正。

10. 調整波的方向以及種類

在這裡有三個地方要調整,分別是波的行走方向、波形的種類以及改變波的振福

  • 波的行走方向 : 為了限制距離及方向,要先新增 randomDelta,接著結束點的位子取決於起點加上 randomDelta 的數值。
  • 波形的種類: 原本波的設定上是 random(['sine','square']) ,但老闆覺得多一點 sin 波比較好看,所以多增加了 sin 波的數量。
  • 波的振福: 新增波的屬性 amp,設定好後在下方乘上 yy 的數值,這裡要寫成 yy*=link.amp 或是 yy = yy * link.amp 都可以
links = Array.from({length: 50}, (d,i) => {
  let start = createVector(int(random(ww)), int(random(hh)))
  let randomDelta = random([-2,2,-5,5,-10,10])
  // 結束點的位子取決於起點加上 randomDelta 的數值
  let end = start.copy().add(createVector(random(randomDelta),random(randomDelta)))
  // 把 sine 與 square 的比例調整為 3比1
  let type = random(['sine','sine','sine','square'])
  return {
    start, end,
    freq: random(1,50),
    color: random(colors),
    label: type + " #"+i,
    // 新增振幅
    amp: random(1,3)*random(),
  }
})
beginShape()
for(var i=0; i<rr; i+=2){
  let ratio = 5*(rr/2-abs(i-rr/2))/rr
  let yy 
  if (link.type=="square"){
    yy=(i+frameCount + mouseY)%100<50?1:-1
  }else{
    yy=sin((i+frameCount + mouseX)/link.freq)
  }
  // 乘上振幅
  yy*=link.amp
  vertex(i, yy*5*ratio)
}
endShape()

11. 增加逼哩逼哩閃爍效果

這次的創作主題是與電有關,想像是與電有關的閃電或是電腦機台運作的時候,都會一閃一閃的,這邊就要來製造這樣的效果。因此要在繪製線條的外面再包一層 if ,限制在特定的情況之下才會顯示線條,而在 if 裡面所帶的參數有跟時間相關 frameCount,也有自行設定隨機的變數 activeMod ,以及 forEach 中的編號 linkId 。為了要能夠存取 linkId ,記得要設定在 forEach 的第二個參數。

links= Array.from({length: 50},(d,i)=>{
  let start = createVector(int(random(ww)),int(random(hh)))
  let randomDelta = random([-2,2,-5,5,-10,10])
  let end = start.copy().add(createVector(random(randomDelta),random(randomDelta)))
  let type = random(['sine','sine','sine','square'])
  return {
    start,	end,
    freq: random(0,50)*random(),
    color: random(colors),
    amp: random(1,3)*random(),
    type,
    label: type + " #"+i,
    activeMod: random(50,100)
  }
})
if((frameCount+linkId*30)%100<link.activeMod && random()>0.01){
  beginShape()
  for(var i=0; i<rr; i+=2){
    let ratio = 5*(rr/2-abs(i-rr/2))/rr
    let yy = sin((i+frameCount + mouseX)/link.freq)
    if (link.type=="square"){
      yy=(i+frameCount + mouseY)%100<50?1:-1
    }
      yy*=link.amp
      vertex(i, yy*5*ratio)
    }
  endShape()
}
// 原本的 forEach 只有一個參數
links.forEach((link)=>{}

// 加入第二個表示 index 的參數
links.forEach((link,linkId)=>{}
https://imgur.com/Oom6CBH.gif

12. 加上文字

最後做一點修飾,加上在科幻電影裡面會出現白底黑字的效果在畫面上的左上方。在畫白底的時候可以透過 textWidth() 來幫助我們動態的依據文字來計算出長度。

// 加上白底黑字
push()
  translate(width-50,50)
  rotate(PI/2)
  textSize(14)

  fill(255)
  let tx1 = " System ?: " + frameCount/10
  let tx2 = " Active Count: " + links.filter(link=>link.active).length
  rectMode(CORNER)
  rect(8,-5,textWidth(tx1)+5,15)
  rect(8,15,textWidth(tx2)+5,15)
  fill(0)
  text(tx1,10,5)
  text(tx2,10,25)
pop()
https://imgur.com/4xW6wGJ.gif

結語

起初在創作上老闆其實也沒有打算想要做逼哩逼哩的效果,一開始也僅僅想是嘗試看看將線條隨意連連看隨呈現什麼樣的效果。是到後半段去讓背景的網格點更加明顯,以及加上逼哩逼哩的效果才看起來有電波訊號得感覺,與最後文字的畫龍點睛才讓它讓整體變得更有科幻感。

https://imgur.com/qH5eQ1U.gif

還意猶未盡?看看這一篇製作色散海葵的教學讓你功力再進階。

如果你因此對互動藝術程式創作產生興趣,歡迎加入老闆開的 Hahow 課程互動藝術程式創作入門,讓老闆跟你分享不同的創作!

互動藝術程式創作入門是為了不會程式的人設計的課程,課程中會帶你看看不一樣的作品,並從基礎引導大家一步步完成作品,透過每次的賞析、實作到修正作品,讓大家覺得寫 code 不是這麼難的事情,將這個過程想像成,拿一隻比較難的畫筆在進行創作,如果有機會使用它,便能夠做出和與眾不同的創作。

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

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 【p5.js創作教學】電波電路Wave Circuit – 來做個逼哩逼哩送訊號的電路吧!(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【p5.js 創作教學】 色散海葵(直播筆記) https://creativecoding.in/2021/01/25/p5-js-%e5%89%b5%e4%bd%9c%e6%95%99%e5%ad%b8-%e8%89%b2%e6%95%a3%e6%b5%b7%e8%91%b5/ Sun, 24 Jan 2021 17:08:55 +0000 https://creativecoding.in/?p=492 這次的直播內容主要有四個部分: 透過繪製扭動的線條來呈現海葵觸鬚的樣子 (步驟一~步驟四) 加上滑鼠的互動以及視覺上的裝飾,包含了顏色以及材質,還有在外層加框框,形成了類似畫框的效果 (步驟五~步驟七…

這篇文章 【p5.js 創作教學】 色散海葵(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

這次的直播內容主要有四個部分:

  • 透過繪製扭動的線條來呈現海葵觸鬚的樣子 (步驟一~步驟四)
  • 加上滑鼠的互動以及視覺上的裝飾,包含了顏色以及材質,還有在外層加框框,形成了類似畫框的效果 (步驟五~步驟七)
  • 加上細的觸鬚增加細節(步驟八)
  • 海葵底部做收斂,形成一叢海葵,以及最後的修飾(步驟九~步驟十)

主要會用到的 API :

還是新手?想要快速上手p5.js請來看p5.js 快速上手:互動網頁教學

Part 1 海葵

透過四個步驟,藉由 curveVertex 來畫線條,並藉由隨機移動線條來形成多條海葵觸鬚

單一條線

首先以畫面的中心為基準點,由下而上畫出一點一點所連成的線條。這邊因為我們將基準的位子轉換了translate(0,height),因此在畫線的的時候,注意是由下往上畫,vertex(xx,-i) 中的 i 要加上負號

function setup() {
	createCanvas(800, 800);
	background(0);
	
	xx = width/2
}
var xx 
function draw() {
	translate(0,height)
	stroke(255)
	
	beginShape()
	strokeWeight(50)
	for(var i=0; i<500; i++){
		vertex(xx,-i)
	}
	endShape()
}
https://i.imgur.com/kTnQOnN.png

左右搖擺

畫好線後,我們為了讓整條線的每一個點隨機分布在不同位子上,同時整條線左右搖擺,所以在 noise 中放入了每個一個點(i)及時間(frameCount)的因子,並存在 deltaX 變數中。 這麼要注意一下,我們為了讓畫出來的圖形不要重疊在一起,所以這裡要在加上 background(0),讓每一次的背景都重新刷新

function draw() {
	translate(0,height)
	stroke(255)
	
	background(0) // 更新 background
	
	beginShape()
	strokeWeight(50)
	for(var i=0; i<500; i++){
		let deltaX = noise(i/400, frameCount/100) * 200 // 加上 noise
		vertex(xx + deltaX,-i)
	}
	endShape()
}

https://i.imgur.com/93JP5pa.gif

固定底部

現在看上去的狀態是整條線都要搖擺,但是我們希望它的底部是相對固定的,所以加上 let deltaFactor = map(i, 0, 50, 0, 1 , true),因為只要限制底部,所以這邊只將 0~50 的點進行轉換,並將 deltaFactornoise 相乘。在 noise 部分,由於它所產生出的數值是 0~1,為了讓它是左右搖擺的,所以在減去 0.5,讓產生出來的值範圍在 -0.5 ~ 0.5 之間。這時候看上去還有些怪怪的,原因是因為系統預設填了白色的顏色,這邊要加上 noFill() 來取消系統的預設填色

function draw() {
	translate(0,height)
	stroke(255)
	
	background(0)
	noFill()  //取消系統填色
	beginShape()
	strokeWeight(50)
	for(var i=0; i<500; i++){
		let deltaFactor = map(i, 0, 50, 0, 1 , true)
		let deltaX = deltaFactor * (noise(i/400, frameCount/100)-0.5) * 200
		vertex(xx + deltaX,-i)
	}
	endShape()
}

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

多個線條

這時候我們可以將剛剛的程式碼包成 anemone() 函式。接著就可以使用 for 迴圈來讓它一次產生多條擺動的線條,為了使每一條線條扭動的方式不一樣,因此加入一個變數 ridfunction 中,並且放到 noise 位子。 另外為了將擺動變得更加平滑一些,這邊將 vertex(xx + deltaX,-i) 更改為 curveVertex(xx + deltaX,-i*2)

function setup() {
	createCanvas(800, 800);
	background(0);
}

function anemone(xx,rid){
	beginShape()
	strokeWeight(80)
	for(var i=0; i<300; i++){
		let deltaFactor = map(i, 0, 50, 0, 1 , true)
		let deltaX = deltaFactor * (noise(i/400, frameCount/100,rid)-0.5) * 300
		curveVertex(xx + deltaX,-i*2)
	}
	endShape()
}

function draw() {
	translate(0,height)
	stroke(255)
	
	background(0)
	noFill()
	for(var i =0;i<20;i++){
		anemone(i*200, i)
	}
}

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

Part 2 互動與裝飾

現在畫好的基本的圖形後,接著就來上顏色、材質以及外框

加上顏色

首先,先建立顏色的清單,使用 forEach 去跑每一個顏色,並在 anemone() 中加入 clr 這個參數,讓用 stroke(clr) 來指定顏色,這時候就有一叢叢具有三種顏色的的海葵了,接著就要使用混和模式來控制顏色的疊加。這裡使用 blendMode(SCREEN) 來控制混和的效果,不過這邊有個問題是,這會讓原本 background 的失效,而解決的辦法就是在畫 backgound 前加上原本預計的顏色疊加模式 blendMode(BLEND)。 這邊除了設定顏色外,也讓線條的粗度(strokeWeight)以及高度(hh)都加上 noise,讓整體更加有變化。

function anemone(xx,rid,clr){
	beginShape()
	strokeWeight(noise(rid,5000)*180) // 使用 noise 加入 rid,加入變化性
	let hh = noise(xx,rid,1000)*500 + random(2) // 高度也使用 noise 加入 rid,加入變化性
	stroke(clr)
	for(var i=0; i<hh; i++){
		let deltaFactor = map(i, 0, 50, 0, 1 , true)
		let deltaX = deltaFactor * (noise(i/400, frameCount/100,rid)-0.5) * 300
		curveVertex(xx + deltaX,-i*2)
	}
	endShape()
}

function draw() {
	translate(0,height)
	stroke(255)
	
	blendMode(BLEND) // 設定回系統預設的疊加模式
	background(0)
	noFill()
	
	let clrs = ['red', 'green', 'blue']  //建立顏色清單
	blendMode(SCREEN) //設定疊加模式
	for(var i =0;i<10;i++){
		clrs.forEach((clr,clrId)=>{
			anemone(i*100, i+clrId/2, clr)
		})		
	}
}

https://i.imgur.com/yOKzWU0.gif
  • 變化多端的顏色 (Option,想讓海葵顏色更豐富可以參考)

滑鼠控制

設定會根據高度影響的 mouseFactor 以及滑鼠左右移動變化量的 mouseDelta。設定好後再加到 curveVertex() 中 x 的位置上,這樣的效果可能讓左右的搖擺是根據滑鼠的移動方向。而這裡還另外加上 mouseDirectionFactor,讓滑鼠的左右移動與海葵搖擺的關係上更加自然。

function anemone(xx,rid,clr){
	beginShape()
	strokeWeight(noise(rid,5000)*200)
	let hh = noise(xx,rid,1000)*500
	stroke(clr)
	for(var i=0; i<hh; i++){
		let deltaFactor = map(i, 0, 50, 0, 1 , true)
		

		let mouseFactor = map(i,0,500,0,1)*log(hh)/10 
        let mouseDirectFactor = noise(frameCount/50)-0.5
		let mouseDelta = map(mouseX,0, width, -500, 500)*mouseDirectFactor
		let deltaX = deltaFactor * (noise(i/400, frameCount/100,rid)-0.5) * 300 
		curveVertex(xx + deltaX +mouseDelta*mouseFactor,-i*2) 
	}
	endShape()
}

加上材質

先在全域的地方定義材質的變數 overAllTexture,並在 setup() 定義材質的樣式。定義好材質後,就可以使用混和模式將材質疊到畫面上。不過由於一開始有移動畫面的中心點translate(0,height),為了讓材質上到正確的位子上,不受這裡的位移影響,所以必須把上材質前畫海葵的部分也用push()pop() 包起來

let overAllTexture   //定義材質

function setup() {
	createCanvas(800, 800);
	background(0);
	
	//設定材質
	overAllTexture=createGraphics(width,height)

	overAllTexture.loadPixels()
	for(var i=0;i<width;i++){
			for(var o=0;o<height;o++){
					overAllTexture.set(i,o,color(100,noise(i/3,o/3,i*o/50)*random([0,50,100])))
			}
	}
	overAllTexture.updatePixels()
}

function anemone(xx,rid,clr){...}

function draw() {
	push()
		translate(0,height)
		stroke(255)

		blendMode(BLEND)
		background(0)
		noFill()

		let clrs = ['red', 'green', 'blue']
		blendMode(SCREEN)
		for(var i =0;i<10;i++){
			clrs.forEach((clr,clrId)=>{
				anemone(i*100, i+clrId/2, clr)
			})		
		}
	pop()
	
	// 使用混和模式疊上材質
	push()
		blendMode(MULTIPLY)
		image(overAllTexture,0 ,0)
	pop()
}
https://i.imgur.com/WM3O1uj.gif

加框

設定混和的模式 blendMode(BLEND),將矩形疊加在上形成外框。這裡要注意記得將原本生成海葵的地方所設定的 background(0) 取消,不然顏色會被蓋過去,這樣框的效果就出不來了。

function draw() {
    //加上外框
	push()
		fill(0)
		noStroke()
	    blendMode(BLEND)
		rect(0,0,width, height)
	pop()
    //
	
	push()
		translate(0,height)
		stroke(255)
		blendMode(BLEND)
		// background(0) 取消這裡的 background,不然會把顏色蓋過去
		noFill()

		let clrs = colors
		blendMode(SCREEN)
		for(var i =0;i<10;i++){
			clrs.forEach((clr,clrId)=>{
				anemone(i*100, i+clrId/2, clr)
			})		
		}
	pop()
	
	
	push()
		blendMode(MULTIPLY)
		image(overAllTexture,0 ,0)
	pop()
}

Part 3 細海葵

接下來要來加上相對比較細小的海葵,這裡可以直接複製我們前面已經利用 for 迴圈所產生出的海葵。在這裡因為我們想要海葵能夠相對長的比較細而且也比較長,所以這邊再多增加兩個參數在後面。接著再 anemone() 中新增變數名稱,分別為控制寬度的 thinkness 長度的 length,並帶到 function 中去做使用

//粗線條
for(var i =0;i<10;i++){
    clrs.forEach((clr,clrId)=>{
        anemone(i*100, i+clrId/2, clr)
    })		
}

//細線條
for(var i =0;i<5;i++){
    clrs.forEach((clr,clrId)=>{
        anemone(i*100, i+clrId/2 + 50, clr, 0.05, 1.2)
    })		
}
function anemone(xx,rid,clr, thinkness=1, length=1){ //新增參數 thinkness=1, length=1
	beginShape()
	strokeWeight(noise(rid,5000)*150*thinkness) // 乘上 thinkness
	let hh = noise(xx,rid,1000)*height/2 + random(2)
	stroke(clr)
	for(var i=0; i<hh; i++){
		let deltaFactor = map(i, 0, 50, 0, 1 , true)
		let mouseFactor = map(i,0,400,0,1)*log(hh)/10
		let mouseDirectFactor = noise(frameCount/50)-0.5
		let mouseDelta = map(mouseX,0, width, -500, 500) * mouseDirectFactor

		let deltaX = deltaFactor * (noise(i/400, frameCount/100,rid)-0.5) * 300
		curveVertex(xx + deltaX +mouseDelta*mouseFactor,-i*2* length) // 乘上 length
	}
	endShape()
}

細線上加入球球

畫好了細的線條後,我們想要在細的線條的上的最後一個點上加上一個小球,所以這裡必須新增的兩個變數(lastX, lastY)去紀錄每一個點,接著由 curveVertex(lastX,lastY) 畫出來線條來。當離開 for 迴圈時,代表已經畫完整條線了,並且此時變數lastX, lastY所存的是最後的一個點的位子,這時在 for 迴圈外面在根據剛才紀錄去畫上圓形。而在小圈圈的繪製上,在這裡加上 nosie,讓小圈圈可以隨機的出現在細線尾端。

function anemone(xx,rid,clr, thinkness=1, length=1){
	beginShape()
	strokeWeight(noise(rid,5000, frameCount/1000)*150*thinkness)
	let hh = noise(xx,rid,1000+frameCount/100)*height*0.6 + random(2)
	stroke(clr)
	
	let lastX, lastY
	for(var i=0; i<hh; i+=2){
		let deltaFactor = map(i, 0, 50, 0, 1 , true)
		let mouseFactor = map(i,0,400,0,1)*log(hh)/10
		let mouseDirectFactor = noise(frameCount/50)-0.5
		let mouseDelta = map(mouseX,0, width, -500, 500) * mouseDirectFactor
		let deltaX = deltaFactor * (noise(i/400, frameCount/100 + mouseY/100,rid)-0.5)*300
		
		lastX = xx + deltaX +mouseDelta*mouseFactor
		lastY = -i*2* length
		curveVertex(lastX,lastY)
	}
	endShape()
	
    if(thinkness!=1 && noise(frameCount/1000,rid)<0.8){ //新增 noise
        ellipse(lastX,lastY-10,6,6)
    }
}

Part 4 一叢海葵

目前海揆線條是由下而上往上生長,現在我們希望它是由中間往外擴散,變成一叢的感覺。所以為了 改變 X 的位子,使用 log 去取值,產生 ratio,再由中心點 width/2 向外向上去畫出一個指數型弧線的線條。這邊直接看 ratio 這個變數一眼看上去有些複雜,不過一開始也是由簡單直覺的方向去嘗試的,像是先試試 let ratio = map(i,0,500,0,1,true),這會讓圖形直接變成一個倒三角形,形成一個線性的變化,接著才嘗試 log,使圖形呈現指數的圖形。實際 log 所對應數值所畫出來的圖形可以參考這裡。可以看的出來在一開始 y 軸的數值上升的很快,接著快速的趨近於緩和,這所對應的ratio 數值也會是如此

let ratio = map(log(i),0,noise(frameCount/100, mouseX/100)*3+5,0,1,true)
curveVertex(lerp(width/2,lastX,ratio),lastY)

最後的微調

最後,在一些小地方進行調整。

  • 將粗度與高度都加入時間上(frameCount)上的變因
strokeWeight(noise(rid,5000, frameCount/1000)*150*thinkness)
let hh = noise(xx,rid,1000+frameCount/100)*height/2 + random(2)
  • 滑鼠上下移動會影響左右的搖擺,所以把 deltaX 這個變數上新增 mouseY 在 noise 之中
let deltaX = deltaFactor * (noise(i/400, frameCount/100 + mouseY/100,rid)-0.5) * 300
  • 由於在顏色上有點過曝,所以加上透明度的效果,並且加上 noise,試試看讓他有忽暗忽亮的效果
for(var i =0;i<10;i++){
    clrs.forEach((clr,clrId)=>{
        let useColor = color(clr)
        useColor.setAlpha(150 + noise(frameCount/100,i)*10)
        anemone(i*100, i+clrId/2, useColor)
    })		
}

結語

回顧整個範例,可以發現我們在不少的地方都使用了 noise 來產生隨機,進而產生了一些動態感。常常與 noise 搭配的有隨時間變化的 frameCount 以及滑鼠移動的變化量 mouseY。在 noise 的使用上有時也並非就直接就使用它,而是當我們覺得某個屬性,像是海葵都高度,向外擴張的程度想要更有隨機動態感的時候再加上去的

在每一個點的繪製上為了製造的左右晃動以及跟滑鼠互動,所以定義了不少的變數,其中還常常用到了 map 以及 noise,這裡面的數字沒有一定的絕對數值,這都是在創作過程中慢慢地去嘗試出來的。若是覺得有些程式碼看起來太過複雜,建議可以先將 noise 改回一般的常數,這樣閱讀起來比較簡單也容易思考。

還意猶未盡?看看這一篇【p5.js創作教學】電波電路Wave Circuit – 來做個逼哩逼哩送訊號的電路吧!的教學讓你功力再進階。

如果你因此對互動藝術程式創作產生興趣,歡迎加入老闆開的 Hahow 課程互動藝術程式創作入門,讓老闆跟你分享不同的創作!

互動藝術程式創作入門是為了不會程式的人設計的課程,課程中會帶你看看不一樣的作品,並從基礎引導大家一步步完成作品,透過每次的賞析、實作到修正作品,讓大家覺得寫 code 不是這麼難的事情,將這個過程想像成,拿一隻比較難的畫筆在進行創作,如果有機會使用它,便能夠做出和與眾不同的創作。

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 【p5.js 創作教學】 色散海葵(直播筆記) 最早出現於 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 - 互動程式創作台灣站

]]>
來做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%8a%e7%af%87%ef%bc%88%e7%9b%b4%e6%92%ad%e7%ad%86%e8%a8%98%ef%bc%89/ Fri, 15 Jan 2021 10:34:05 +0000 https://creativecoding.in/?p=461 本文翻自 [週四寫程式系列] 來做SVG動畫讓蔥油餅翻滾吧!,若是對文章內容有疑問,或是想要老闆手把手帶你飛,都可以觀看影片詳細內容。 今天老闆要以曾經接手的 台北聲音地景計畫 做為案例,跟大家分享從…

這篇文章 來做SVG動畫讓蔥油餅翻滾吧! (上):發想素材、將素材引入網頁(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

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

今天老闆要以曾經接手的 台北聲音地景計畫 做為案例,跟大家分享從專案發想、素材準備到 svg 網頁動畫。當初這個專案是做為捷運站音樂的競賽網站,希望透過聲音做為媒介,連結城市的生活空間。客戶為這個專案準備許多環境音素材,提供比賽使用,但僅是把聲音素材提供給參賽者,讓老闆覺得非常的可惜,該如何將這些素材效益最大化,聲音又要如何跟場景的記憶關聯起來,把故事跟企畫更加完善。最後老闆決定結合網頁動畫與聲音,讓使用者觀看網頁時,透過與現場環境的圖象互動,觸發聲音的回饋,讓使用者將環境與聲音做連結,於是有了這樣的專案產生。

目標

本篇文章中,老闆會帶大家完成元件的創作,並在網頁中將元件導入,完成基本的動畫操作,作為下一篇文章的暖身。

  • isometric 創作場景
  • 使用 vue, ajax 將 svg 放進網頁
  • 小試身手﹔操作 svg 元件,賦予簡單的互動與動態

素材準備

有許多方式和工具能夠製作網頁動態畫面,舉凡 css、svg、canvas、Pixi.js 等,但不管哪一種方式製作動畫,場景和演員是不可或缺的,接下來讓老闆分享動畫要使用到的素材發想過程,想看老闆怎麼完成素材,可以跟著前面提供的影片連結,和老闆一起動手操作。

創作工具

Adobe Illustrator (可到官網上選擇免費版,若是常使用也可選擇付費版)

素材收集與發想

這個專案的困難點,是因為捷運站眾多,創作前,需要到各場域中收集足夠的場景資料,才有足夠的資料來作為創作的依據。

引用自電獺少女

在風格方面,老闆選擇以 isometric 作為創作風格,等距視角(isometric)風格也稱為等軸測圖。採用45度視角,沒有消失點,達到平面上模擬 3D 效果,讓人產生有深度的錯覺。最有名的代表遊戲就是紀念碑谷,若是想知道更多相關設計,也可以到 Pinterest 上查詢相關風格的作品。

創作 isometric 作品的時候,可以利用等距網格輔助創作。

等距網格

用 Illustrator 創作時,記得要幫元件群組與命名,讓開發時,能夠快速選取目標套上動畫。

起手式 – 環境與素材載入

環境

影片中老闆是使用 codepen 來做為開發環境,好處是可以直接引入需要的工具,但載入素材時會遇到跨域問題要解決。這邊提供大家在本機開發的方式。

  1. 將開發需要用到的圖片整理到專案資料夾中
  2. 開始撰寫 html ,載入 CDN,這邊會使用到的 CDN 有兩隻,目的分別如下
    • jQuery – 負責將素材導入
    • vue.js -資料綁定畫面

資源載入

完成 html 結構且載入 CDN 之後,我們要準備讓 svg 動起來,這邊也獻上素材,提供給沒有素材的開發者們,可以直接進入開發。

這邊先解說 script 的內容,我們使用 Vue 來做為資料與畫面綁定的套件。宣告 Vue 的方式如下, el 為要將資料綁定在哪個區塊的畫面中,這裡填寫 #app,也就是到時資料會被綁定在 id=”app” 的 div 中。data 的內容為需要綁定的資料。mounted 為 Lifecycle 中的其中一個鉤子,代表已經抓到渲染的對象,且資料已經成功綁定,這個階段就是載入外部素材的最佳時刻,確保我們的導入的資料能夠成功綁定在對應的位置。

在 Vue 中,從元件初始化到註銷的過程中有許多 hook (created, mounted, destroyed…),提供使用者在元件不同階段中,能夠執行不同工作。詳細內容可以參考 官方文件

使用 jQuery 抓資料的方式如以下程式碼,這邊要小心 this 的使用,如果在外面沒有先宣告 vobj 把 this 記下來,直接寫 this.svg = red 會抓不到外層 vue 的實體資料。

var vm = new Vue({
	// 綁定的範圍
	el: '#app',
	// 綁定的資料
  data: {
		scene_door: ''
	},
	// vue 生命函數,資料載入完成後就執行
	mounted() {
		var vobj = this
		$.get('./imgs/scene.svg', function (res) {
			vobj.scene_door= res
		}, 'text')
	}
})

接著要把素材導入畫面了,這邊我們先在 <body></body> 中加入以下程式碼,id=”app” 是給 vue 辨識綁定畫面的位置。這邊可以看到 v-html=”scene_door” ,v-html 是 vue 的模板與法,會將 data 內的 scene_door 用 html 的方式導入。

在實務中,要小心使用,確定是安全的資料才渲染出來,避免使用者塞入惡意程式碼,但因為此專案是使用我們自己製作的內容,不用擔心被惡意攻擊。

<body>
	<div id="app">
		<div class="scene" v-html="scene_door"></div>
	</div>
</body>

成功之後我們會看到, svg 已經備載入到網頁中了,也可以撰寫 css 將畫面置中。

圖片成功引入

先備知識

接下來,藉由以下兩個目標,讓大家熟悉這個專案會頻繁使用到的概念,1. css 選擇器 2. transition

灰色框與神秘的互動區塊

在畫面中可以看到許多灰色的外框,這是老闆設計要來做為使用者觸發動畫偵測範圍。但是總不能讓這些灰框直接在正式專案上顯示吧?這時就得感謝先前在製作素材時,貼心的我們了。若是在製作素材時,有勤勞的為圖層命名,匯出時會提供選項讓大家選擇,現在我們就能直接在寫 css 使用 data-name 的方式去控制對應元件了。

為什麼不使用 id 呢?因為 id 名稱一個網頁只能有一個,為了避免日後撞名字的問題,這邊選擇使用 data-name。

我們使用開發者工具檢查時,可以觀察到老闆提供的素材中,灰框的 data-name 屬性值為 hidden,我們可以在 scss 中加入一些語法將它變成透明的,但是只把它透明度變成0還不夠,還必須幫它加上背景顏色,並在 trigger 加上改變滑鼠游標的語法。讓使用者滑入互動區塊時,藉由滑鼠的改變,提示使用者此區塊是可以互動的。

// 擁有 data-name 屬性,且該屬性的值為 hidden
[data-name='hidden'] {
	opacity: 0;
	stroke: transparent;
  fill: transparent;
}
// 擁有 data-name 屬性,且該屬性開頭的值為 trigger
[data-name*='trigger'] {
	cursor: pointer;
}
觀察滑鼠進出感應區塊的狀態

做個開關模擬網頁剛進入的狀況

畫面的感應區塊完成後,我們接著來製作開關模擬使用者剛進入網頁的狀況。首先先在 data 中新增一個新的變數,並綁定到畫面中的 input 。大家如果想觀察 vue 資料的變化,也可以去下載套件 vue devtool 輔助開發。透過 input 做為開關之後,場景也必須要跟著互動,這邊我們先使用 v-model 綁定資料,並用 v-bind 來綁定場景 class,當監聽到 trigger 為 true 時,程式會動態的幫我們在 .scene 加上 .active。

var vm = new Vue({
	...
	data: {
		trigger: true,
		scene_door: ''
	},
	...
})
<div id="app">
	<!-- v-model 綁定資料 trigger-->
	<input id="trigger" v-model="trigger" type="checkbox">
	<label for="trigger">場景開啟</label>
	<!-- v-bind 動態改變 class -->
	<div class="scene" v-html="scene_door" v-bind:class="trigger? 'active': ''"></div>
</div>

接下來要讓開關與動畫結合,我們希望當打開開關時,三個小島會有時間差的進入場景,這裡我們又再次使用到前面的技巧,使用 data-name 將所有 island 開頭的內容透明度改成0,在外層吃到 active 後輪流進場。要注意的是,svg 不吃 top 屬性,所以這邊我們改使用 translateY 來讓小島有浮起來的效果。透過 transition-delay 設定不同秒數,達成輪流進場的效果。svg 支援的 css 屬性可參考這篇文章。使用到 transition, transition-delay 也可以到 w3c 中深入了解

.active {
    [data-name*="island"] {
        opacity: 1;
        transform: translateY(0px);
    }
}

[data-name*="island"] {
    opacity: 0;
		// 補間動畫屬性 時間
    transition: all .5s;
		// 初始位置往下 30px
    transform: translateY(30px);
}
[data-name*="island_1"] {
    transition-delay: .1s;
}
[data-name*="island_2"] {
    transition-delay: .3s;
}
[data-name*="island_3"] {
    transition-delay: .5s;
}
  • 做個開關模擬剛進入畫面的狀況
div {
	// 補間動畫 屬性 時間
	transition: all .2s;
	// 動畫,要與 @keyframe 搭配,動畫名稱 動畫時間 延遲時間 次數
	animation: animationName 2s -0.2s infinite;
	// 為了避免動畫走鐘,加上動畫時可以設定動畫的中心點,也可以寫成百分比。
	transform-orign: center center;
	
	// 滑鼠經過時觸發的內容
	&:hover { ... }
	// 行動裝置不會有 hover, 改使用為 focus
	&:focus { ... }
}

@keyframes animationName {
 // 在整段時間的 0% 要做什麼動畫
	0% { ... }
	50% { ... }
	100% { ... }
}

結語

藉由以上的操作,相信大家已經能夠利用 vue, svg 製作簡易的動畫了,下一篇老闆將帶大家逐一將場景中的動畫實現,也會與大家分享每個區塊動畫的製作思維。

課程推薦

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

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

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 來做SVG動畫讓蔥油餅翻滾吧! (上):發想素材、將素材引入網頁(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
資料視覺化的動態長條圖製作(下):Vue/ D3(直播筆記) https://creativecoding.in/2021/01/12/%e8%b3%87%e6%96%99%e8%a6%96%e8%a6%ba%e5%8c%96%e7%9a%84%e5%8b%95%e6%85%8b%e9%95%b7%e6%a2%9d%e5%9c%96%e8%a3%bd%e4%bd%9c%ef%bc%88%e4%b8%8b%ef%bc%89%ef%bc%9avue-d3%ef%bc%88%e7%9b%b4%e6%92%ad%e7%ad%86/ Tue, 12 Jan 2021 15:44:10 +0000 https://creativecoding.in/?p=405 在上篇我們講解了如何使用jQuery製作網頁上常用的動態長條圖,在本篇文章中我們將實際操作另外兩種框架:Vue/ D3的製作方法。還沒看上一篇的快快補唷👉 資料視覺化的動態長條圖製作(上):…

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

]]>
在上篇我們講解了如何使用jQuery製作網頁上常用的動態長條圖,在本篇文章中我們將實際操作另外兩種框架:Vue/ D3的製作方法。還沒看上一篇的快快補唷👉 資料視覺化的動態長條圖製作(上):jQuery(直播筆記)

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

主要會用到的知識:

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

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


框架二:Vue

Vue就像你雇用了一位負責代換抄寫的小幫手,你所需要做的就是提供資料的類型和處理方式,小幫手就會自動化地填入。

首先將剛剛jQuery完成的圖表直接fork,fork是幫你複製一個新的pen,修改時不會動到原本的東西。在JS的設定裡面代入Vue。

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

由於Vue的特性是在HTML寫模板,然後利用JS帶入資料,所以在HTML裡面新增.bar,JS的部分先把height和background-color的設定複製起來,整個forEach的部分拿掉,//更新的地方暫時註解掉。

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

接著把Vue這個小幫手請出來幫我們抄寫跟產生DOM物件,告訴他工作的範圍後,也要指定原本的資料datas,在HTML告訴他希望他幫我們重複打.bar這個木樁10遍,同時把木樁的數值也打上去,用toFixed(1)取到小數點第1位。

HTML

#app.graph
  .bar(v-for="data in datas") {{data.toFixed(1)}}

JavaScript

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

for( var i=0; i<10; i++){
  datas.push(Math.random()*10+5);
}

var vm = new Vue({
  el: "#app",
  data:{
    datas: datas
  }
})

接著來設定長條方塊的樣式,透過Vue可以動態地去修改CSS屬性。在HTML新增:style="gen_style(data)",gen_style()像是一個方法,指導我們的小幫手說style要用data這個資料去算一個屬性(顏色、高度)做呈現。那小幫手要如何知道gen_style這個方法的實際內容呢?我們需在JS新增一個Method:{}來告訴他。Method就像一本作業指導書,在裡面我們需要回傳一個JSON物件,用來說明CSS屬性的設定。因為rgb數值設定的關係,記得data需要先用語法parseInt()處理成整數唷。

HTML

#app.graph
  .bar(v-for="data in datas",
       :style="gen_style(data)") {{data.toFixed(1)}}

JS

var vm = new Vue({
  el: "#app",
  data:{
    datas: datas
  },
  methods:{
    gen_style(data){
      var d = parseInt(data);
      return {
        "height": data*20+"px",
        "background-color":"rgb("+data*10+","+data*10+","+data*10+")"
      };
    }
  }
})

登愣,神奇的事情發生了!我們明明沒有實際地對DOM物件做屬性指定的動作,他卻乖乖地自動產生對應資料的長條圖,這就是Vue好用的地方啦,就像你買了一台3D印表機,只要告訴他規則、資料,他就會自動幫你蓋好房子了。

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

剛剛我們用jQuery寫了一拖拉庫來達到每500毫秒自動更新的效果,在Vue只需要告訴他vm.datas = datas;就可以完成囉。

//更新
setInterval(()=>{
  datas = generateData();
  vm.datas = datas;
},500);

總地來說Vue的自動化很高、很方便,但相對他的產出就必須follow你訂下的規則,沒辦法一個一個做特定調整。


框架三:D3

D3有點像是jQuery的強化版,只是這次你拿的不是一支小破錘子,而是課金後的超炫砲無敵錘子,除了可以根據規則做重複的事情以外,還可以針對個體做客製化調整。

我們再fork一次剛剛jQuery完成的圖表。在JS的設定裡面代入D3。

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

剛剛在jQuery我們透過forEach把物件一個一個抓出來改,現在我們不需要單獨抓出來了所以先註解掉,取而代之的是批次抓取做修改,setInterval()的地方也暫時註解掉。

  1. select()選擇graph裡面全部的bar
  2. 資料來源是datas請來這邊找。
  3. 因為現在bar還不存在而datas有10筆,透過enter()來補齊物件跟資料差距的量。當DOM數量少於data的數量,或者壓根一個都沒有的時候,我們一般會使用enter()幫忙建立。
  4. append顧名思義就是添加的意思,透過append()新增div。
  5. attr()是負責命名這個div的class為bar。
d3.select(".graph").selectAll(".bar")
  .data(datas)
  .enter()
  .append("div")
  .attr("class", "bar")
https://i.imgur.com/VSS87CD.png

有了十個bar之後接下來就是我們的重頭戲──調整高度和顏色。高度根據資料datas(d)中的第幾筆資料(i)去做調整,乘上20讓他差距更大、可視性更好,記得加上px因為他是CSS的屬性。

.style("height",(d,i)=>(d*20+"px"))

這邊要介紹D3中的一個概念:定義比例scale.linear()。我們可以把它理解成地圖上常見的比例尺,例如台灣面積大約3萬多平方公里,要塞進一張 A4 大小的地圖根本是不可能的任務,這時候就必須要用到比例了。首先,命名一個函數為yscale做線性轉換,domain()代表「原始的資料範圍」,range()則代表「轉換後的資料範圍」。先前我們在CSS把grpah的height設定為400px,所以設定轉換後range為0-400。

var yscale= d3.scaleLinear().domain([5,15]).range([0,400])

由console的結果可見yscale現在是一個可做等比例放大的函數,就像多拉O夢的放大燈ㄧ樣,藉由yscale我們也不需要再自己手動幫d乘上20增加他的級距了。

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

顏色也是用相同的概念處理,除了數值的放大轉換外,D3也可以幫你把數值轉成顏色。命名一個函數為color,設定轉換成白色到紅色的區間。由console的結果可見color會自動幫我們把5-15的數值轉成rgb的色碼。

var color = d3.scaleLinear().domain([5,15]).range(['white','red'])

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

接著就來完成我們的圖表啦,使用語法style()做高度和顏色的設定,美美的圖表就大功告成了。

d3.select(".graph").selectAll(".bar")
  .data(datas)
  .enter()
  .append("div")
  .attr("class", "bar")
  .style("height",(d,i)=>(yscale(d)+"px"))
  .style("background-color", (d)=>color(d))  

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

體會到D3的強大了嗎?雖然Vue的效能最好,但D3的優點在於他可以批次地處理一些華麗的動畫效果,例如上面的圖表還可以添加以下幾種語法,至於圖表會如何地變動就讓大家體驗看看囉!

  .style("margin-bottom", (d)=>(200 - yscale(d)/2 + "px"))
  .transition().duration(500)
  .style("margin-bottom","0px")

最後再來個結尾小bonus,或許你也會有一樣的疑問。

讀者:「如果我想要有Vue的效率配上D3的華麗,到底該用哪一個呢?」

老闆:『爭什麼,摻在一起做成撒尿牛丸啊!』

Vue跟D3該如何結合呢?把剛剛用Vue完成的圖表叫回來,可以看到我們在height和background-color那做了一拖拉庫的處理,現在這些都可以交給D3來代勞。把剛剛在D3圖表用scaleLinear處理的程式碼貼過來,data的部分用yscale和color這兩個函數做計算,這樣就大功告成囉。

return {
        "height": yscale(data)+"px",
        "background-color": color(data)
      };

https://i.imgur.com/1j70XT6.png

想看更複雜的結合使用,請往這邊走👉 codepen實作範例:長條圖_Vue&D3

框架比較

以上就是這次如何用三種框架實作長條圖的講解,希望這個介紹會讓大家對於各框架的優劣勢更加了解,在未來的專案中可以無痛使用,讓長條圖替你的網頁畫龍點睛吧!我們下次再見囉~

課程推薦

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

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

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

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

]]>