摘要:前言上篇主要是理論的概述,本篇會多些實踐,來講講的基礎用法,并包含一些基礎三角函數的應用,推薦沒有基礎的朋友閱讀,熟悉的朋友可以跳過。完整實例一個會跟蹤鼠標位置的箭頭三角函數上下運動終于順利過渡到三角函數的話題笑。
前言
上篇主要是理論的概述,本篇會多些實踐,來講講canvas的基礎用法,并包含一些基礎三角函數的應用,推薦沒有canvas基礎的朋友閱讀,熟悉的朋友可以跳過。
本人能力有限,歡迎牛人共同討論,批評指正。
canvas的API有很多,如果一一列舉30分鐘你是絕對看不完的,而且怎么流水賬還不如自己去看文檔呢(笑),本教程的思路是用實例一步一步從無到有講解基礎用法。
canvas相關文檔
布置畫布:通過添加標簽,添加canvas元素;
獲取畫布:通過標簽的id,獲得canvas對象;
獲得畫筆:通過canvas對象的getContext("2d")方法,獲得2D環境。
const canvas = document.getElementById("canvas"); const context = canvas.getContext("2d");畫個箭頭
首先我們來畫個紅邊黃底的箭頭,使用面向對象的代碼組織方式,全部代碼如下。
類名為Arrow。它擁有x軸坐標、y軸坐標、底的顏色color、旋轉弧度rotation四個屬性。
實例方法是draw(),它需要一個context對象作為參數,就是準備工作里的context,它就相當于是畫筆,這里其實是類似依賴注入的過程,將canvas的畫筆交給實例的draw()方法,實例用這個畫筆去畫出箭頭,繪畫過程見代碼注釋。特別注意以下幾點:
beginPath()方法調用后moveTo()和lineTo移動坐標是相對與beginPath()時畫筆的坐標的,可以理解成畫筆自帶一個坐標系,它可以旋轉和在畫布上移動,繪制工作的坐標都是屬于這個坐標系的;
beginPath()是繪制設置狀態的起始點,它之后代碼設置的繪制狀態的作用域結束于繪制方法stroke()、fill()或者closePath();
save()的作用是保存筆的狀態,因為一個畫布的筆只有一支,會在不同對象中傳遞,為了不污染后續的畫就應該先保存,畫完再restore()還原;
本身是透明的,可以使用CSS給它個背景,例子中普遍使用白色背景。
/** * 箭頭類 * @class Representing a arrow. */ /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "Arrow" }] */ class Arrow { /** * Create a arrow. */ constructor() { this.x = 0; this.y = 0; this.color = "#ffff00"; this.rotation = 0; } /** * Draw the arrow. * @param {Object} _context - The canvas context. */ draw(_context) { const context = _context; // 會先保存畫筆狀態 context.save(); // 移動畫筆 context.translate(this.x, this.y); // 旋轉畫筆 context.rotate(this.rotation); // 設置線條寬度 context.lineWidth = 2; // 設置線條顏色 context.strokeStyle = "#ff0000"; // 設置填充顏色 context.fillStyle = this.color; // 開始路徑 context.beginPath(); // 將筆移動到相對位置 context.moveTo(-50, -25); // 畫線到相對位置 context.lineTo(0, -25); context.lineTo(0, -50); context.lineTo(50, 0); context.lineTo(0, 50); context.lineTo(0, 25); context.lineTo(-50, 25); context.lineTo(-50, -25); // 閉合路徑 context.closePath(); // 填充路徑包圍區 context.fill(); // 繪制路徑 context.stroke(); // 載入保存的筆信息 context.restore(); } }
同理我們還可以畫點其他的,比如一個圓ball.js,稍微多些參數,慢慢理解。
成品的效果可以先看這個(稍微劇透):一個會跟蹤鼠標位置的箭頭
現在我們已經掌握了畫畫的基本功,并且可以畫箭頭arrow.js和圓ball.js,然而這樣只是靜止畫,接下來我們需要一個循環,不斷的執行擦除和重畫的工作才能實現幀動畫。
下面這段代碼的中繪圖函數drawFrame被立即執行,并遞歸調用自身,你將會在大部分例子中看到。
循環原理上一篇已經說明,不再重復。這里要說明的是clearRect(),這個函數接受一個矩形坐標,也就是(x軸坐標,y軸坐標,矩形寬度,矩形高度),用于清除矩形區域內的畫。
例子里直接是清除了整個畫布,但這不是絕對的,刷不刷新,是局部刷新還是全部刷新,都需要靈活處理。
這里有個不刷新的例子:鼠標畫圖工具
(function drawFrame() { // 類似setTimeout的操作 window.requestAnimationFrame(drawFrame, canvas); // 將畫布擦干凈 context.clearRect(0, 0, canvas.width, canvas.height); // ...繼續你的作畫 }());給它點動力
現在畫面已經是在不斷的重繪,但為什么還是靜止的呢?因為每一次刷新都沒有改變要畫的內容。
那我們就給它一個目標吧,這樣它才能動起來,比如就讓箭頭始終指向鼠標。
下面是核心代碼,主要目的就是求出每幀arrow的旋轉角度,這里使用的工具類mouse會實時返回鼠標的x,y軸坐標,封裝原理上一篇已經講過,根據這鼠標的坐標和arrow的坐標,即可得到鼠標的相對于arrow的距離dx和dy,如下圖:
而arrow的旋轉角度即可以通過dx和dy使用反正切函數得到,這里需要注意幾點:
仔細看上面代碼中arrow的繪制過程,可知其原點是在中心位置的,所以剛好旋轉角度就是畫筆的旋轉角度;
dx和dy是鼠標相對與arrow的坐標,所以圖中把坐標系挪動箭頭中心是沒毛病的;
用atan2,而不是atan,是因為tan值本來就可能是重復的,比如-1/2和1/(-2)兩個都是-0.5,無法區分象限,而atan2就可以區分開。
完整實例:一個會跟蹤鼠標位置的箭頭
window.onload = function () { const canvas = document.getElementById("canvas"); const context = canvas.getContext("2d"); const mouse = utils.captureMouse(canvas); const arrow = new Arrow(); arrow.x = canvas.width / 2; arrow.y = canvas.height / 2; (function drawFrame() { window.requestAnimationFrame(drawFrame, canvas); context.clearRect(0, 0, canvas.width, canvas.height); const dx = mouse.x - arrow.x; const dy = mouse.y - arrow.y; arrow.rotation = Math.atan2(dy, dx); arrow.draw(context); }()); };三角函數 上下運動
終于順利過渡到三角函數的話題(笑)。三角函數不止有反正切一個應用,下面再看一個例子。
下面是一個ball在上下運動的核心代碼,重點就是ball的y軸坐標改變,就是這句:
ball.y = clientY + Math.sin(angle) * range;
利用Math.sin(angle)的取值范圍是-1到1,并且會隨著angle增大而反復,使ball在一定范圍上下運動。
完整例子:一個上下運動的球(可調參數版)
window.onload = function () { const canvas = document.getElementById("canvas"); const context = canvas.getContext("2d"); const ball = new Ball(); let angle = 0; // 運動中心 const clientY = 200; // 范圍 const range = 50; // 速度 const speed = 0.05; ball.x = canvas.width / 2; ball.y = canvas.height / 2; (function drawFrame() { window.requestAnimationFrame(drawFrame, canvas); context.clearRect(0, 0, canvas.width, canvas.height); ball.y = clientY + Math.sin(angle) * range; angle += speed; ball.draw(context); }()); };向前運動
只是上下運動不過癮,那就讓圓前進吧,其實就是每幀改變x軸的位置。
核心代碼如下,相比前面的上下運動,多了x軸的速度,每幀移動一點就形成了波浪前進的效果。
完整實例:一個波浪運動的球
window.onload = function () { const canvas = document.getElementById("canvas"); const context = canvas.getContext("2d"); const ball = new Ball(); let angle = 0; const centerY = 200; const range = 50; const xspeed = 1; const yspeed = 0.05; ball.x = 0; (function drawFrame() { window.requestAnimationFrame(drawFrame, canvas); context.clearRect(0, 0, canvas.width, canvas.height); ball.x += xspeed; ball.y = centerY + Math.sin(angle) * range; angle += yspeed; ball.draw(context); }()); };其他示例
其他的應用就不一一講解,羅列出來一些:
不斷縮放的球
兩軸同時改變的圓
繪制波
一個做圓周運動的圓
一個做橢圓形運動的圓
計算兩個隨機塊的距離
中心點到鼠標的距離
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/54524.html
摘要:前言上篇主要是理論的概述,本篇會多些實踐,來講講的基礎用法,并包含一些基礎三角函數的應用,推薦沒有基礎的朋友閱讀,熟悉的朋友可以跳過。完整實例一個會跟蹤鼠標位置的箭頭三角函數上下運動終于順利過渡到三角函數的話題笑。 前言 上篇主要是理論的概述,本篇會多些實踐,來講講canvas的基礎用法,并包含一些基礎三角函數的應用,推薦沒有canvas基礎的朋友閱讀,熟悉的朋友可以跳過。 本人能力...
摘要:所建議的刷新率是秒幀,大部分瀏覽器是遵循這一標準的。基于時間的動畫其實無論是還是定時器,都不能保證以特定速率播放。將物體每幀移動距離,轉變為物體每秒移動距離。 前言 本文雖說是基礎教程,但這是相對動畫/游戲領域來說,在前端領域算是中級教程了,不適合前端小白或萌新。閱讀前請確保自己對前端三大件(JavaScript+CSS+HTML)的基礎已經十分熟悉,而且有高中水平的數學和物理知識。d...
摘要:所建議的刷新率是秒幀,大部分瀏覽器是遵循這一標準的。基于時間的動畫其實無論是還是定時器,都不能保證以特定速率播放。將物體每幀移動距離,轉變為物體每秒移動距離。 前言 本文雖說是基礎教程,但這是相對動畫/游戲領域來說,在前端領域算是中級教程了,不適合前端小白或萌新。閱讀前請確保自己對前端三大件(JavaScript+CSS+HTML)的基礎已經十分熟悉,而且有高中水平的數學和物理知識。d...
閱讀 3724·2021-11-24 10:23
閱讀 2771·2021-09-06 15:02
閱讀 1274·2021-08-23 09:43
閱讀 2351·2019-08-30 15:44
閱讀 3045·2019-08-30 13:18
閱讀 779·2019-08-23 16:56
閱讀 1743·2019-08-23 16:10
閱讀 536·2019-08-23 15:08