直播筆記 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/直播筆記/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Wed, 05 Jul 2023 04:48:10 +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 直播筆記 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/直播筆記/ 32 32 p5 js互動藝術程式創作 – 初階應用實戰教學!(下篇) https://creativecoding.in/2022/12/26/p5js-workshop-clab-2/ Mon, 26 Dec 2022 03:12:37 +0000 https://creativecoding.in/?p=3435 老闆在第一屆「Processing 臺灣國際社群日」活動中,受邀為設計工作坊擔任講師,針對 p5.js互動藝術程式創作入門的主題進行為期兩天的分享。分別為第一天的基礎練習與第二天的生程式藝術實作,本篇文章內容為第二天的實作練習,希望能讓同學能學習到以基礎的方式,將創意想像以自己的技術實現!

這篇文章 p5 js互動藝術程式創作 – 初階應用實戰教學!(下篇) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
臺灣當代文化實驗場 C-LAB 在 2021 年 10 月 19 日到 10 月 24 日,邀請全球 Processing 使用者共同參與台灣第一屆「Processing 臺灣國際社群日」,活動中集結不同藝術家及設計師的觀點,帶領大眾以多方的視角,進入未來新媒體藝術的全新想像。
老闆在這一連串的活動中,除了擔任對談講者外(參考文章:創意程式設計:Processing/p5.js教學與趨勢觀察——王連晟、吳哲宇台美連線對談),也受邀為設計工作坊擔任講師,此次就是針對 p5.js互動藝術程式創作入門的主題進行為期兩天的分享,分別為第一天的基礎練習與第二天的生程式藝術實作,希望在兩天的時間內,讓同學能學習到生程式藝術創作的基礎,將自己的創意想像以技術實作呈現!在工作坊正式開始前,老闆提供下列素材讓學員進行課前準備,包含:p5.js 的簡短介紹與 Hahow 上的課程示範。
工作坊會從 p5.js 及工具介紹、p5.js 的開發入門、基本圖形繪製以及變數解說循序漸進。
本篇文章為第二天的生程式藝術實作,依據前一天的基礎練習再更進一階,影片總長兩個小時左右,那我們就事不宜遲開始進入工作坊啦!

上篇這邊走:

課程開始

首先,延續第一天的工作坊進度,老闆先大致瀏覽了昨日有進行創作練習的同學們的作品。有同學提出在創作時的疑問,例如,當設定圖形為筆刷時,雖然圖形是連續性的出現,但當滑鼠動作較快時,圖形就無法連續性的出現,這個問題能如何解決呢?

其實這個問題反向思考來說,就是要讓筆刷連在一起,所以老闆先設定 line(),將已經經過的滑鼠位置,與滑鼠正在進行的位置串起,形成如同在繪圖軟體中,筆刷繪製的效果。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  stroke(255)
  line(pmouseX, pmouseY, mouseX, pmouseY)
}

也可以再依據線條想要調整粗細、顏色變化,或是當滑鼠按下右鍵後再執行筆刷等等變化。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  stroke(255, frameCount%255, 100)
  strokeWeight(10)
  if(mouseIsPressed){
  line(pmouseX, pmouseY, mouseX, pmouseY)
}

第一階段 – 重複迴圈

短暫回顧完第一天的內容後,老闆進行第二天的主要內容的講解,包含以迴圈與互動、增加不同色彩或是複雜圖形的編輯做分享。先從迴圈與互動,建構創作規則上面開始。老闆以知名藝術家,草間彌生的作品說明建構創作規則的原因。草間彌生的知名創作,大多都建立在「觀察事物的形體」、「重複線條的位置」以及「大量的重複」等手法,去構成她的創作系統。或是莫內及梵谷,拆解其創作手法,就會發現使用連續性的線條和錯落的顏色,去重複堆疊出作品。而上述列出的方法,對程式創作來說,是低成本就能執行的技術,也因此我們能藉由這樣的方式去快速獲得一件具有「美術手法」的創作作品。以迴圈來說,可以設計出相同圖形但是不同排列,具有設計感或是抽象感的作品,像臺灣的傳統花磚,就是利用重複圖案,但是利用不同角度的鏡射,排列出具有韻律感的設計作品。

台灣傳統花磚(圖片來源
使用重複技巧的程式作品(作品連結

那我們也開始迴圈實作吧!

老闆先以最單純的圓圈重複做範例,在還沒學習到如何使用重複迴圈技法時,我們可能就是使用重複貼上同一組程式碼,再在位置上做些許變化執行。但這樣並不便利,也因此又能使用迴圈 for() 來解決此項問題,先來說明 for 的基本語法結構。

for (計數變數的起始狀態; 結束條件; 每次結束後變數如何變化) {
  概念相同,需要重複執行的事情
}

實際帶入數字撰寫示範說明:

function setup() {
  createCanvas(windowWidth, windowHeight);
   background(100);
}

function draw() {
  background(0)
  for(let i=0; i<10; i+=1){
    ellipse(width/2+i*50, height/2, 300, 300)
  }
}

在 i 這個變數下,根據前面設定的數字,起始到結束重複執行。

創作過程中,老闆也提供一些設計小技巧,像是顏色選擇。在創作中,顏色佔了很大的一部分,但當我們對於選色上想要有更快速的方式就是應用設計網站。老闆提供一個叫做 COOLORS 的網站給同學做參考,可以選擇自已想要的主要顏色,點按空白鍵就能產生多種配色選項。

COOLORS 網站首頁(網站連結

那要如何經由程式,去排列出不同顏色卻執行重複動作的流程呢?

老闆以陣列舉例,去執行顏色上的排列。在前一天的工作坊中,也有提到陣列以中括號執行即可。我們先列出需要的顏色,再將其存取起來。當形成一個完整陣列後,再套入重複圖形的顏色當中。

let colorList = ["#ffffff","#2e86ab","#d0cdd7","#ffa62b","#273469"]

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(0)
  noStroke()
  for(let i=0; i<50; i+=1){
    fill(colorList[i%5])
    circle(width/4+i*(mouseX/10), height/2, 300 -i*6)
  }
}

在程式創作的好處就是能夠快速地進行大量編輯,例如如果要替換配色,只要在陣列中變換色票即可,又或是,想要變化圖形的位置,我們可以從觀察程式中哪裡有重複進行編輯,以下示範:

先設置一個 function() ,將會重複使用到的程式包在裡面,再將設置的代表文字,套入 draw() 中,簡化重複的程式,增加多樣化編輯。

let colorList = ["#ffffff","#2e86ab","#d0cdd7","#ffa62b","#273469"]

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  noStroke()
  drawCircles(random(width), random(height))
}

function drawCircles(posX, posY){
  for(let i=0; i<50; i+=1){
    fill(colorList[i%5])
    circle(posX+i*(mouseX/10), posY, 300 -i*6)
  }
}

不想制式的都從同一個顏色開始,可以在顏色上使用 random,讓顏色隨機出現,也需要 int 套入在設定前,讓出現的數字得以四捨五入為整數。

for(let i=0; i<count; i+=1){
  fill(colorList[int(i+random(10))%colorList.length])
  circle(posX+i*span, posY, 300 -i*(300/count))
}

增加了隨機的亂數,作品是否又多增加了不同的詩意呢?老闆在這邊提出了有趣的看法,他認為,程式雖然可大量重複的行為,但再加入無法預測的亂數後,出其不意的呈現,就會在視覺上趨向傳統所謂的藝術感。

迴圈教學到這邊,休息十分鐘讓同學提問以及練習。有同學提問,為何在此次練習中,老闆使用 let 而不是 var 做函數參數呢?老闆在這邊鼓勵大家,雖然在 javascipt 當中是以 var 做使用,但 let 會更加嚴謹。舉例說明,用在函數定義範圍時,let 就是在大括號的範圍內具有作用。但如果是 var 的話,是以 function 為範圍,所以定義較大,判斷上較不容易。在這十分鐘,老闆也持續的做小技巧教學。例如,要如何隨機對應不同顏色組合,做圖形上的顏色變化,我們必須先設定不同的 colorList 設定顏色組合,再依據需求變更。

延伸閱讀:使用 let / var / const 宣告變數的差異(鐵人賽:ES6 開始的新生活 let, const – 卡斯伯

let ColorList = ["#ffffff","#2e86ab","#d0cdd7","#ffa62b","#273469"]
let ColorList2 =["#c9cba3","#ffe1a8","#e26d5c","#723d46","#472d30"]

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  noStroke()
  drawCircles(random(width), random(height), mouseX/5, 50, ColorList2)
}

function drawCircles(posX, posY, span, count=50){
  for(let i=0;i<count;i+=1){
    // 從清單中選取顏色
    let colorIndex = int(i+random(10))%ColorList2.length
    fill(ColorList2[colorIndex])
    circle(posX+i*span, posY, 300 -i*(300/count))
  }
}

可以依照上方陣列設定,變化要使用 ColorList 或是 ColorList2 執行效果。另外,設定畫布的大小可以讓畫面更接近畫作感覺。

再來進入到使用變數紀錄狀態,如同一個簡化邏輯的過程,就像是假如今天我們設置了三種不同筆刷效果供使用,要如何快速切換這些效果呢?最快的方式又是設定變數。設定筆刷 paintMode,並且設定筆刷在不同模式時的效果,像是隨機大小變化,就能使用 random 進行。

let colorList = "32373b-4a5859-f4d6cc-f4b860-c83e4d".split("-").map(clr=>"#"+clr)
let colorList2 = "0c090d-e01a4f-f15946-f9c22e-53b3cb".split("-").map(clr=>"#"+clr)
let colorList3 = "6622cc-a755c2-b07c9e-b59194-d2a1b8-fff".split("-").map(clr=>"#"+clr)
let colorList4 = "2f2d2e-41292c-792359-d72483-fd3e81-fff".split("-").map(clr=>"#"+clr)

let paintMode = 0

function setup() {
  createCanvas(1000,1000);
  background(0);
  paintMode = int(random(2))
  // print(colorList[2])
}

function draw() {
  // background(0)
  noStroke()
  if (paintMode==0){
    drawCircles(
      random(width),random(height),
      mouseX/5, 50, random([colorList, colorList2])
    )
  } else if (paintMode==1) {
    drawRects(
      random(width), random(height),
      mouseX/5, 50, random([colorList3, colorList4])
    )
  }
}

function mousePressed(){
  paintMode++
  paintMode = paintMode %2
}

function drawCircles(posX, posY, span, count=50, useColorList){
  for(let i=0; i<count; i+=1){
    let colorIndex = int(i+random(10) )%useColorList.length
    fill(useColorList[colorIndex])
    circle(
      posX+i*span, posY,
      300 -i*(300/count)
    )
  }
}

function drawRects(posX, posY, span, count=50, useColorList){
  for (let i=0; i<count; i+=1) {
    let colorIndex = int(i+random(10) )%useColorList.length
    fill(useColorList[colorIndex])
    rectMode(CENTER)
    rect(
      posX+i*span, posY,
      300 -i*(300/count)
    )
  }
}

應用重複的紋理與符號,改變物件的大小、方向或是出現的頻率,讓畫面的效果更加豐富。

第二階段 – 色彩

在 p5.js 裡常用的色彩系統有 RGB 與 HSB 兩種,RGB 是指顏色紅、綠與藍三種交疊後產生,HSB 是由色相、飽和度與亮度三種維度組成。應用 p5.js 的 Color 物件,並填上以像是一個數值、RGB 數值、填入顏色名稱、填入色票號碼或是宣告色彩模式後再填入色碼等多樣化模式都是得以進行的。了解基本設定後,就來進行實際操作示範吧!

先由圖形的顏色變化開始,假設要執行顏色漸變的圓形,可以先設定色彩系統 HSB,再依據可能使用滑鼠讓顏色變化的技巧,進而使顏色做漸進變化。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  colorMode(HSB)
  fill(mouseX%360, 100, 100)
  ellipse(mouseX, mouseY, 500, 500);
}

多方應用 random,使其在顏色或是圖形大小變化上,都能做一定範圍的控制。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  colorMode(HSB)
  noStroke()
  fill(random(0,50), random(50,100), 100)
  circle(mouseX, mouseY, random(300));
}

或者是控制在特定範圍內,即使是 random 的效果也能控制想要的方向,像是指定色相的呈現,顏色偏移在指定的範圍內。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  colorMode(HSB)
  noStroke()
  let startHue =random(0,150)
  for(let i=0; i<20; i++) {
    fill(startHue+i*5, random(50,100), 100)
    circle(mouseX+i*20, mouseY, 200-i*10);
  }
}

另一種常見的顏色使用功能為  blendMode(模式名稱),與 p5.js 當中的疊色模式十分相近, 帶入不同的模式時,疊色的效果就會不同,以 SCREEN 作範例,就是亮系的疊色效果,老闆調整變化速度與圓形顆數示範疊色效果,過程中也可以應由透明度的調整變化出不同的感覺。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
  frameRate(5)
}

function draw() {
  colorMode(HSB)
  noStroke()
  let startHue = random(0, 150)
  blendMode(SCREEN)
  for(let i=0; i<1; i++){
    let currentStartHue = (startHue+mouseY)%360
    fill(currentStartHue+i*5, random(50,100), 100,1)
    circle(mouseX+i*100, mouseY, 200-i*10);
  }
}

或是設定變數,讓在不同位置圖形有指定的顏色,限制色相的範圍,使其在生成時顏色為固定效果,以及加上填塞與單純線條的選項,讓作品更多活潑變化性。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
  // frameRate(5)
}

function draw() {
  colorMode(HSB)
  noStroke()
  let startHue = random(0, 150)
  blendMode(SCREEN)
  // blendMode(MULTIPLY)
  for(let i=0;i<1;i++){
    let xx = random(width) + i*100
        yy= random(height)
    let currentStartHue = (startHue+yy/3)%360
    if(random()<0.5){
      fill(currentStartHue+i*5, random(50,100), 100, 1)
      noStroke()
    } else {
      stroke(currentStartHue+i*5, random(50,100), 100, 1)
      noFill()
    }
    circle(xx, yy, random(100));
  }
}

但,當在使用重複多種圖案顯示時,有些圖案並沒有顯示在畫布上,這時可以應用 pushpop,將圖案分別開來指定顯示,或是設定 frameRate() 圖形的出現速度變化,控制一秒中出現幾次。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
  // frameRate(5)
}

function keyPressed(){
  if (key == "1") {
    frameRate(5)
  } else if (key == "2") {
    frameRate(30)
  } else if (key == "3") {
    frameRate(60)
  } else if (key == "4") {
    frameRate(120)
  }
}

function draw() {
  colorMode(RGB)
  background(0, 1)
  push()
    colorMode(HSB)
    noStroke()
    blendMode(SCREEN)
    let startHue =random(0,50)
    // blendMode(MULTIPLY)
    for(let i=0; i<1; i++){
      let xx = random(width)+i*100
          yy = random(height)
      let currentStartHue = (startHue+yy/3)%360
      if (random() < 0.5) {
        fill(currentStartHue+i*5, random(50,100), 100, 1)
        noStroke()
      } else {
        stroke(currentStartHue+i*5, random(50,100), 100, 1)
        noFill()
      }
      circle(xx,yy, random(50,500));
    }
  pop()
}

練習過程中,老闆也陸續回答同學的發問。

【問題一】請問 Openprocessing Editor 與 p5.js Editor 有所不同嗎?

回答:其實兩者的使用方式以及效能都是大同小異的,皆為提供給創作者即時顯示創作效果的地方。但老闆覺得在整體設計上,openprocessing 對於初學者來說較友善且容易上手,但每個人的感受不一,建議同學都能去嘗試看看自己比較適合哪種應用介面。

【問題二】請問 p5.js 可以做到如同 Team Lab 或是 梵谷光影展那樣的內容嗎?

回答:可以,但在 3D 的呈現上,因為函式庫的內容還沒有那麼完整,在建構呈現效果以及動畫上可能就需要再多加著墨。

【問題三】請問對於每行的分號加或不加有什麼心得或是建議嗎?

回答:老闆在撰寫時習慣不加,因為在 p5.js 撰寫時目前並不是必要的。但是如果同學們擔心的話,在網路上也都能找到自動幫你每行加分號的執行系統。

【問題四】請問 p5.js 有類似於 opencv 進行影像處理或是人臉辨識的進階 library 嗎?

回答:有的,但並不是官方的,名為 ml5.js。在臉部的輪廓或是 pixel 的呈現都是能夠抓到的。

臉部輪廓抓取 api 介面。(截圖自 ml5.js

老闆也有執行過類似的創作,抓取人體位置輪廓,以線條和原點呈現。(作品參考)也建議如果有更多函示庫應用的相關資訊想了解,可以觀看上一篇的影片介紹喔!(影片連結

第三階段 – 畫布操作

此階段以畫布操作,使同學在畫複雜圖形的時候,能以簡化的方式執行,快速且不需多樣計算,重點即是如何簡化繪製圖形的位置。

老闆先以要在畫布上畫多重圓形為舉例,比較先前執行與畫布操作後的差別。這邊應用到三角函數,因為需要使用三角函數去執行圖形的位置,而三角函數的取得需要使用到角度 degree() 功能去計算,今天以要得到一個在中心點圓形的45度角運行的圓形,以下為設定了角度、半徑與函數設定的程式。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  background(0)
  fill(255)
  let r = 200
  let ang =frameCount/100
  let x = r*cos(ang)
  let y = r*sin(ang)
  ellipse(width/2, height/2, 100)
  ellipse(width/2+x, height/2+y, 100)
}

此類的設定模式,會出現兩種問題。一,算式偏多,應該可以更加簡化。二,如果要畫多個圓形,就必須重複設定位置。對於這一系列的設定,老闆先解釋角度後說明如何應用,為何畫布的操作便是可以簡化此應用的方式。

我們在操作時,是為了要設定以一個圖形為中心,並依據此的某個特定角度,使用 translate() 進行其他圖形繪製。假設,今天要為中心的圖形,其實位在左上角,那我們移轉畫布就是讓左上角的圖形變成畫布的中央。(移轉畫布解說參考

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  background(0)
  fill(255)
  let r = 300
  let ang = frameCount/100
  let x = r*cos(ang)
  let y = r*sin(ang)
  translate(mouseX, mouseY)
  ellipse(0, 0, 100)
  ellipse(x, y, 400)
  ellipse(x, y, 300)
  ellipse(x, y, 200)
  ellipse(x, y, 100)
  fill(255, 0, 0)
  rect(200, 200, 50, 50)
}

此種移轉畫布的方式,讓圖形的總體位置相關參數,並不會跟程式邏輯混和在一起。偏移結束後,接下來進到畫布的旋轉。當設定為同種圖形在不同的位置重複出現,使用 translate()roatate() 繪製出多個圖形。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  fill(255)
  let r = 300
  let ang = frameCount/100
  translate(width/2, height/2)
  rotate(ang)
  translate(200, 0)
  rect(0, 0, 200, 200)
  fill(255, 0, 0)
  rect(200, 200, 50, 50)
}

嘗試多做點不同變化,像是旋轉的幅度以及圖形的大小隨著旋轉有所改變。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  push()
    fill(255)
    let r = 300-frameCount/2
    let ang = frameCount/100
    let currentScale = 1-frameCount/500
    translate(width/2, height/2)
    scale(currentScale)
    rotate(ang)
    translate(r, 0)
    rect(0, 0, 200, 200)
  pop()
}

在顏色排列上,除了 random 的隨機排列外,也能使用 noise() 執行持續性的變化。什麼是 noise?其代表著連續性的、可預測性的亂數。依據 perlin 噪聲圖上的不同位置,去影響每個設定,給相同點的時候,出來的結果會是一樣的,所以就可以嘗試將 random 更換成 noise 去觀察結果的差異。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  noStroke()
  push()
    colorMode(HSB)
    fill(noise(frameCount/50)*100,100,100)
    let r = 300-frameCount/2
    let ang = frameCount/20
    let currentScale = 1 - frameCount/500 + random(0.1, 0.5)
    translate(width/2, height/2)
    scale(currentScale)
    rotate(ang)
    translate(r, 0)
    rect(0, 0, 200, 200)
  pop()
}

將 noise 與 random 共同使用,讓顏色變化不死板,有多樣性的呈現。使用 hue 色相作為變數,去引導顏色為漸進的變化。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  push()
    colorMode(HSB)
    let useHue = (random(20)+noise(frameCount/50)*100 +frameCount/3)%360
    fill(useHue,100,100)
    let r = 300-frameCount/2
    let ang = frameCount/20
    let currentScale = 1 -frameCount/500 +random(0.1,0.2) +noise(frameCount/50)/2
    translate(width/2, height/2)
    scale(currentScale)
    rotate(ang)
    translate(r, 0)
    rect(0, 0, 200, 200)
  pop()
}

今日工作坊總結

在本次工作坊的下篇,我們總共學習到了三大項主題,包含迴圈的建構創作規則、變化色彩使用與留下痕跡,到最後的進階應用,畫布的變化。上述這些應用,老闆在做後一個示範中,將上述的功能都包含在裡面。由左到右畫一連串的長方形,這些動作是可以被累加的。先使用 translate 將圖形重複的執行,再設定變數以及出現的規則,將圖形依照滑鼠位置做變化。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(100);
}

function draw() {
  // ellipse(mouseX, mouseY, 20, 20);
  fill(random(255), 200, 200)
  translate(0, height/2)
  rectMode(CENTER)
  for(let i=0; i<50; i++){
    rotate(map(mouseY, 0, height, -0.5, 0.5))
    translate(50,0)
    scale(0.95)
    rect(0, 0, 500, 500)
  }
}

雖然快接近結束,同學們還是十分積極的詢問問題,像是,如果想針對資料進行統計的視覺化呈現,有什麼相關的 lib 或 sample 可以參考嗎?比如地區人口統計?針對此問題,老闆認為目前 p5.js 的函數庫在視覺化上的效果還不是最齊全的,可能會使用其他函數庫如 D3.js 來進行,但也還是可以參考先前的相關課程,針對 p5.js 在視覺化上的 api 應用教學,來嘗試進行(參考文章)。

最後,也不免俗的與同學分享老闆在互動藝術程式創作的其他課程,或是影片推廣給大家。包含 Creative Coding TW – 互動程式創作台灣站 的專業文章網站、社群軟體上的即時資訊分享 老闆 來點寇汀吧。 Boss,CODING please(臉書)、老闆來點寇汀吧 Boss, Coding Please (臉書)、老闆,來點寇汀吧。Boss, CODING please(Youtube 頻道),當然還有想要進一步正式開始創作,歡迎加入 互動藝術程式創作入門課程 開始學習!本次分享就到這邊結束啦,歡迎各位一起踏入程式與藝術交織的世界!

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

這篇文章 p5 js互動藝術程式創作 – 初階應用實戰教學!(下篇) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
p5 js互動藝術程式創作 – 初階應用實戰教學!(上篇) https://creativecoding.in/2022/12/26/p5js-workshop-clab-1/ Mon, 26 Dec 2022 03:10:20 +0000 https://creativecoding.in/?p=3403 老闆在第一屆「Processing 臺灣國際社群日」活動中,受邀為設計工作坊擔任講師,針對 p5.js互動藝術程式創作入門的主題進行為期兩天的分享。分別為第一天的基礎練習與第二天的生程式藝術實作,本篇文章內容為第一天的基礎練習,希望能讓同學能學習到以基礎的方式,將創意想像以自己的技術實現!

這篇文章 p5 js互動藝術程式創作 – 初階應用實戰教學!(上篇) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
臺灣當代文化實驗場 C-LAB 在 2021 年 10 月 19 日到 10 月 24 日,邀請全球 Processing 使用者共同參與台灣第一屆「Processing 臺灣國際社群日」,活動中集結不同藝術家及設計師的觀點,帶領大眾以多方的視角,進入未來新媒體藝術的全新想像。
老闆在這一連串的活動中,除了擔任對談講者外(參考文章:創意程式設計:Processing/p5.js教學與趨勢觀察——王連晟、吳哲宇台美連線對談),也受邀為設計工作坊擔任講師,此次就是針對 p5.js互動藝術程式創作入門的主題進行為期兩天的分享,分別為第一天的基礎練習與第二天的生程式藝術實作,希望在兩天的時間內,讓同學能學習到生程式藝術創作的基礎,將自己的創意想像以技術實作呈現!在工作坊正式開始前,老闆提供下列素材讓學員進行課前準備,包含:p5.js 的簡短介紹與 Hahow 上的課程示範。
工作坊會從 p5.js 及工具介紹、p5.js 的開發入門、基本圖形繪製以及變數解說循序漸進。本篇文章為第一天的基礎練習介紹,影片總長兩個小時左右,那我們就事不宜遲開始進入工作坊啦!

什麼是 Creative Coding ?

老闆以自我介紹作為開場,從大學的電機工程背景到至紐約就讀新媒體藝術相關研究所,而開始了一連串生成式藝術的創作。對於什麼是 Creative Coding,老闆說自己也是在不太理解的狀況下開始的。是用程式創作藝術嗎?那到底程式創作出來的藝術,最終呈現出來的效果又會是如何呢?這些想必是初接觸生成式藝術的人都會提出的問題,因此老闆提供不同的案例分享,作為答覆的參考。
以目前最常看到的例子來說,視覺上,像是原先平面的設計海報以動態的方式呈現增加趣味性,或是模擬傳統媒材(如水墨)等等,藉由制定規則,讓每一次不同互動產生出的作品都有不一樣的呈現,這使每件藝術作品都是獨一無二的,並且在參與度提升的情況下,成為對互動者更特別的藝術存在。還有在互動網頁上,像是老闆成立的墨雨設計工作室,就有許多應用相關技術的案例,例如與台灣啤酒合作的網頁。或是現在有很多網頁,當使用者將滑鼠向下移動要瀏覽整個畫面時,也會有不同的動畫呈現,整個瀏覽體驗就像是一個完整的時間軸。

老闆作品 Slient Sky 參考範例(連結
台灣啤酒合作案例(連結

在執行設計創作時,老闆也提出自己建立規則的範例供大家參考,以下方作品為例,從中心散開的圓心,其規則並不複雜。先從中心設定隨機數量生成的粒子,粒子會向外成長擴散,並且在成長的過程中變化成不同的顏色,以及施加不同的外力讓粒子扭曲變形。綜合以上這四項變化,就會得出每一次都是不同作品的互動創作。

視覺平面的傳達外,像是在美術館裡的互動式藝術,或是在街角呈現的大型裝置都是可以用 p5.js 執行的。

p5.js 是什麼?

Processing 為一種開源式語言,最初的目的是希望能以視覺化的方式幫助非程式設計師學習程式語言,而後演變成在電子藝術或是互動式設計上都能發現其應用。

Processing 網站首頁(截圖自 Processing網站首頁 )

p5.js 即是將 Processing 以 Javascript 語言做應用,使其可在網路瀏覽器上進行直接創作,免費及開放資料庫的特性,降低了學習門檻,也吸引更多人進入創意程式設計 Creative Coding 的領域。

p5.js 網站首頁(截圖自 p5.js網站首頁 )

此外還有像是 MaxMSP,以 Creative Coding 應用在音樂的視覺化效果呈現。其撰寫時與 p5.js 的不同在於,p5.js 一般會以程式碼顯示,但在 MaxMsp 是直接以視覺的圖案的方式做呈現,將語言命令可視化成一塊一塊的圖形,再依位置的安排去創造不一樣的效果,串接出不同的流程,進行音樂的創作。

MaxMSP 網站首頁(取自 MaxMSP 網站首頁)

或是 Sonic Pi,一個使用文字創作音樂的程式設計環境。

Sonic Pi 網站首頁(取自 Sonic Pi 網站首頁 )

Touch Designer 也屬於視覺為基礎的語言,採用圖形化的介面讓使用者創作,實現多媒體特效。

Touch Designer 網站首頁(取自 Touch Designer 網站首頁 )

不同的創作環境,都說明 Creative Coding 不論在視覺上,或是聲音上都有豐富的創作應用。而 p5.js 代表的就是更全面的環境,整合了從3D 到聲音都有涵蓋。有同學提到,既然 p5.js 與 processing 的語法幾乎相容,那還有什麼狀況下選擇 processing 比較好呢?老闆回覆是,其實 processing 在過去的優勢現在幾乎在 p5.js 上都能進行,建議剛入門的同學以好操作上手的 p5.js 開始創作入門,這樣到後面如果真的因為 pocessing 可能在效能上有相對好一些,要回 processing 上進行創作,入門時間也不會過長。歸類以下 p5.js 的優勢,簡化了語法、減少了無程式背景的創作者的入門門檻,以及多項資料庫可使用或是簡易自行建立。

課程開始

首先,登入進 Openprocessing ,直接點擊連結便能開始以創作,或是也可以追蹤老闆及不同的創作者,參考不同的創作概念。進入個人創作頁面後,便能看到 「Create a Sketch」,每建立一個 Sketch 都代表一個新的創作實驗場域。點進去創作介面會先看到基本程式裡分成兩個部分, setupdraw 兩種不同的規則。前者代表在畫布上的設定,一次性的準備動作,後者代表不斷在畫布上重複著畫上新東西的動作。在創作同時,我們也可以在右方欄位做 layout 設定,讓撰寫程式同時在同一版面看到作品呈現。

一、顏色置換示範

推薦使用 Chrome 瀏覽器的擴充功能「 ColorZilla 」,可以挑選想要的顏色並顯示出色票。當有了色票後,便可以依上色元素不同,在 background() 或是 fill() 做更換。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255,181,35);
}

function draw() {
  circle(mouseX, mouseY, 20);
}

二、繪製圖形、圖形大小與顏色變更示範(筆刷變換)

基本變化像是圖形中不填色 noFill()、去掉圖形外框 noStroke(),或是 strokwWeight() 控制線框粗細。另外,當你要暫停一行程式的功能,請按下 ctrl 鍵與 /,就能看到反灰並且暫停功能的程式碼。

基本圖形控制可以至 p5.js 資料庫中做多方搜尋,以本次範例橢圓形 ellipse 為主,有設定位置是特別指定或是以滑鼠 mouseX、mouseY 代替,與形狀的長寬大小設定。

進入到顏色階段,針對圖形總共有兩個部份可以做編輯,分別是圖形內的顏色以及圖形線條的顏色。fill() 提供圖形內的顏色,stroke()則負責圖形線條的顏色,在顏色選取上,能使用顏色色票、顏色名稱(請記得要加上雙引號,例如:”white”)以及 rgb 顏色(CSS Colors 網站)。如果想要使顏色變化根據互動而有更多元的呈現,可以將 rgb 色彩數值的其中一項更改成滑鼠位置,如此一來顏色就會根據滑鼠位置變化。

滑鼠效果除移動中會有變化外,在 if 與 else 間進行條件判斷,就能在不同情況中呈現不同的效果,下方是根據滑鼠點按之間的效果呈現。

function draw() {
  noStroke()
  fill(mouseX/4,mouseY/4,200)
  print(mouseX/4)
  
  if (mouseIsPressed){
    ellipse(mouseX, mouseY, 100,100);
  } else {
    stroke(255)
    noFill()
    rect(mouseX,mouseY,100,100)
  }
}

如果要增加顏色多樣性,可以使用 random() 設定需要的顏色數值,讓不同的顏色隨機出現。也另外補充個撰寫程式小技巧,可以在選擇多組相同數字時,使用 ctrl 鍵加上 D 進行多重選取減少時間。

上述是由變換位置去進行筆刷的變更,那藉由時間來變更的部分,可以使用 frameCount() 編輯。以示範案例中的橢圓形舉例來說,假使要讓橢圓形的 Y 軸隨著時間來進行筆刷大小的變化,在相對應的 Y 軸編輯位置放上 frameCount 就可以執行,下方範例:

function draw() {
  noStroke()
  fill(mouseX/4,mouseY/4,200)
  print(mouseX/4)

  if (mouseIsPressed){
    ellipse(mouseX, mouseY, 200,frameCount);
  } else {
    stroke(255)
    noFill()
    rectMode(CENTER)
    rect(mouseX,mouseY,100,100)
  }
}

講解到這邊後,老闆給線上參與的同學進行五分鐘的線上練習,題目為:請設計出一個隨機大小為 0 到 50 的圓形筆刷。與此同時,也開放同學在製作時有任何問題能當場詢問,以下問題歡迎參考。

【問題一】 在 p5.js 裡面是否有質感筆刷可以應用或是製作呢?像是油漆筆刷般的效果?

答:有許多方式可以處理,目前先以比較便捷的方式做示範。

先至網路上找尋相關筆刷的圖樣,比如此次是製作油漆類型,那就搜尋相近的筆刷圖案,先下載圖片。

下載其中自己比較喜歡的圖片後,再至 openprocessing 的編輯頁面上傳圖片檔案。便可以開 使進行質感操作啦!

首先,需要先使用 var 宣告與 preload function 預載入,設置質感 texture 的出現,再設置筆刷 image 進行應用,程式與呈現效果如下:

var paintTexture

function preload(){
  paintTexture =loadImage("20190806101953yop172.png")
}

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0);
}

function draw() {
  noStroke()
  fill(mouseX/4,mouseY/4,200)
  print(mouseX/4)

  if (mouseIsPressed){
    image(paintTexture,mouseX,mouseY)
  } else {
    stroke(255)
    noFill()
    rectMode(CENTER)
    rect(mouseX,mouseY,100,100)
  }
}

【問題二】 如何分享在 openprocessing 上創作的作品呢?

答:將作品儲存後,可以使用網址直接分享,或是 openprocessing 作品頁面右上方的分享功能都可以使用喔。

接下來進到線上練習的分享時間!

經過五分鐘的練習後,同學應用上述老闆教學說明到的不同應用進行創作。老闆也針對這些分享作品提供不同的建議,或是告訴其他同學這些不同創作是怎麼製作的。例如,有作品使用透明度進行不同編輯,在這邊透明度的應用分為兩種:第一種為圖案顏色的透明度,此類型是在圖案填色 fill() 中,除了 rgb 填寫外,再增加一個代表透明度的數字,變成 rgba 模式,例如 fill(255,255,255,10),代表顏色具有百分之十的透明度;另一種情況為圖形筆刷在背景留下軌跡與否,在 background() 編輯時,設定透明度,讓背景每秒覆蓋的顏色不會完全覆蓋,如此一來就能留下上一秒的軌跡。

三、變數解說教學

進入應用變數的環節,老闆以設計橢圓形為範例說明。如何快速的將兩個不同的,寬與高等比例放大呢?我們需要設置一個櫃子來存放將這些即將運用到的變數,在需要的時候就可以快速抓取使用。運用 let 設置存放變數,舉例來說 let r=50,後續在 draw 裡面,使用到 r 時程式就會帶入 50 這個數字。

延伸閱讀:使用 let / var / const 宣告變數的差異(鐵人賽:ES6 開始的新生活 let, const – 卡斯伯

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(0,50);
}

function draw() {
  noStroke()
  fill(mouseX/4,mouseY/4,200)
  print(mouseX/4)

  if (mouseIsPressed){
    let r =random(50,200)
    ellipse(mouseX, mouseY, r,r);
    image(paintTexture,mouseX,mouseY)
  } else {
    stroke(255)
    noFill()
    rectMode(CENTER)
    rect(mouseX,mouseY,100,100)
  }
}

四、如何繪製形狀

經由上述的基礎設定、顏色選填、變數教學與基本圖形設定解說後,接下來進行繪製形狀的教學。形狀除了先前提到的圓形或是方形外,還有線條、3/4圓甚至是自己建立的幾何形狀,其實都能應用函數的變化去繪製出。老闆先以線條舉例,函數式為 line(x1,y1,x2,y2),分別代表連成線條的不同點,而線條粗細是以 strokeWeight() 調整。那該如何得知座標呢?可以先透過設定文字,顯示出滑鼠在座標上移動的位置。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(255)
  strokeWeight(10)
  line(0,0,500,500)
  textSize(50)
  text(mouseX + "," + mouseY,mouseX,mouseY)
}

老闆也提到如何將圖案融合文字,呈現對話框般的資訊。先設定隨機出現的長方形,並且將方角設定成圓角,使其更具有對話框的設定。並且運用變數設定,使文字與對話框在相同的設定下隨機出現。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  // background(255)
  strokeWeight(2)
  let x = random(width)
      y = random(height)
  rect(x,y,200,50,20)
  text("HELLO",x,y+40)
}

或者使用特定條件呈現更多層次的畫面,像是加上 if()else() 的二元判斷變化,以及使用變數讓不同文字隨機出現在不同的對話框中。執行的同時,老闆也會分享一些在寫程式的快捷鍵和小撇步,假使不想只選取單一文字,而是同行多個文字選取,可以根據游標位置,按下 alt 和 shift 再向右或左按下鍵盤左右鍵。或是因為撰寫項目太多,想要進行分類時,可以將無效文字標註在程式中,以防後續搞混或忘記。另外,建立好形狀繪製的基礎後,我們也可以控制出現的頻率。其中一種作法是使用  frameRate() 來編輯。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
  frameRate(10)
}

function draw() {
  strokeWeight(2)
  let x = random(width)
      y = random(height)

  if(random()<0.5){
    fill(0)
    rect(x,y,200,50,20)
    textSize(30)
    fill(255)
    text(random(["HELLO","TAIWAN","CREATIVE","CODING"]),x+20,y+40)
  } else {
    // 指定文字大小
    textSize(30)
    // 白底黑字
    let myText = random(["CLAB","TAIWAN","當代文化實驗場"])
    let w=textWidth(myText)
    // 對話框
    fill(255)
    rect(x,y,200,50,20)
    fill(0)
    // 文字
    text(myText,x+20,y+40)
  }
}

瞭解圖形與文字的隨機變化後,繼續進行隨機自行繪製形狀的產生,從三角形隨機顏色變化為例,以及自行繪製的圖形示範。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  strokeWeight(random(5))
  line(0,0,width/2,height/2)
  fill(random(0,255),random(0,255),200)
  triangle(0,0,width/2,height/2,mouseX,mouseY)
}

在隨機變化中,老闆習慣設定顏色要在哪種色調或色系間做變化進行設計,建議以這樣的方式進行才比較不易出現奇怪的顏色配置問題。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  strokeWeight(random(5))
  line(0,0,width/2,height/2)
  fill(random(50,100),random(100,150),200)
  triangle(0,0,width/2,height/2,mouseX,mouseY)
}

再來是自行繪製的圖形解說,老闆以王冠繪製示範。多邊形製作需要使用到 beginShape() 代表起始位置、 endShape() 代表終點位置,以及 vertex() 說明在起始與終點間會經過哪些點。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(255)
  // strokeWeight(random(5))
  strokeWeight(50)
  line(0,0,width/2,height/2)
  // line(width/2,height/2,mouseX,mouseY)
  // ellipse(mouseX, mouseY, 20,20);
  fill(random(50,100),random(100,150),200)
  // triangle(0,0,width/2,height/2,mouseX,mouseY)
  fill("#ffcc00")
  beginShape()
    vertex(200,200)
    vertex(200,600)
    vertex(750,600)
    vertex(750,200)
    vertex(600,350)
    vertex(470,200)
    vertex(350,350)
  endShape(CLOSE)
  // 繪製座標文字
  fill("red")
  textSize(50)
  text(mouseX+","+mouseY,mouseX,mouseY)
  // circle(mouseX, mouseY, 20);
}

或者是互動式的顏色應用,將王冠與背景顏色或是新增圖形都設定成會依據滑鼠位置的改變進行更動。使用預設函數改變線條粗細、圖案顏色或是模式等等,去繪製圓形、方形,甚至是多邊形,提供作者與互動者不同的作品溝通模式。像是在皇冠上,老闆進行顏色、大小和特別圖形繪製,主要是根據 mouseXmouseY 的設定。

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(mouseX/4,100,50)
  strokeWeight(20)
  line(0,0,width/2,height/2)
  fill(random(50,100),random(100,150),200)
  fill(255,mouseX/4,0)
  beginShape()
    vertex(200,200)
    vertex(200,600)
    vertex(750,600)
    vertex(750,200)
    vertex(600,350)
    vertex(470+mouseX/10,200)
    vertex(350,350)
  endShape(CLOSE)
  fill(255)
  circle(325,480,100)
  circle(600,480,100+mouseY/10)
  fill(0)
  circle(325,480,30)
  circle(600,480,30)
  line(355,550,500,520+mouseX/30)
  // 繪製座標文字
  fill(255)
  textSize(50)
  text(mouseX + "," + mouseY, mouseX, mouseY)
}

說明完後,又進到同學們自行練習的時間啦!

那也一樣,老闆趁這時解答同學問題。其中有一位同學詢問到霓虹效果該如何呈現,老闆拿出先前做過類似效果的作品說明示範(作品連結)。主要使用 shadow() 的疊色模式編輯,總體上分為兩個步驟,一個是指定形狀中的顏色,另一個是周圍光暈的 dawingContext() 代表周圍光暈的顏色及顯現的程度。老闆以王冠作品進行實際操作, 將王冠周圍的光暈,從最原始的單一色調設定,進階到隨滑鼠移動改變顏色,到最後是點按滑鼠鍵進行光暈顏色變更,多樣化的創作方式供同學參考。

var lightColor

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(255);
}

function draw() {
  background(mouseX/4,100,50)
  strokeWeight(20)
  line(0,0,width/2,height/2)
  fill(random(50,100),random(100,150),200)

  // 畫王冠
  fill(255,mouseX/4,0)
  drawingContext.shadowColor = lightColor;
  drawingContext.shadowBlur =30;
  beginShape()
    vertex(200,200)
    vertex(200,600)
    vertex(750,600)
    vertex(750,200)
    vertex(600,350)
    vertex(470+mouseX/10,200)
    vertex(350,350)
  endShape(CLOSE)

  // 畫眼睛
  fill(255)
  circle(325,480,100)
  circle(600,480,100+mouseY/10)
  fill(0)
  circle(325,480,30)
  circle(600,480,30)
  line(355,550,500,520+mouseX/30)

  // 繪製座標文字
  fill(255)
  textSize(50)
  text(mouseX + "," + mouseY, mouseX, mouseY)
  // circle(mouseX, mouseY, 20);
}

function mousePressed(){
  lightColor = color(
    random([
      "#F2C400",
      "#F9C784",
      "#8AE1FC"
    ])
  )
}

後續特別應用,例如想讓 frameCount() 的變化是限制在特定範圍內,可以使用 constrain() 限制 frameCount() 隨機大小的範圍,或是使用 sincos 設計。 sin 是指一到負一之間,譬如 sin(frameCount)*50+200,就可以得知是設定在 負五十加兩百到五十加兩百間。或是,如果要設定由左到右的數字大小,使用 map 設定初始範圍與限制範圍進行有意識的創作編輯。

結語

主要實作教學結束後,老闆也傳授了一些編輯使用上的常用事項。例如,openprocessing 封面設定是可以藉由上方編輯按鈕,進行特定封面圖案設定,如下圖圖示,在此頁面按下右上方 edit 編輯即可操作。

也有同學問到,mouse 的座標是否可以連結到外部感應器的點位?可以開啟編輯頁面左方需連結感應的部分,但如何應用就需要後面再多做分享。或是,想要將 p5.js 的效果呈現在網頁上,能如何操作呢?老闆說明,最快的方式是使用 iframe,先至分享區域做壓縮檔下載、直接使用 emded code 程式碼崁入,或是使用 GitHub 將程式碼轉換成 index 檔案後,到 setting 的 GitHub Pages 上傳,並且再選擇 main 之後存檔,就能藉由網址看到自己已 p5.js 創作的互動程式以網頁的方式呈現。上述精彩的課程教學以及同學們多樣化的提問,就是說明互動藝術程式創作還有好多應用面向可以進行探討,與繼續發掘它不同的樣貌。閱讀到這邊,相信你也對互動藝術程式抱持著更高的興趣了吧!就讓我們接續收看下半場的精彩解說吧!

但,如果你已經迫不急待要開始進行創作,加入 互動藝術程式創作入門課程 開始進行深度學習吧!還有不要忘了 追蹤老闆 Twitter 和訂閱 老闆,來點寇汀吧。Boss, CODING please. Youtube頻道隨時補充新媒體藝術的養份,讓我們一起探索這多元的世界!

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

這篇文章 p5 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 - 互動程式創作台灣站

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

]]>
基礎JS物件導向教學(直播筆記) https://creativecoding.in/2021/03/31/js%e7%89%a9%e4%bb%b6%e5%b0%8e%e5%90%91%e9%9a%a8%e6%84%8f%e5%af%ab%e7%a8%8b%e5%bc%8f%e7%9b%b4%e6%92%ad%e7%ad%86%e8%a8%98/ Wed, 31 Mar 2021 10:35:00 +0000 https://creativecoding.in/?p=526 這篇文章老闆想跟大家分享物件導向,以及如何使用 js 結合物件導向概念包裝東西,創造並管理物件。 本文翻自 0212 隨意寫程式直播 / 物件導向,若是對文章內容有疑問,或是想要跟著影片一起做,都可以…

這篇文章 基礎JS物件導向教學(直播筆記) 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
這篇文章老闆想跟大家分享物件導向,以及如何使用 js 結合物件導向概念包裝東西,創造並管理物件。

本文翻自 0212 隨意寫程式直播 / 物件導向,若是對文章內容有疑問,或是想要跟著影片一起做,都可以點入觀看影片詳細內容。

工商時間:老闆亂入在這打個小廣告 – 動畫互動網頁特效入門(JS/CANVAS)。網頁技術博大精深,學也學不完,老闆很開心不少同學在上過老師的課程後,成功加入 coder 的一員。 其他平台在對於 js 的課程教學內容上,已經很完善。但這門課不一樣的地方是,老闆會在課程中穿插不同的範例(例如摩斯密碼…等),有興趣的也可以支持一下。

目標

  1. 物件導向是什麼
  2. 使用 js 結合物件導向概念包裝東西

為什麼需要物件導向?

這邊我們舉個飛機射擊打怪獸遊戲作為例子。在這個遊戲中,操作者按下射擊會射出子彈,按鍵移動飛機…等功能,在監聽到前述事件後,需要不斷地增加子彈,或是針對飛機進行畫面位置改變,若只是規則簡易的小遊戲,這些都還能硬寫完成。但是當遊戲複雜度增加、要控制的物件增加時,就會讓製作過程非常痛苦。

再舉一個例子,假設今天 人物A 要將 蘋果 交給 人物B 的過程,若是不看畫面,在程式碼內應該要看到三個物件(人物A 、人物B、蘋果),程式碼就會像下面這樣:

A.給(B, 蘋果)

我們把非常繁雜的物件、屬性包成一個抽象的概念,讓他們之間去做互動,管理物件的方式,讓大家在寫程式的時候,不會被底層的內容給干擾。需要使用物件導向的原因,就是因為我們需要一個抽象的介面來處理這些事情。希望能夠透過物件導向,將程式碼簡潔,也不會讓遊戲分數或是各種邏輯條件的程式碼四散各處。

接下來我們來一起寫一些程式,加深對物件導向的理解。

邁向物件導向

要什麼就宣告什麼

我們先來建立一個會打招呼的人,他的名字叫Frank:

var person_name = 'Frank'
var personSpeak = function (name) {
  console.log('Hello, ' + name)
}
personSpeak(person_name) // Hello, Frank

這樣寫會發現一個狀況,人名和他的動作(Speak)是分開的,這樣對於管理並不友善,所以接下來我們嘗試將人名與說話的方法統一管理。

物件屬性統一管理

接著我們嘗試結合物件,將跟人有相關的屬性(名字、性別、年紀…等)包成物件。這種方式讓函式可以吃到人的所有屬性,也可以針對此來做更多的變化,我們試著把程式碼改動成:

var person = {
  name: 'Frank',
  gender: 'Male'
}
function personSpeak (person) {
  console.log('Hello, ' + person.name, person.gender)
}
personSpeak(person) // Hello, Frank, Male

將 function 也放進物件中

但是當程式越來越大,人有太多的屬性,每次呼叫 function 都要傳人這個參數進去,會覺得有點麻煩。這時候,我們開始思考,是不是要連名字也一起放進來,就不用在呼叫 function 的時候,還要傳人的屬性進去,所以我們把程式碼改動成:

var person = {
  name: 'Frank',
  gender: 'Male',
  speak: function () {
    console.log('Hi, I\'m ' + this.name + '!')
  }
}
person.speak() // Hi, I'm Frank!

這個概念可以延伸到我們在寫遊戲,要讓遊戲物件棋子往前,不需要呼叫往前並傳入棋子這個參數,而是針對這個棋子呼叫往前的 function。

類別定義、函數產生器

當只要產生一個人的時候,我們宣告物件很容易,但是當今天要產生五個、十個、甚至一百個的時候,我們不可能打一百個 var person98… var person99… var person100 吧?所以接下來將這種宣告方式改寫成物件產生器。我們將這種物件產生器稱為「類別定義(模板)」。現在不管要幾個人,我們就可以使用這種方式產出無限多人了!

🔔小叮嚀:類別為抽象概念,程式語言的習慣上也會將類別的首字大寫

var Person = function (name, gender) {
  this.name = name
  this.gender = gender
  this.speak = function () {
    console.log('Hi, I\'m ' + this.name + '!')
  }
}
var person1 = new Person ('Frank', 'Male')
var person2 = new Person ('Mick', 'Female')
person1.speak() // Hi, I'm Frank!
person2.speak() // Hi, I'm Mick!

抽出共同方法

現在我們使用 chrome 的開發者工具,檢查 person1, person2 的結構會發現。產生器所產出的每個物件,包進去的函式都是一個新的 function ,並不是共用的。

person1.speak = function () {
  console.log('Hello, I\'m ' + this.name + '!')
}
person1.speak() // Hello, I'm Frank!
person2.speak() // Hi, I'm Mick!

這時狀況來了,假設今天我們要改變某一個人說話這個函式的內容,就必須一個一個去修改。這樣的操作顯得有點不人性化。我們希望能夠在修改函式的時候,只需修改一次。所以我們可以試著將共同的函式抽出,只要是抽象定義共用的,我們就將它拉出來,而不是再複製一份。

我們針對人這個類別,將共同的函式抽出,改使用 prototype(原型,不會變動的,有點像是人的根源的概念)。

var Person = function (name, gender) {
  this.name = name
  this.gender = gender
}
// 將共同方法抽出
Person.prototype.speak = function () {
  console.log('Hi, I\'m ' + this.name + '!')
}
var person1 = new Person ('Frank', 'Male')
var person2 = new Person ('Mick', 'Female')

person1.speak() // Hi, I'm Frank!
person2.speak() // Hi, I'm Mick!

// 修改共同方法
Person.prototype.speak = function () {
  console.log('Hello, I\'m ' + this.name + '!')
}

//說話從Hi變成了Hello
person1.speak() // Hello, I'm Frank!
person2.speak() // Hello, I'm Mick!

這時候再使用開發者工具檢查 Person,會發現它的結構變成如下,只有兩個屬性,沒有打招呼的函式,理論上是無法說話,那為什麼 person1 還能說話呢? 發現他們參考到相同的源頭 _proto_

這個函數共用的概念,可以拿來做許多的應用,例如彈珠檯遊戲,每個球都有自己的位置和移動,我們就可以用這個概念去產出許多顆彈珠。

繼承 – 抽取出更抽象的類別

現在的物件產生器,不僅可以幫我們快速產出一個又一個的人,並且擁有一樣的函式。以為這樣就結束了嗎?太小看物件導向了。像是球球對打的遊戲中,球和兩個板子雖然完全不相干,但是我們可以發現,他們除了外型不同之外,其實有許多共同屬性與方法(例如x, y的座標、移動、碰撞),所以我們是不是可以依照需求,抽取出更抽象的類別。

js 內的繼承非常的麻煩,若是想要了解更詳細的內容,也可以參考動畫互動網頁特效入門(JS/CANVAS)課程。在課程內的範例,是以狗去繼承生物體的屬性。狗和人都是生物,都會有名字、生命長度…等基本屬性,但是狗自己又有品種、喜歡吃的東西等屬性和方法,這時候我們就可以執行繼承,讓狗除了自己特有的屬性外,還能夠繼承生物體本身的基本屬性。

剛剛我們已經製作好人的產生器,並且加了賦予他們說話能力的函式,現在我們多了一個新的類別 – 工作者WorkPerson,會有自己額外的屬性(自己的工作)。我們並不會想再把人的屬性寫一遍,額外多一筆工作的值,這種寫法非常地冗,該如何讓 WorkPerson 使用人的屬性呢?首先讓我們整理出這次的三個新名詞:

  1. 繼承:類別繼承到物件,產生的人都要能有這些方法, WorkPerson 要能回去參考 Person 說話的方法
  2. prototype:原型、不會動的
  3. _proto_:共同源頭

操作的步驟如下:

繼承者有原始的屬性可以使用 – call,WorkPerson 初始化時,要使用 Person 的屬性執行
我們把程式碼改寫成如下:

var Person = function (name, gender) {
  this.name = name
  this.gender = gender
}
Person.prototype.speak = function () {
  console.log('Hi, I\'m ' + this.name + '!')
}

// 錯誤寫法
var WorkPersonFalse = function (name, gender, work) {
  this.name = name
  this.gender = gender
  this.work = work
}

// 正確寫法 - 步驟1
var WorkPerson = function (name, gender, work) {
  Person.call(this, name, gender)
}
var person = new WorkPerson('Amy', 'Female', 'designer')

這時候我們可以從開發者工具看到 person 已經有 Person 宣告的屬性了。

但此時,我們發現 person 還不能參考到 Person 的方法 speak。所以我們接下來再做些處理

// 步驟 2 
var WorkPerson = function (name, gender, work) {
  Person.call(this, name, gender)
  this.work = work
}

// 錯誤寫法
WorkPerson.prototype = Person.prototype
// 正確寫法
WorkPerson.prototype = Object.create(Person.prototype)

WorkPerson.prototype.speakWork = function () {
  console.log('I\'m ' + this.work + '.')
}

var person = new WorkPerson('Amy', 'Female', 'designer')

那為什麼我們不能直接寫成 WorkPerson.prototype = Person.prototype 呢?

因為如果我們今天針對 WorkPerson.prototype.speak 去修改,也會連帶修改到 Person 中的 speak 方法,這並不是我們希望達到的效果,我們希望的只是 WorkPerson 參考 Person 的方法。

當用錯誤方式去宣告,造成 WorkPerson 有一個說自己工作的函式時, Person 也會有這個函式。

複寫

老闆在這邊也給大家一個小提示,今天當 WorkPerson 自己也有 speak 的函式時,它就會優先使用自己有的。當它沒有這個函式時,才會向它繼承的對象去找有沒有這個方法。

Person.prototype.speak = function () {
  console.log('I\'m ' + this.work + '.')
}

WorkPerson.prototype.speak = function () {
  console.log('Aloha.')
}

以上為物件導向的過程,大家可以一起跟著寫寫看,透過實際操作了解之中的差別。也因為現在工具越來越方便,比較少會去直接接觸到這些內容,若是大家想要對程式有更深的了解,我們建議大家還是可以去了解一下底層的概念。

動畫互動網頁特效入門(JS/CANVAS)課程裡面,分成三個章節在講物件導向:概念篇、繼承篇及實作篇,有更詳細的介紹,再完成一個小遊戲。

老闆來閒聊

另外老闆也跳出來和大家分享,在老闆進行網頁設計的過程,常用的一些軟體和工具。

網頁設計

老闆在製作遊戲前,都會先畫個草圖,來敘述遊戲配置和過關條件。製作網頁時,老闆會使用模板工具( Zeplin, Sketch, Illustrator.. )來做內部與外部的設計稿討論、tag 討論。 動畫互動網頁特效入門(JS/CANVAS)會和大家分享一些動態網頁的形式與實際應用,以及怎麼使用 Sketch 以及 Zeplin 來執行專案,有興趣想深入了解老闆怎麼利用這些工具進行設計,一樣可以支持一下老闆的課程。

後端 – firebase

老闆在這邊跟大家誠摯推薦 – firebase 。 firebase 可以做為後端資料庫使用外,也支援檔案上傳,取得 url 就可以直接將圖片引用到你的網站中。作品集若是使用 firebase 串的,若是在後台更改後,可以馬上在前端看到畫面上的變動。也可以利用 firebase 來製作權限功能,提供不同權限的人有不同功能。

案子分享 – 雜學校

這個案子屬於單頁式的 spa ,透過動態轉換,不會有換頁效果。也因為是單頁應用程式,所有狀態是同步的。現在的網站會把整個網站想像成一個應用程式,不像以往是一頁一頁區分的,好處是通用狀態不用存在後端 php session內,也不會有換頁必須要重整頁面的狀況。

這次的分享就到這邊,如果有甚麼問題,都歡迎私訊老闆 來點寇汀吧粉專,或是到我們的Instagram上看看最近都在玩些甚麼吧!

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

墨雨設計banner

訂閱 Creative Coding Taiwan 電子報:

這篇文章 基礎JS物件導向教學(直播筆記) 最早出現於 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 - 互動程式創作台灣站

]]>