HTML5 Canvas (III)

Maxwell Alexius
12 min readJan 24, 2017

--

Simple Water Ripple

Concept of Animation

In this third HTML5 Canvas tutorial series, this article is going to teach you how to create simple animation that can be implemented by Canvas! Animation, simply put, is a process that displaying still images (that is, the canvases) rapidly which creates an illusion of movement. (See this wiki for more information.)

在這第三篇的 HTML5 Canvas 教學系列,即將要教到的是藉由 Canvas 創作出一個簡單的動畫!簡單來說,動畫是一種快速將靜態的影像(這些影像即為 Canvas)顯示出來造成一種動態的錯覺。(想知道更多的話,可以查看這個維基百科

In this section, it will show you, by utilising some JavaScript built-in functions with Canvas, you can create a simple animation which works like a charm. In the previous article, you can see in the main title, there is an example digital clock. If you have read through this tutorial, you can make the digital clock works.

在這一個篇章裡,會從善用一些 JavaScript 本身的函式和 Canvas 結合,你就可以創造出一個神奇的動畫。在上一個篇章裡,你可以看到開頭有一個數位時鐘,若你把這一個篇章的內容熟悉,你就可以輕鬆的讓數位時鐘動作了。

Additionally, in this article’s main title, we also provide another challenge for you to create a simple water ripple (As illustrated below, in the main title or click here to view it from CodePen!). The water ripple will follow your mouse position and disperse in a time interval (or a period). In the end of this article, it will provide you some tips to help you create the water ripple. Of course, if you are curious or eager to see the source code, you may view this Gist as you wish.

此外,在這一篇文章的主題,也提供了另一個挑戰,創作出一個簡單的水波特效(見下圖、本篇章開頭圖示或者你可以到 CodePen 看看!)。 這個水波還有另一個特性,它會跟隨著你的滑鼠的位置然後在一定的時間間隔(或一個週期)擴散開來。當然,如果你很想或者你非常好奇原始碼到底怎麼編成的,你可以隨心所欲的去查看這個 Gist

The water ripple will follow the mouse position.
Water Ripple on CodePen

It’s JavaScript Time !

Crash Course for JS Syntax

In this part of section, we are going to spend a little time on JavaScript. If you are familiar with it, you may skip to next section. However, for those who didn’t know where or how to create animation, it is best to start from knowing the “tools” you need!

在這一節,我們會稍微在 JavaScript 方面多做一些著墨。如果你本身就很熟悉 JavaScript,你可以直接跳到下一節。然而,如果你不知道從何開始,最好的選擇就是先熟悉你會需要用到的工具!

We will quickly get through this section with the subtitles listed in this banner.

Data Types

In computer programming, data types are the fundamental type of information defined by languages. (There are other interpretation about data types or you can view more information on wiki.) In JavaScript, basic types are Number, String, Boolean and Object.

  • Number type — Numeric values, for instance : 0, 1, 0.5, 123, 2e10 (two times ten to tenth power) ... etc
  • String type — A sequence of characters which is nested by single or double quotes, such as :"A", "123", "Hello World", 'I love JavaScript', ... etc
  • Boolean type — Represents the state of true or false .
  • Object type — A JavaScript object can be a sequence of data separated by commas and nested with the square brackets (which we called Array), such as :[1, "Hello World", true, ['Nested array', 1]]; However, it can also have a series of key value pairs separated by commas and nested with curly braces, for instance, { name: "Maxwell", age: 20, isMale: true, married: false }.

在程式語言的世界裡,資料型態是由程式語言定義基本的資料存儲的形式。(當然,有不同的方式可以去解釋什麼叫做資料型態,你也可以去查看維基百科怎麼說)在 JavaScript 裡,基本的資料型態有 Number, String, Boolean 以及Object.

  • Number 型態 — 數字型態的值,例如:0, 1, 0.5, 123, 2e10 (2乘以10的10次方) ... 等等
  • String 型態 — 字串(一連串單字串起來的值)型態,通常會被單引號以及雙引號括起來例如:"A", "123", "Hello World", 'I love JavaScript', ... 等等
  • Boolean 型態 — 布林型態代表只有兩個值, true(真) 或者 false(假)
  • Object 型態 — 一個 JavaScript 物件可以表示為一連串的資料被逗號分隔後再被中括號括起來(我們稱它為 Array(陣列)),例如:[1, "Hello World", true, ['Nested array', 1]];然而我們也可以有一連串為鍵值對的資料所組成,每一對資料會被逗號分隔,並使用大括弧括起來,比如: { name: "Maxwell", age: 20, isMale: true, married: false }.

Array & Objects

It is a little tricky in JavaScript objects — Array is also a type of Object . In console, you can try the typeof operator to verify the data type of array.

這裡有一個特殊的點在於,JavaScript的物件裡 — 陣列們都被視為一種物件。你可以藉由 typeof 運算子去驗證這個道理。

console.log(typeof [1, 2, 3, true, false, "String"]); // "object"

Well, it returns the "object” value, but that is not the thing what we need to concern. You can view the array object as a series of key value pair of informations which keys are the indices start from zero.

它印出了 "object” 這個值,但這並不是我們會關注的點。你可以將陣列們看作是一連串的鍵值對,而那些“鍵”是從零開始計數向上序列的數字

In JavaScript Objects (including Arrays), variables store object’s reference but not their value. What does this mean ? If you use new variable to store the same object, it does not create new value and store into the new variable, it stores the object’s reference. In the new variable, if you change the properties or values of the object, the previous variable will be also affected by the modification. Because they store the “same reference“ of the object. (You can try to figure out the example code.)

在 JavaScript 物件(包含陣列),變數儲存的是物件的“位址”而不是“值”。這代表什麼?如果你使用新的變數去存取這個物件,它不會是創造一個新的物件在存進這個新變數,而是存同一物件的“位址”到這個新變數裡。如果你對這個新的變數所存的物件做更動,裡面的結果也會影響到原先的變數所存的物件。因為它們存的都是同一個物件的位址。(你可以看以下的範例去了解 JavaScript 的物件特性)

Example of Showing the Property of JS Objects

By the way, if you want to copy the same object value instead of its reference, you can try this (which I found on StackOverflow, however, I choose this method) :

var newObject = JSON.parse(JSON.stringify(oldObject));

順帶一提,如果你想要複製一個相同的物件的值而非位址,你可以試試看這個方法(從 StackOverflow 網站上找到的,但我是選擇這一個方式):

var 新的物件 = JSON.parse(JSON.stringify(舊的物件));

Conditional & Looping Statements

Conditional statement is a basic way to perform different actions in corresponding conditions. The basics are the if ... else ... statement or the switch statements.

條件運算式是一個基本運算式讓你可以去因應不同的狀態去執行相對應的命令。主要有兩大基本的條件運算式,分別為 if ... else ... 運算式以及 switch 運算子。

JS Conditions

Looping statement executes a block of code several times. There are basic looping statements such as the while statement or the for statement.

迴圈運算式負責重複執行一段命令複數次。基本的運算是包含 while 以及 for 迴圈敘述。

Example While Loop and For Loop

Additionally, when looping through JavaScript Objects, we sometimes use the for — in loop or the for - of loop. The for — in loop loops through the object’s indices which the for - of loop loops through the object’s values. The example provides several ways to loop through the objects. (Usually, I use for — of loop in arrays but it depends on how your coding style is.)

此外,如果想要迭代 JavaScript 的物件,有時候可以使用 for — in 迴圈或者是 for — of 迴圈。 for — in 迴圈專門迭代一輪物件的“鍵”,然而 for — of 迴圈則是迭代一輪物件的“值”。以下的例子提供了幾種方式去迭代 JavaScript 的物件。(通常,我會運用 for — of 迴圈在陣列上,但是根據不同的編成風格會有不同的編寫迴圈的方式。)

Looping Through JS Objects.

Functions & Variable Scope

Functions in JavaScript is a way to pack a series of statements or expressions into a block code which can be called or invoked. (And you can call functions anytime.) However, for more information, check out other interpretation on wiki.

函式在 JavaScript 裡可以將一系列的運算式或表達式包成一塊可以被呼叫執行的程式碼(而且可以不斷的呼叫)。然而,如果要知道更多有關於函式的更精確的定義,可以看看維基百科

Several Ways to Using Functions

Here comes another important topic in JavaScript, there are only two kinds of variable scopes. One is “global scope” and the other is “local scope” (or I mean like “functional scope”). That is, the variables in global scope can be access globally while variable in local scope (or functional scope) can only be access in the function’s code block. Local variables cannot be accessed outer from the function’s code block.

這裡又有另一個 JavaScript 重點,JavaScript 只有兩種變數的作用域,分別是“全域”以及“函式作用域”。也就是說,全域變數隨時隨地都可以被使用,但函式作用域裡面的變數只能在函式內部使用,而在函式外部則視為無效。

Below there are several examples to show how variable scope works. Inside the functions, if you didn’t define variable by using the keyword var, it will automatically access to the global variables.

以下的程式碼有幾個範例解釋變數作用域是怎麼運作的。在函式內部,如果你沒有使用關鍵字 var 的話,此變量就會以全域變數的方式運作。

Global & Local (Functional) Scope Explained

You may hear that there are more details to discuss on JavaScript functions, such as “callback functions” or “closures”. But in this article, we should mainly focus on what we need to know to create canvas animation. If you want to know more details about functions, you can consult Google or Wikipedia.

你可能會常常聽到會有更多有關於 JavaScript 函式的細節,像是 “回呼函式” 或者是 “閉包”。但在這一篇文章裡,我們只探討我們需要的知識去創作一個簡單的 Canvas 動畫。如果你想要瞭解更多的話,你可以上網查詢 Google 或者是維基百科。

Events

JavaScript Events are the key points to make webpages interact with users dynamically. The most easiest way is to write JavaScript into HTML elements with special attributes. Such technique has already implemented in the very beginning of the canvas tutorial. We used this on setting up the draw() function after the HTML document loaded by using the onload attribute on the <body> tag. (Because we want to make sure the <canvas> element loaded before we use the draw() function !)

JavaScript 事件是使網頁可以和使用者互動最為關鍵的要素。其中,最簡單的方式是把 JavaScript 寫進 HTML 元素裡的特殊屬性。這一個技巧打從這一系列的教學的最開始就已經有使用到了。我們為了確保在我們使用 draw() 函式繪圖前,讓 <canvas> 元素先被渲染出來。於是必須設定等待這個 HTML 文件讀完之後,也就是在<body> 元素上設定 onload 屬性再呼叫 draw() 函式。

Example of Setting Up the Event

You can also try simple events with the HTML <button> elements by using the onclick attribute.

你也可以試試看在 HTML <button> 元素上藉由 onclick 來設定事件。

<button onclick="alert('You triggered the event!');"></button>

Learning JavaScript Events basics, you should also know how to use the function addEventListener(eventType, callback). This function mainly set up events and this function requires the type of the event and a callback function which describes the behaviour when the event triggered. Usually, there are so many kinds of events and you can look up on the internet about JavaScript Events. Or you can click the link. >_O

學習基礎的 JavaScript 事件,你應該要會使用 addEventListener(事件型態, 回呼函式)。這個函式需要指定事件型態以及當事件觸發時需要執行的回呼函式。通常,事件會有很多種型態供你選擇,你可以上網查詢有關於這些 JavaScript 事件。或者是點這裡。>_O

If you want to implement the function addEventListener on setting up canvas draw() function after document loaded, here is the example. (However, the draw() will be a callback function which need to be stored as a variable!)

如果你想要試著去藉由 addEventListener 設定事件的方式去設定 draw() 函式,你可以這樣做。(然而,你的 draw()會成為一個回呼函式,而這個函式必須用變數的形式去儲存這個函式。)

Using the addEventListener Function to setup Events
Let’s Draw a Dynamic Sine Wave !

Dynamic Wave Example

Create The Stable State of the Canvas First

Creating HTML Canvas animation may need to take some time to understand the design process, but as long as you practice, it will become easier every time you do it. It is recommended to start from the static state of the animation. The illustration shows the static state of the canvas, it is just a sequence of dots forms a horizontal line in the middle Y position.

想要創造一個 HTML Canvas 動畫可能會需要一些時間去理解設計的過程,但是只要持續的應用,這段過程會隨著你每一次的練習會越來越簡單。比較建議設計動畫可以從初始靜態的畫面開始建立。下圖就是這個動畫中靜態的 Canvas 畫面,一系列的點點形成的一條水平線,然後這一條直線是位於 Y 位置的中心。

Static State of the Animation
Static Version of the Dynamic Wave Example

Don’t worry, we will break down the code into several pieces and explain step by step. So the first thing is to define the draw function and setup the Canvas environment. It has been done many times in this series of HTML5 Canvas tutorial.

不要擔心,我們會把這一個範例程式碼分成好幾塊一步一步講解。首先,我們會定義 draw 函式並建立起 Canvas 的繪圖環境。這個步驟已經在這一系列的 HTML5 Canvas 教學已經建立了好幾遍。

The HTML Canvas Environment

Next, we define several variables that we may use to construct the horizontal line :

  • countOfDots — The numbers of dots on the horizontal line (set to 100)
  • radiusOfDots — The radius of the dots
  • lineYPosition — The vertical position of the horizontal line (set to be on the middle of the vertical position)
  • amplitudeOfDots — Because every dots have different displacement, we creates an array to record the displacement (array with 100 elements, each element is set to zero using the for loop)

我們定義了構成這一條線所需的變數:

  • countOfDots — 構成這條直線的點點數(設定為 100 )
  • radiusOfDots — 點點的半徑
  • lineYPosition — 水平線位在的垂直位置(設定為整個 canvas 垂直的中央)
  • amplitudeOfDots — 每個點會有不同的位移大小,所以創造一個陣列去記錄它們(陣列大小為 100,藉由 for 迴圈讓每一個位置都設為 0 )
Defining Variables

After variable defined, then start on the main program. By using the for — of loop on the amplitudeOfDots array and define count as a counter to count the X, Y position of the dots. Using the drawFilledDot helper function defined below the main program and draw the dots. (Below the comments in the program describes how the program works)

變數已經定義好了之後,就可以著手主程式的部分。使用 for — of 迴圈迭代 amplitudeOfDots 陣列,然後再定義一個 count 變數作為計數器,每一個迭代過程會記錄每一個點的 X, Y 位置。藉由 drawFilledDot 自定義小幫手函式去畫出點點。(以下的程式碼有更詳細的註解敘述每一行執行的過程與解說。)

Main Control Flow

Don’t forget to setup the event. Before executing the draw function, the document body should be loaded!

不要忘了設定事件。在執行 draw 函式之前,必須先讓文件的內容載入!

Setting Up Events

Let It Animate — The setInterval() Function

In the main title, we know that the animation is a process that displays still images (the canvases) in a rapid rate speed which cause the illusion of the movement. So, we need to continuously draw the canvas and refresh the canvas again and again in a short period. Another JavaScript function serves this purpose well.

setInterval(callback, periodInMillisecond)

在文章的開頭,我們知道動畫是一個快速顯示靜態影像而產生動態的錯覺。所以,我們會需要不斷地畫出 Canvas 以及重整 Canvas 在一段短的週期。另一個 JavaScript 函式剛好符合這一個需求。

setInterval(回呼函式, 以微秒為單位之週期)

To try how this function works, you can simply type this line of simple code in console and it will print out the message every 5 seconds.

setInterval(function() { console.log("Hello! World!"); }, 5000);

想要測試這個函式怎麼動作的,你可以在你的開發者工具測試這一條簡單的程式碼,每隔五秒會印出裡面的訊息。

setInterval(function() { console.log("哈囉!美麗新世界!@@"); }, 5000);

Let’s add this function into our main programming code, we will set 20 milliseconds (which is 0.02 second) as a period. The control flow in the callback function will be the process of drawing the canvas.

接著我們把這個函式加進範例的程式裡,我們設定 50 微秒(也就是 0.05 秒)為一個週期。內部回呼函式主要的內容為畫出 Canvas 的過程。

Using the setInterval Function

Let It Animate — The clearRect() Function

We can now ensure that the canvas will be redraw in every period. However, before every redrawing process, we need to clear up the canvas. In order to clear the canvas, another function called clearRect() which belongs to the canvas context object can clear the canvas in a shape of rectangle. It requires the left-top corner position X, Y and the right-bottom corner position X, Y of the rectangle.

clearRect(leftTopX, leftTopY, rightBottomX, rightBottomY)

我們可以確定的是,每隔一個週期 Canvas 就會再度被重新畫出來。然而,我們也要再重新畫上去前清空 Canvas 。為了要達成此一目的,所以有另一個屬於畫布背景物件的函式叫做 clearRect() 。這個函式提供一個藉由畫出一個以長方形就可以來清空該區塊的 Canvas,所以會需要填入的參數為該長方形左上角的 X, Y 座標以及長方形右下角 X, Y 座標。

clearRect(左上角X位置, 左上角Y位置, 右下角X位置, 右下角Y位置)

We can add this statement at the beginning of the callback function in the setInterval function. In order to clear all the space of the canvas, so the left-top corner position may be (0, 0) and the right-bottom corner position may be (screen.width, screen.height).

我們可以加入這一條敘述式在 setInterval 內部的回呼函式的一開始。由於要把整面 Canvas 給清除,我們填入的參數 — 左上角之座標為 (0, 0),右下角之座標為 (screen.width, screen.height)

Clear Canvas Before Redraw

Let It Animate — The Mysterious Sine Wave

Well, the subtitle said Sine wave is mysterious, but actually it can be quite interesting if we can animated in our browser. It is simple to access the sine wave function via the Math object with the Math.sin() function. The illustration below shows the property of the sine wave.

雖然副標題認為正弦波是一個神秘的波形,但實際上如果我們能夠讓它在我們的瀏覽器上震動的話,那豈不是很有趣。想要產生正弦波的值可以藉由 Math 物件的正弦函數 Math.sin() 實現。有關於正弦波的特性就由以下的簡圖呈現。

Math.sin() Function

The Math.sin() function only requires the radian of the sine wave. The key informations you should know from the illustration are listed below :

  • Sine wave repeats in a period of 2 * Math.PI (which is a total of 360 degree in the unit circle) where Math.PI represents the value of 3.1415926 ...
  • The Math.sin() function requires the value of radian. For a given radian value of X (which is the parameter that the function required), you will get the corresponding value of Y.
  • You can get the maximum value of sine wave when your X position is Math.PI / 2 (which is the90th degree in the unit circle).
  • You can get the minimum value of sine wave when your X position is Math.PI * 3 / 2 (which is the 270th degree in the unit circle).

Math.sin() 函式只需要一個簡單的輸入值代表著徑度量。你只需要從這張圖得知這些東西就夠了:

  • 正弦函數會以每 2 * Math.PI (也就是總共 360 度的一個單位圓)的週期重複,而 Math.PI 得值約為 3.1415926 ...
  • Math.sin() 函式只需要一個徑度量的值帶入。徑度量 X 帶進去就會得到相對應的 Y 值出來
  • 你可以找到正弦函數的最大值位於 Math.PI / 2 (也就是單位圓的第 90 度)
  • 你可以找到正弦函數的最小值位於 Math.PI * 3 / 2 (也就是單位圓的第 270 度)

Let It Animate — Pack It Up

We are now going to let the sine wave animate, the code below defines more variables in order to make the sine wave animate in the way we want :

  • waveMaxAmplitude — The maximum amplitude (displacement) from the middle line (set to 200)
  • waveDegree — The current degree (which represents the X position)of the sine wave (set to zero)

我們可以開始想辦法讓正弦波動作,以下的程式碼多定義了幾個變數讓我們可以用想要的方式去控制正弦波的動畫:

  • waveMaxAmplitude — 正弦波的強度(或者是位移),代表和中心水平線之間的距離(設定為 200)
  • waveDegree — 波的 X 座標位置 (設定為零)
Define More Variables to Control the Appearance of Sine Wave

Inside the callback of the setInterval function, using the pop and unshift methods to change the value of the amplitudeOfDots array. The pop method (of Array type object) helps remove the last element of the array while the unshift method (of Array type object) inserts new value in the beginning of the array. Increase the waveDegree by 10 (you can try other values such as 5 or 20) in order to make the dots displacement change in every period.

setInterval 的回呼函式裡,使用 pop 以及 unshift 函式達到隨時可以改變 amplitudeOfDots 陣列。pop 方法可以移除陣列的最後一個元素。而 push 方法則可以插入一個新的值到陣列的第一個位置。我們可以在每一個週期往上加 waveDegree 的值(加 5 或者 20 都可以試試看,範例是每週期往上加 10)使得每一個週期都可以改變點點跟水平線之間位移。

Animation Process

That’s it! You have successfully created the animated sine wave on Canvas!

到這裡為止!你成功地創造出一個正弦波的動畫在 Canvas 上了!

Stunning Sine Wave

Summery

In this section of the article, it has shown you the process to create the animation from HTML Canvas. You have learned :

  • JavaScript Basics
  • A Process of Creating Animation — from creating stable image (or canvas) to dynamic animation
  • The setInterval() function which can execute the callback function in a period of time (in milliseconds)
  • The clearRect() function which belongs to the canvas context object that clears a rectangular space in Canvas
  • The sine wave and the usage of Math.sin() function

在這一篇文章裡,主要讓你可以使用 HTML Canvas 創造動畫。你也學到了:

  • 基本的 JavaScript 概念
  • 創造一個動畫的過程 — 從穩態的影像到動態的影像的過程
  • setInterval() 函式的使用,讓你可以每隔一定的週期(單位為微秒)重複執行一段程式碼
  • 畫布背景物件 clearRect() 函式的使用,藉由畫出一個長方形清空 Canvas 的方式
  • 正弦波以及 Math.sin() 函式的使用

So What Will Be Our Next Topic ?

Basically, you can utilise the knowledge of the tutorial series that taught you to make HTML Canvas. However, that is not the end of the series. In the next tutorial series, we are going to make simple games by canvas. Before that, it is very important for you to know, setting up JavaScript Events is a key to make games!

基本上,你可以善用這一系列的教學文章所教的知識去創作出任意形式的 HTML Canvas。然而,這並不是這系列文章的結束。在下一篇文章開始,我們將利用 Canvas 創作出遊戲。在這之前,有一件重要的事情是,知道如何去設定 JavaScript 事件是創作出遊戲的關鍵!

If you can accomplish the challenges which presents in the main title (which the water ripple will be dispersed in a period of time from your mouse position), making and designing game becomes an easier challenge.

如果你可以完成這一個章節給你的挑戰(也就是每隔一定的週期水波會隨著你的滑鼠位置擴散開來),創造並設計出遊戲是一件可以輕鬆完成的挑戰。

We can give you a hint, the example code below detects and prints out the mouse X, Y positions whenever your mouse is moving. (The type of the JavaScript event is mousemove) Happy coding ~

我們可以給一個小提示,下面的範例,當你的滑鼠在移動的時候,隨時會偵測並印出滑鼠的 X, Y座標位置。(此 JavaScript 的事件型態為 mousemove)開心地去編程吧~

The “mousemove” Event

Previous Tutorial : HTML5 Canvas (II)

上一篇教學:HTML5 Canvas (II)

Recommended : Introduction to Three.JS

自薦文章:入門 Three.JS

--

--

Maxwell Alexius

A web developer, artist, and designer, and loves everything related to dragons. Welcome to visit my site : https://svartalvhe.im/maxwell-alexius