2023年8月9日 星期三

HTML 的 Canvas 繪圖演練

作者:楊于葳




使用 HTML 檔案練習 Canvas 的簡易實作演練,呈現:下雨特效、重力加速度、四面八方的飛、煙火爆炸畫面。




畫布的基本操作




  1. <!DOCTYPE html>
  2. <html lang="en">
  3.  
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Canvas 繪圖</title>
  8. </head>
  9.  
  10. <body>
  11. <h3>Canvas 繪圖</h3>
  12. <canvas width="600" height="600" id="cvs" style="border: 1px solid black;"></canvas>
  13. <script>
  14. let cvs = document.querySelector("#cvs"); // 取得 canvas 物件
  15. let ctx = cvs.getContext("2d"); // 取得對應的 context 物件
  16. // 填滿
  17. ctx.fillStyle = "red";
  18. ctx.fillRect(50, 50, 100, 100);
  19. ctx.fillStyle = "black";
  20. ctx.fillText("Hello World", 200, 50);
  21. // 描邊
  22. ctx.strokeStyle = "blue";
  23. ctx.strokeRect(50, 200, 50, 50);
  24. // 路徑
  25. ctx.beginPath();
  26. ctx.moveTo(200, 200);
  27. ctx.lineTo(300, 250);
  28. ctx.lineTo(250, 300);
  29. ctx.fill();
  30. // 圓形
  31. ctx.beginPath();
  32. ctx.arc(400, 50, 20, 0, 2 * Math.PI);
  33. ctx.stroke();
  34. // 曲線
  35. ctx.beginPath();
  36. ctx.moveTo(400, 400);
  37. ctx.quadraticCurveTo(450, 200, 550, 350); // 決定控制點和終點
  38. ctx.stroke();
  39. // 圖片
  40. let img = new Image();
  41. img.src = "conan.jpg";
  42. img.addEventListener("load", function () {
  43. ctx.drawImage(img, 50, 350, 200, 200);
  44. });
  45. </script>
  46.  
  47. </body>
  48.  
  49. </html>






分子系統:下雨特效




  1. <!DOCTYPE html>
  2. <html lang="en">
  3.  
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Canvas 繪圖</title>
  8. </head>
  9.  
  10. <body>
  11. <h3>Canvas 繪圖</h3>
  12. <canvas width="600" height="600" id="cvs" style="border: 1px solid black;"></canvas>
  13. <script>
  14. class Rect {
  15. constructor(x, y) {
  16. this.x = x;
  17. this.y = y;
  18. this.vx = Math.random() * 2; // 0 ~ 2 之間的亂數
  19. this.vy = Math.random() * 1; // 0 ~ 1 之間的亂數
  20. }
  21. update() {
  22. this.x = this.x + this.vx;
  23. this.y = this.y + this.vy;
  24. }
  25. draw() {
  26. ctx.fillRect(this.x, this.y, 4, 4);
  27. }
  28. }
  29.  
  30. let cvs = document.querySelector("#cvs"); // 取得 canvas 物件
  31. let ctx = cvs.getContext("2d"); // 取得對應的 context 物件
  32. let particles = []; // 建立一個空陣列
  33.  
  34. function refresh() {
  35. particles.push(new Rect(50, 50));
  36. // 更新資料
  37. for (let i = 0; i < particles.length; i++) {
  38. particles[i].update();
  39. }
  40. // 清空畫面
  41. ctx.clearRect(0, 0, cvs.width, cvs.height);
  42. // 根據新的資料重新畫過
  43. for (let i = 0; i < particles.length; i++) {
  44. particles[i].draw();
  45. }
  46. }
  47. window.setInterval(refresh, 10);
  48.  
  49. </script>
  50. </body>
  51.  
  52. </html>



分子系統:重力加速度



  1. <!DOCTYPE html>
  2. <html lang="en">
  3.  
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Canvas 繪圖</title>
  8. </head>
  9.  
  10. <body>
  11. <h3>Canvas 繪圖</h3>
  12. <canvas width="600" height="600" id="cvs" style="border: 1px solid black;"></canvas>
  13. <script>
  14. class Rect {
  15. constructor(x, y) {
  16. this.x = x;
  17. this.y = y;
  18. this.vx = Math.random() * 2; // 0 ~ 2 之間的亂數
  19. this.vy = Math.random() * 1; // 0 ~ 1 之間的亂數
  20. }
  21. update() {
  22. this.x = this.x + this.vx;
  23. this.y = this.y + this.vy;
  24. this.vy = this.vy + 0.05; // 重力加速度
  25. // return 是否死掉
  26. return this.x > cvs.width || this.y > cvs.height;
  27. }
  28. draw() {
  29. ctx.fillRect(this.x, this.y, 4, 4);
  30. }
  31. }
  32.  
  33. let cvs = document.querySelector("#cvs"); // 取得 canvas 物件
  34. let ctx = cvs.getContext("2d"); // 取得對應的 context 物件
  35. let particles = []; // 建立一個空陣列
  36.  
  37. function refresh() {
  38. particles.push(new Rect(50, 50));
  39. // 更新資料
  40. for (let i = 0; i < particles.length; i++) {
  41. let isDead = particles[i].update();
  42. if (isDead) {
  43. particles.splice(i, 1);
  44. i--;
  45. }
  46. }
  47. // 清空畫面
  48. ctx.clearRect(0, 0, cvs.width, cvs.height);
  49. // 根據新的資料重新畫過
  50. for (let i = 0; i < particles.length; i++) {
  51. particles[i].draw();
  52. }
  53. }
  54. window.setInterval(refresh, 10);
  55.  
  56. </script>
  57. </body>
  58.  
  59. </html>



分子系統:四面八方的飛



  1. <!DOCTYPE html>
  2. <html lang="en">
  3.  
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Canvas 繪圖</title>
  8. </head>
  9.  
  10. <body>
  11. <h3>Canvas 繪圖</h3>
  12. <canvas width="600" height="600" id="cvs" style="border: 1px solid black;"></canvas>
  13. <script>
  14. // 分子系統 Particle System
  15. class Rect {
  16. constructor(x, y) {
  17. this.x = x * 5.5;
  18. this.y = y * 5.5;
  19. this.vx = (Math.random() - 0.5) * 4; // 隨機範圍在 -2 到 2 之間
  20. this.vy = (Math.random() - 0.5) * 2; // 隨機範圍在 -1 到 1 之間
  21. }
  22. update() {
  23. this.x = this.x + this.vx;
  24. this.y = this.y + this.vy;
  25. // this.vy = this.vy + 0.05; // 重力加速度
  26. // return 是否死掉
  27. return this.x > cvs.width || this.y > cvs.height;
  28. }
  29. draw() {
  30. ctx.fillRect(this.x, this.y, 4, 4);
  31. ctx.fillStyle = "#77bfd9";
  32.  
  33. }
  34. }
  35.  
  36. let cvs = document.querySelector("#cvs"); // 取得 canvas 物件
  37. let ctx = cvs.getContext("2d"); // 取得對應的 context 物件
  38. let particles = []; // 建立一個空陣列
  39.  
  40. function refresh() {
  41. particles.push(new Rect(50, 50));
  42. // 更新資料
  43. for (let i = 0; i < particles.length; i++) {
  44. let isDead = particles[i].update();
  45. if (isDead) {
  46. particles.splice(i, 1);
  47. i--;
  48. }
  49. }
  50. // 清空畫面
  51. ctx.clearRect(0, 0, cvs.width, cvs.height);
  52. // 根據新的資料重新畫過
  53. for (let i = 0; i < particles.length; i++) {
  54. particles[i].draw();
  55. }
  56. }
  57. window.setInterval(refresh, 10);
  58.  
  59. </script>
  60. </body>
  61.  
  62. </html>



分子系統:煙火爆炸的飛+黑色背景




  1. <!DOCTYPE html>
  2. <html lang="en">
  3.  
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Canvas 繪圖</title>
  8. </head>
  9.  
  10. <body style="background-color: black;">
  11. <h3 style="color: white;">Canvas 繪圖</h3>
  12. <canvas width="600" height="600" id="cvs" style="border: 1px solid black;"></canvas>
  13. <script>
  14. // 分子系統 Particle System
  15. class Rect {
  16. constructor(x, y) {
  17. this.x = x * 5.5;
  18. this.y = y * 5.5;
  19. this.vx = (Math.random() - 0.5) * 4; // 隨機範圍在 -2 到 2 之間
  20. this.vy = (Math.random() - 0.5) * 2; // 隨機範圍在 -1 到 1 之間
  21. }
  22. update() {
  23. this.x = this.x + this.vx;
  24. this.y = this.y + this.vy;
  25. this.vy = this.vy + 0.01; // 重力加速度
  26. // return 是否死掉
  27. // return this.x > cvs.width || this.y > cvs.height;
  28. return this.alpha < 0.02;
  29. }
  30. draw() {
  31. ctx.fillStyle = "#FFD700"; // 將顏色設定為黃色
  32. ctx.fillRect(this.x, this.y, 4, 4);
  33. }
  34. }
  35.  
  36. let cvs = document.querySelector("#cvs"); // 取得 canvas 物件
  37. let ctx = cvs.getContext("2d"); // 取得對應的 context 物件
  38. let particles = []; // 建立一個空陣列
  39.  
  40. function refresh() {
  41. particles.push(new Rect(50, 50));
  42. // 更新資料
  43. for (let i = 0; i < particles.length; i++) {
  44. let isDead = particles[i].update();
  45. if (isDead) {
  46. particles.splice(i, 1);
  47. i--;
  48. }
  49. }
  50. // 清空畫面
  51. ctx.clearRect(0, 0, cvs.width, cvs.height);
  52. // 根據新的資料重新畫過
  53. for (let i = 0; i < particles.length; i++) {
  54. particles[i].draw();
  55. }
  56. }
  57. window.setInterval(refresh, 10);
  58.  
  59. </script>
  60. </body>
  61.  
  62. </html>





分子系統:冒煙


  1. <!DOCTYPE html>
  2. <html lang="en">
  3.  
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <title>Canvas 繪圖</title>
  8. </head>
  9.  
  10. <body>
  11. <h3>Canvas 繪圖</h3>
  12. <canvas width="600" height="600" id="cvs" style="border: 1px solid black;"></canvas>
  13.  
  14. <script>
  15. // 分子系統 Particle System
  16. class Rect {
  17. constructor(x, y) {
  18. this.x = x;
  19. this.y = y;
  20. this.vx = (Math.random() - 0.5) * 4; // 隨機範圍在 -2 到 2 之間
  21. this.vy = (Math.random() - 0.5) * 2; // 隨機範圍在 -1 到 1 之間
  22. }
  23. update() {
  24. this.x = this.x + this.vx;
  25. this.y = this.y + this.vy;
  26. // this.vy = this.vy + 0.01; // 重力加速度
  27. // return 是否死掉
  28. return this.x > cvs.width || this.y > cvs.height;
  29. }
  30. draw() {
  31. ctx.fillRect(this.x, this.y, 4, 4);
  32. ctx.fillStyle = "#77bfd9";
  33. }
  34. }
  35.  
  36. class Smoke {
  37. constructor(x, y) {
  38. this.x = x;
  39. this.y = y;
  40. this.size = 20;
  41. this.alpha = 1;
  42. this.vx = Math.random() * 0.8 - 0.4; // -0.3 ~ 0.5
  43. this.vy = Math.random() * 1.5 - 2; // -2 ~ -0.5
  44. }
  45. update() {
  46. this.x += this.vx;
  47. this.y += this.vy;
  48. this.size += 0.25;
  49. this.alpha -= 0.002;
  50. return this.alpha < 0.03;
  51. }
  52. draw() {
  53. ctx.globalAlpha = this.alpha;
  54. ctx.drawImage(smokeImg,
  55. this.x - (this.size / 2),
  56. this.y - (this.size / 2),
  57. 30, 30
  58. );
  59. }
  60. }
  61.  
  62. let cvs = document.querySelector("#cvs"); // 取得 canvas 物件
  63. let ctx = cvs.getContext("2d"); // 取得對應的 context 物件
  64. let particles = []; // 建立一個空陣列
  65.  
  66. function refresh() {
  67. particles.push(new Smoke(cvs.width / 2, 420));
  68. // 更新資料
  69. for (let i = 0; i < particles.length; i++) {
  70. let isDead = particles[i].update();
  71. if (isDead) {
  72. particles.splice(i, 1);
  73. i--;
  74. }
  75. }
  76. // 清空畫面
  77. ctx.clearRect(0, 0, cvs.width, cvs.height);
  78. // 根據新的資料重新畫過
  79. for (let i = 0; i < particles.length; i++) {
  80. particles[i].draw();
  81. }
  82. }
  83. // 預先載入需要的圖片
  84. let smokeImg = new Image();
  85. smokeImg.src = "smoke.png";
  86. smokeImg.addEventListener("load", function () {
  87. window.setInterval(refresh, 10);
  88. });
  89.  
  90. </script>
  91. </body>
  92.  
  93. </html>



分子系統:移動的火把



  1.  
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.  
  5. <head>
  6. <meta charset="UTF-8">
  7. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  8. <title>Canvas 繪圖</title>
  9. </head>
  10.  
  11. <body>
  12. <h3>Canvas 繪圖</h3>
  13. <canvas width="600" height="600" id="cvs" style="border: 1px solid black;"></canvas>
  14.  
  15. <script>
  16. class Torch {
  17. constructor(x, y) {
  18. this.x = x;
  19. this.vx = 1;
  20. this.y = y;
  21. this.smokes = [];
  22. }
  23. update() {
  24. this.x += this.vx;
  25. if (this.x > cvs.width) {
  26. this.vx = -1;
  27. }
  28. if (this.x < 0) {
  29. this.vx = 1;
  30. }
  31.  
  32. this.smokes.push(new Smoke(this.x, this.y));
  33. for (let i = 0; i < this.smokes.length; i++) {
  34. let isDead = this.smokes[i].update();
  35. if (isDead) {
  36. this.smokes.splice(i, 1);
  37. i--;
  38. }
  39. }
  40. }
  41. draw() {
  42. ctx.fillStyle = "#803C49";
  43. ctx.fillRect(this.x - 5, this.y, 10, 30);
  44. for (let i = 0; i < this.smokes.length; i++) {
  45. this.smokes[i].draw();
  46. }
  47. }
  48. }
  49.  
  50. class Smoke {
  51. constructor(x, y) {
  52. this.x = x;
  53. this.y = y;
  54. this.size = 20;
  55. this.alpha = 1;
  56. this.vx = Math.random() * 0.8 - 0.4; // -0.3 ~ 0.5
  57. this.vy = Math.random() * 1.5 - 2; // -2 ~ -0.5
  58. }
  59. update() {
  60. this.x += this.vx;
  61. this.y += this.vy;
  62. this.size += 0.25;
  63. this.alpha -= 0.009;
  64. return this.alpha < 0.05;
  65. }
  66. draw() {
  67. ctx.globalAlpha = this.alpha;
  68. ctx.drawImage(smokeImg,
  69. this.x - (this.size / 2),
  70. this.y - (this.size / 2),
  71. 20, 20
  72. );
  73. }
  74. }
  75.  
  76. class Rect {
  77. constructor(x, y) {
  78. this.x = x;
  79. this.y = y;
  80. this.vx = (Math.random() - 0.5) * 4; // 隨機範圍在 -2 到 2 之間
  81. this.vy = (Math.random() - 0.5) * 2; // 隨機範圍在 -1 到 1 之間
  82. }
  83. update() {
  84. this.x = this.x + this.vx;
  85. this.y = this.y + this.vy;
  86. this.vy = this.vy + 0.01; // 重力加速度
  87. //return 是否死掉
  88. return this.x > cvs.width || this.y > cvs.height;
  89. }
  90. draw() {
  91. ctx.fillRect(this.x, this.y, 4, 4);
  92. ctx.fillStyle = "#77bfd9";
  93. }
  94. }
  95. // 分子系統 Particle System
  96. let cvs = document.querySelector("#cvs"); // 取得 canvas 物件
  97. let ctx = cvs.getContext("2d"); // 取得對應的 context 物件
  98. let particles = [new Torch(cvs.width / 2, 400)]; // 建立一個空陣列
  99. function refresh() {
  100. // 更新資料
  101. for (let i = 0; i < particles.length; i++) {
  102. let isDead = particles[i].update();
  103. if (isDead) {
  104. particles.splice(i, 1);
  105. i--;
  106. }
  107. }
  108. // 清空畫面
  109. ctx.clearRect(0, 0, cvs.width, cvs.height);
  110. // 根據新的資料重新畫過
  111. for (let i = 0; i < particles.length; i++) {
  112. particles[i].draw();
  113. }
  114. }
  115. // 預先載入需要的圖片
  116. let smokeImg = new Image();
  117. smokeImg.src = "fire.png";
  118. smokeImg.addEventListener("load", function () {
  119. window.setInterval(refresh, 10);
  120. });
  121.  
  122. </script>
  123. </body>
  124.  
  125. </html>





自由新增會移動的火把


  1.  
  2. <!DOCTYPE html>
  3. <html lang="en">
  4.  
  5. <head>
  6. <meta charset="UTF-8">
  7. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  8. <title>Canvas 繪圖</title>
  9. </head>
  10.  
  11. <body>
  12. <h3>Canvas 繪圖</h3>
  13. <canvas width="600" height="600" id="cvs" style="border: 1px solid black;"></canvas>
  14.  
  15. <script>
  16.  
  17. class Torch {
  18. constructor(x, y) {
  19. this.x = x;
  20. this.vx = 1;
  21. this.y = y;
  22. this.smokes = [];
  23. }
  24. update() {
  25. this.x += this.vx;
  26. if (this.x > cvs.width) {
  27. this.vx = -1;
  28. }
  29. if (this.x < 0) {
  30. this.vx = 1;
  31. }
  32.  
  33. this.smokes.push(new Smoke(this.x, this.y));
  34. for (let i = 0; i < this.smokes.length; i++) {
  35. let isDead = this.smokes[i].update();
  36. if (isDead) {
  37. this.smokes.splice(i, 1);
  38. i--;
  39. }
  40. }
  41. }
  42. draw() {
  43. ctx.fillStyle = "#803C49";
  44. ctx.fillRect(this.x - 5, this.y, 10, 30);
  45. for (let i = 0; i < this.smokes.length; i++) {
  46. this.smokes[i].draw();
  47. }
  48. }
  49. }
  50.  
  51. class Smoke {
  52. constructor(x, y) {
  53. this.x = x;
  54. this.y = y;
  55. this.size = 20;
  56. this.alpha = 1;
  57. this.vx = Math.random() * 0.8 - 0.4; // -0.3 ~ 0.5
  58. this.vy = Math.random() * 1.5 - 2; // -2 ~ -0.5
  59. }
  60. update() {
  61. this.x += this.vx;
  62. this.y += this.vy;
  63. this.size += 0.25;
  64. this.alpha -= 0.009;
  65. return this.alpha < 0.05;
  66. }
  67. draw() {
  68. ctx.globalAlpha = this.alpha;
  69. ctx.drawImage(smokeImg,
  70. this.x - (this.size / 2),
  71. this.y - (this.size / 2),
  72. 20, 20
  73. );
  74. }
  75. }
  76.  
  77. class Rect {
  78. constructor(x, y) {
  79. this.x = x;
  80. this.y = y;
  81. this.vx = (Math.random() - 0.5) * 4; // 隨機範圍在 -2 到 2 之間
  82. this.vy = (Math.random() - 0.5) * 2; // 隨機範圍在 -1 到 1 之間
  83. }
  84. update() {
  85. this.x = this.x + this.vx;
  86. this.y = this.y + this.vy;
  87. this.vy = this.vy + 0.01; // 重力加速度
  88. //return 是否死掉
  89. return this.x > cvs.width || this.y > cvs.height;
  90. }
  91. draw() {
  92. ctx.fillRect(this.x, this.y, 4, 4);
  93. ctx.fillStyle = "#77bfd9";
  94. }
  95. }
  96. // 分子系統 Particle System
  97. let cvs = document.querySelector("#cvs"); // 取得 canvas 物件
  98. let ctx = cvs.getContext("2d"); // 取得對應的 context 物件
  99. cvs.addEventListener("click", function (e) {
  100. particles.push(new Torch(e.offsetX, e.offsetY));
  101. });
  102. let particles = [new Torch(cvs.width / 2, 400)]; // 建立一個空陣列
  103. function refresh() {
  104. // 更新資料
  105. for (let i = 0; i < particles.length; i++) {
  106. let isDead = particles[i].update();
  107. if (isDead) {
  108. particles.splice(i, 1);
  109. i--;
  110. }
  111. }
  112. // 清空畫面
  113. ctx.clearRect(0, 0, cvs.width, cvs.height);
  114. // 根據新的資料重新畫過
  115. for (let i = 0; i < particles.length; i++) {
  116. particles[i].draw();
  117. }
  118. }
  119. // 預先載入需要的圖片
  120. let smokeImg = new Image();
  121. smokeImg.src = "fire.png";
  122. smokeImg.addEventListener("load", function () {
  123. window.setInterval(refresh, 10);
  124. });
  125.  
  126. </script>
  127. </body>
  128.  
  129. </html>





關於作者