【Canvas】使用Vue3+TS+Canvas实现星星连线

1.效果预览

动画.gif

👉 项目地址

2.实现思路

1.绘制一颗星星,让这颗星星在画布中运动
2.在画布中绘制一组星星,每颗星星都在画布中运动
3.当星星之间的距离小于某个数值时进行连线
4.当鼠标点击画布时,新增5颗星星到画布中
5.鼠标移入画布时,在鼠标处新建一颗星星,跟随鼠标移动

3.实现代码

1.画布设置

  • 新建画布
<div class="star_content">
  <canvas id="star_canvas" ref="star_canvas"></canvas>
</div>
  • 给画布设置背景,也可以选择图片背景
#star_canvas {
  background-color: #333333;
}
  • 设置画布大小,获取上下文对象
const star_canvas = ref<HTMLCanvasElement>();
star_canvas.value!.width = 1000;
star_canvas.value!.height = 800;
let ctx: CanvasRenderingContext2D;
ctx = star_canvas.value!.getContext("2d") as CanvasRenderingContext2D;
  • 清空画布
ctx.clearRect(0, 0, 1000, 800);
  • 设置画笔的边缘和填充颜色
ctx.fillStyle = "white";
ctx.strokeStyle = "white";

2.绘制单个移动的星星

  • 每个星星的结构包含坐标、星星半径和移动速度
interface Star {
  x: number;
  y: number;
  r: number;
  speedX: number;
  speedY: number;
}
  • 星星的坐标、半径和移动速度都随机生成
const randomSpeed = () => {
  return Math.random() * 3 * Math.pow(-1, Math.round(Math.random()));
};
  • 生成一颗星星
const generateStar = () => {
  const star: Star = {
    x: 0,
    y: 0,
    r: 0,
    speedX: 0,
    speedY: 0,
  };
  star.x = Math.random() * 1000;
  star.y = Math.random() * 800;
  star.r = Math.random() * 5;
  star.speedX = randomSpeed();
  star.speedY = randomSpeed();
  return star;
};
let star = generateStar();
  • 将星星绘制在画布上
const Draw = (ctx: CanvasRenderingContext2D, star: Star) => {
  ctx.beginPath();
  ctx.arc(star.x, star.y, star.r, 0, Math.PI * 2);
  ctx.fill();
  ctx.closePath();
};
Draw(ctx, star);
  • 使星星移动,碰到画布边缘时,改变星星的移动方向
const Move = (star: Star) => {
  star.x -= star.speedX;
  star.y -= star.speedY;
  if (star.x < 0 || star.x > 1000) {
    star.speedX *= -1;
  }
  if (star.y < 0 || star.y > 800) {
    star.speedY *= -1;
  }
};
let timer;
timer = setInterval(() => {
  ctx.clearRect(0, 0, 1000, 800);
  Draw(ctx, star);
  Move(star);
}, 50);

3.绘制一组星星,在画布中移动

let starArr: Star[] = [];
for (let i = 0; i < 20; i++) {
let star = generateStar();
  starArr.push(star);
}
timer = setInterval(() => {
  ctx.clearRect(0, 0, 1000, 800);
  starArr.forEach((star: Star) => {
    Draw(ctx, star);
    Move(star);
  });
}, 50);

4.判断星星之间的距离,进行连线

  • 两颗星星进行连线
const DrawLine = (startX: number, startY: number, endX: number, endY: number, ctx: CanvasRenderingContext2D) => {
  ctx.beginPath();
  ctx.moveTo(startX, startY);
  ctx.lineTo(endX, endY);
  ctx.stroke();
  ctx.closePath();
};
  • 判断距离
starArr.forEach((star: Star, index: number) => {
  for (let i = index + 1; i < starArr.length; i++) {
    if (Math.abs(star.x - starArr[i].x) < 50 && Math.abs(star.y - starArr[i].y) < 50) {
      DrawLine(star.x, star.y, starArr[i].x, starArr[i].y, ctx);
    }
  }
});

5.点击添加星星和鼠标星星跟随

  • 点击添加星星
star_canvas.value!.onclick = e => {
  for (let i = 0; i < 5; i++) {
    let star = generateStar();
    star.x = e.offsetX;
    star.y = e.offsetY;
    starArr.push(star);
  }
};
  • 鼠标跟随
const mouse_star = generateStar();
mouse_star.speedX = 0;
mouse_star.speedY = 0;
star_canvas.value!.onmousemove = e => {
  mouse_star.x = e.offsetX;
  mouse_star.y = e.offsetY;
};
timer = setInterval(() => {
  ctx.clearRect(0, 0, 1000, 800);
  // 鼠标star移动
  Draw(ctx, mouse_star);
  // star移动
  starArr.forEach((star: Star) => {
    Draw(ctx, star);
    Move(star);
  });

  // 比较star和所有其他star的距离,小于50连线
  starArr.forEach((star: Star, index: number) => {
    for (let i = index + 1; i < starArr.length; i++) {
      if (Math.abs(star.x - starArr[i].x) < 50 && Math.abs(star.y - starArr[i].y) < 50) {
        DrawLine(star.x, star.y, starArr[i].x, starArr[i].y, ctx);
      }
    }
  });
  // 比较鼠标star和所有star的距离
  for (let i = 0; i < starArr.length; i++) {
    if (Math.abs(mouse_star.x - starArr[i].x) < 50 && 
  Math.abs(mouse_star.y - starArr[i].y) < 50) {
      DrawLine(mouse_star.x, mouse_star.y, starArr[i].x, 
  starArr[i].y, ctx);
    }
  }
}, 50);
  • 离开页面前清除定时器
onBeforeUnmount(() => {
  clearInterval(timer);
});

3.全部代码

  • index.vue
<template>
  <div class="star_content">
    <canvas id="star_canvas" ref="star_canvas"></canvas>
  </div>
</template>

<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from "vue";
import { Star, Move, Draw, DrawLine, generateStar } from "./index";

const star_canvas = ref<HTMLCanvasElement>();
let ctx: CanvasRenderingContext2D;

let timer: any;

const initStar = () => {
  ctx = star_canvas.value!.getContext("2d") as CanvasRenderingContext2D;
  ctx.fillStyle = "white";
  ctx.strokeStyle = "white";
  let starArr: Star[] = [];
  for (let i = 0; i < 20; i++) {
    let star = generateStar();
    starArr.push(star);
  }

  // 鼠标star
  const mouse_star = generateStar();
  mouse_star.speedX = 0;
  mouse_star.speedY = 0;
  // 鼠标star跟随移动
  star_canvas.value!.onmousemove = e => {
    mouse_star.x = e.offsetX;
    mouse_star.y = e.offsetY;
  };

  timer = setInterval(() => {
    ctx.clearRect(0, 0, 1000, 800);
    // 鼠标star移动
    Draw(ctx, mouse_star);
    // star移动
    starArr.forEach((star: Star) => {
      Draw(ctx, star);
      Move(star);
    });

    // 比较star和所有其他star的距离,小于50连线
    starArr.forEach((star: Star, index: number) => {
      for (let i = index + 1; i < starArr.length; i++) {
        if (Math.abs(star.x - starArr[i].x) < 50 && Math.abs(star.y - starArr[i].y) < 50) {
          DrawLine(star.x, star.y, starArr[i].x, starArr[i].y, ctx);
        }
      }
    });
    // 比较鼠标star和所有star的距离
    for (let i = 0; i < starArr.length; i++) {
      if (Math.abs(mouse_star.x - starArr[i].x) < 50 && Math.abs(mouse_star.y - starArr[i].y) < 50) {
        DrawLine(mouse_star.x, mouse_star.y, starArr[i].x, starArr[i].y, ctx);
      }
    }
  }, 50);

  // 点击添加star
  star_canvas.value!.onclick = e => {
    for (let i = 0; i < 5; i++) {
      let star = generateStar();
      star.x = e.offsetX;
      star.y = e.offsetY;
      starArr.push(star);
    }
  };
};
const initCanvas = () => {
  ctx = star_canvas.value?.getContext("2d") as CanvasRenderingContext2D;
  star_canvas.value!.width = 1000;
  star_canvas.value!.height = 800;
  initStar();
};
onMounted(() => {
  initCanvas();
});
onBeforeUnmount(() => {
  clearInterval(timer);
});
</script>

<style lang="less" scoped>
#star_canvas {
  background-color: #333333;
}
</style>
  • index.ts
export interface Star {
  x: number;
  y: number;
  r: number;
  speedX: number;
  speedY: number;
}

export const generateStar = () => {
  const star: Star = {
    x: 0,
    y: 0,
    r: 0,
    speedX: 0,
    speedY: 0,
  };
  star.x = Math.random() * 1000;
  star.y = Math.random() * 800;
  star.r = Math.random() * 5;
  star.speedX = randomSpeed();
  star.speedY = randomSpeed();
  return star;
};

export const randomSpeed = () => {
  return Math.random() * 3 * Math.pow(-1, Math.round(Math.random()));
};

export const Draw = (ctx: CanvasRenderingContext2D, star: Star) => {
  ctx.beginPath();
  ctx.arc(star.x, star.y, star.r, 0, Math.PI * 2);
  ctx.fill();
  ctx.closePath();
};

export const Move = (star: Star) => {
  star.x -= star.speedX;
  star.y -= star.speedY;
  if (star.x < 0 || star.x > 1000) {
    star.speedX *= -1;
  }
  if (star.y < 0 || star.y > 800) {
    star.speedY *= -1;
  }
};

export const DrawLine = (startX: number, startY: number, endX: number, endY: number, ctx: CanvasRenderingContext2D) => {
  ctx.beginPath();
  ctx.moveTo(startX, startY);
  ctx.lineTo(endX, endY);
  ctx.stroke();
  ctx.closePath();
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容