Vue3 + TS 浩瀚星空动画效果 组件封装

在这里插入图片描述

最近看到了一个浩瀚星空效果图,来自于B站UP主山羊の前端小窝,于是照着效果封装了一个 vue组件。

废话不多说,一起来看看吧!

效果展示

浩瀚星空

源码

如果当做背景,需要把 .g-background 样式中的 z-index 属性打开,这个时候鼠标动画不会生效。

App.vue

<script setup lang="ts">
import Background from "@/components/BackgroundView.vue";
</script>

<template>
  <Background></Background>
  <RouterView />
</template>

<template>
  <Background>
    <RouterView />
  </Background>
</template>

<style scoped></style>

BackgroundView.vue

<script lang="ts" setup>
const options = {
  STAR_COLOR: "white", // 星星颜色
  STAR_SIZE: 3, // 星星大小
  STAR_NUMBER: (window.innerWidth + window.innerHeight) / 8, // 星星数量
  STAR_MIN_SCALE: 0.2, // 星星最小缩放比例
  OVERFLOW_THRESHOLD: 50, // 溢出阈值
  touchInput: false,
  scale: 1, // 定义缩放比例     device pixel ratio
  width: 0, // 画布宽度
  height: 0, // 画布高度
  mouseX: 0,
  mouseY: 0,
};
let velocity = { x: 0, y: 0, tx: 0, ty: 0, z: 0.0009 }; // 速度对象
type STAR = { x: number; y: number; z: number };
let stars = <STAR[]>[];
let context = <CanvasRenderingContext2D | null>null;
const canvas = useTemplateRef("canvas");
starInit();
onMounted(() => {
  context = canvas.value!.getContext("2d");
  canvas.value!.width = options.width;
  canvas.value!.height = options.height;
  animation();
  canvas.value!.onmousemove = mouseMove;
  canvas.value!.ontouchmove = touchMove;
  canvas.value!.ontouchend = mouseLeave;
  document.onmouseleave = mouseLeave;
});

// 动画运行
function animation() {
  // 清空画布
  context!.clearRect(0, 0, options.width, options.height);
  starDrawing();
  starUpdate();
  // 请求下一帧动画
  requestAnimationFrame(animation);
}

// 生成星星
function starInit() {
  // 获取设备像素比例
  options.scale = window.devicePixelRatio || 1;
  // 设置画布的宽高
  options.width = window.innerWidth * options.scale;
  options.height = window.innerHeight * options.scale;
  for (let i = 0; i < options.STAR_NUMBER; i++) {
    stars.push({
      x: Math.random() * options.width,
      y: Math.random() * options.height,
      z: options.STAR_MIN_SCALE + Math.random() * (1 - options.STAR_MIN_SCALE),
    });
  }
}

// 绘制星星
function starDrawing() {
  for (let star of stars) {
    // 设置星星样式
    // context!.beginPath();
    context!.lineCap = "round";
    context!.lineWidth = options.STAR_SIZE * star.z * options.scale;
    context!.globalAlpha = 0.3 + 0.7 * Math.random();
    context!.strokeStyle = options.STAR_COLOR;
    // 绘制星星路径
    context!.beginPath();
    context!.moveTo(star.x, star.y);
    let tailX = velocity.x * 2,
      tailY = velocity.y * 2;
    // 计算星星的尾巴坐标
    if (Math.abs(tailX) < 0.5) tailX = 0.5;
    if (Math.abs(tailY) < 0.5) tailY = 0.5;
    // 绘制星星的尾巴
    context!.lineTo(star.x + tailX, star.y + tailY);
    context!.stroke();
  }
}
// 更新星星位置和速度
function starUpdate() {
  // 移动速度
  velocity.tx *= 0.96;
  velocity.ty *= 0.96;
  velocity.x += (velocity.tx - velocity.x) * 0.8;
  velocity.y += (velocity.ty - velocity.y) * 0.8;
  for (let star of stars) {
    // 根据速度和缩放比例更新星星的位置
    star.x += velocity.x * star.z;
    star.y += velocity.y * star.z;
    // 使星星围绕屏幕中心旋转
    star.x += (star.x - options.width / 2) * velocity.z * star.z;
    star.y += (star.y - options.height / 2) * velocity.z * star.z;
    // 更新星星的缩放比例
    star.z += velocity.z;
    // 如果星星超出屏幕范围,则重新放置到屏幕上
    if (
      star.x < -options.OVERFLOW_THRESHOLD ||
      star.x > options.width + options.OVERFLOW_THRESHOLD ||
      star.y < -options.OVERFLOW_THRESHOLD ||
      star.y > options.height + options.OVERFLOW_THRESHOLD
    ) {
      recycleStar(star);
    }
  }
}
// 回收星星并重新放到新的位置
function recycleStar(star: STAR) {
  let direction = "Z", // 初始化方向
    vx = Math.abs(velocity.x),
    vy = Math.abs(velocity.y);
  // 如果速度大于1,则根据速度的大小随机确定方向
  if (vx > 1 || vy > 1) {
    let axis;
    // 如果水平速度大于垂直速度,则根据水平速度的比例随机确定水平或垂直方向
    if (vx > vy) axis = Math.random() < vx / (vx + vy) ? "h" : "v";
    else axis = Math.random() < vy / (vx + vy) ? "v" : "h";
    // 根据方向确定具体的移动方向
    if (axis == "h") direction = velocity.x > 0 ? "L" : "R";
    else direction = velocity.y > 0 ? "T" : "B";
  }
  // 随机设置星星的缩放比例
  star.z = Math.random() + (1 - Math.random()) * options.STAR_MIN_SCALE;
  switch (direction) {
    // 星星放置在屏幕中心
    case "Z":
      star.z = 0.1;
      star.x = options.width * Math.random();
      star.y = options.height * Math.random();
      break;
    // 星星放置在屏幕左侧
    case "L":
      star.x = -options.OVERFLOW_THRESHOLD;
      star.y = options.height * Math.random();
      break;
    // 星星放置在屏幕右侧
    case "R":
      star.x = options.width + options.OVERFLOW_THRESHOLD;
      star.y = options.height * Math.random();
      break;
    // 星星放置在屏幕顶部
    case "T":
      star.x = Math.random() * options.width;
      star.y = -options.OVERFLOW_THRESHOLD;
      break;
    // 星星放置在屏幕底部
    case "B":
      star.x = Math.random() * options.width;
      star.y = options.OVERFLOW_THRESHOLD + options.height;
      break;
  }
}
function mousePoint(x: number, y: number) {
  // 如果之前有记录鼠标指针的位置,则计算鼠标指针的移动距离,并更新速度
  if (options.mouseX && options.mouseY) {
    let ox = x - options.mouseX,
      oy = y - options.mouseY;
    velocity.tx = velocity.tx + (ox / 8) * options.scale * (options.touchInput ? 1 : -1);
    velocity.ty = velocity.ty + (oy / 8) * options.scale * (options.touchInput ? 1 : -1);
  }
  options.mouseX = x;
  options.mouseY = y;
}
function mouseMove(e: MouseEvent) {
  options.touchInput = false;
  mousePoint(e.clientX, e.clientY);
}
function mouseLeave() {
  options.mouseX = 0;
  options.mouseY = 0;
}
function touchMove(e: TouchEvent) {
  options.touchInput = true;
  mousePoint(e.touches[0].clientX, e.touches[0].clientY);
  e.preventDefault();
}
</script>

<template>
  <div class="g-background">
    <canvas ref="canvas" class="g-canvas"></canvas>
    <slot></slot>
  </div>
</template>

<style lang="scss" scoped>
.g-background {
  position: fixed;
  width: 100%;
  height: 100%;
  background: linear-gradient(-225deg, #231557 0%, #43107a 30%, #ff1361 100%);
  // z-index: -999;
  .g-canvas {
    position: absolute;
    width: 100%;
    height: 100%;
    z-index: inherit;
  }
}
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值