Vue3+不正规ts实现不正规的落叶飘落效果

Vue3+不正规ts实现不正规的落叶飘落效果

Vue3+不正规ts完结不正规的落叶飘落作用

大家好,今日我向大家共享一个根据 Vue3 ts canvas 完结的落叶飘过作用。当然这儿并没有做一些根底的知识的科普,咱们考究完结的逻辑。(多多包容)

Vue3+不正规ts完结不正规的落叶飘落作用

能够用在看不见的当地,因为看得见的当地不太好意思用。

首要,咱们先创立根底的vue文件结构

创立Leaf.vue,没啥特别的,就一个vue文件,声明晰一些函数,调用执行而已(这儿最特别的应该便是各位看官老爷了)


<template>
	<div ref="dropLeafContainer" class="drop-leaf-contaier">
		<canvas ref="canvasRef"></canvas>
	</div>
</template>
<script lang="ts" setup>
import { ref, getCurrentInstance, onMounted } from 'vue'
import { Leaf } from './Leaf'
const colorList = [
  "#1abc9c",
  "#2ecc71",
  "#3498db",
  "#9b59b6",
  "#f1c40f",
  "#e67e22",
  "#e74c3c",
  "#34495e",
];
interface IProps {
	width?: number
	height?: number
	full?: Boolean
}
const props = withDefaults(defineProps<IProps>(), {
	height: 400,
	width: document.body.clientWidth,
})
let canvasRef: HTMLCanvasElement | null = null
let canvasCtx = null
let leafList: Array<object> = []
// 开端动画
const startAnimate = () => {
	// canvasCtx.clearRect(0, 0, canvasRef.width, canvasRef.height)
	leafList.forEach((leaf) => {
		// leaf.update()
		leaf.draw()
	})
	// requestAnimationFrame(startAnimate)
}
// 初始化canvas
const initCanvas = () => {
    canvasRef.height = props.height
    canvasRef.width = props.width
    canvasCtx = canvasRef?.getContext('2d')
    const x = Math.random() * props.width;
    const y = 100;
    const speedX = Math.random() - 0.5;
    const speedY = Math.random() * 2   1;
    const radiusX = Math.random() * 5   5;
    const radiusY = Math.random()   5;
    const rotation = (Math.random() * 30) / Math.PI;
    const color = colorList[Math.floor(Math.random() * 9)];
    const leaf = new Leaf(
      canvasCtx,
      x,
      y,
      radiusX,
      radiusY,
      speedX,
      speedY,
      color,
      0,
      rotation,
      1,
      props.width,
      props.height,
    );
    leafList = [leaf]
    startAnimate()
}
onMounted(() => {
	const { ctx } = getCurrentInstance()
	canvasRef = ctx.$refs.canvasRef
	initCanvas()
})
</script>
<style lang="scss" scoped>
.drop-leaf-contaier {
	position: absolute;
	top: 0px;
	z-index: -1;
	width: 100%;
}
</style>

其次,创立单个叶子玩玩

相信眼尖的朋友们,应该有看到了,上方咱们引用了一个 .Leaf 的文件。猜猜这个文件哪来的。(我猜应该是本地声明的吧)

接下来,咱们先创立一个小叶子玩玩

创立叶子

正常逻辑下,咱们叶子需求包含以下办法

  • draw 制作一个叶子
  • resize 屏幕宽高度变化时,更新宽度高度
  • update 更更新叶子的方位

接下来,咱们一步一步来,从声明一个目标开端

声明叶子目标

首要上图,让咱们看看叶子长啥样

Vue3+不正规ts完结不正规的落叶飘落作用

能够五颜六色,可是不要绿色就行

咱们看到首要一个叶子,他需求是椭圆的(也不肯定,大佬们弄个奇形怪状的都行),一个这种形状的叶子,需求什么参数捏?

需求 圆角,巨细,色彩,透明度等,所谓咱们底下就先定义了一个目标,并声明晰一些特点,以便于后边运用,

// 创立叶子目标,并声明一些必要参数
class Leaf {
  ctx: any // canvas ctx实例
  x: number // 叶子的x坐标
  y: number // 叶子的y坐标
  radiusX: number // 叶子的x圆角
  radiusY: number // 叶子的y圆角
  speedX: number // 叶子的x速度
  speedY: number // 叶子的y速度
  color: string // 色彩
  rotate: number // 旋转视点
  globalAlpha: number // 透明度
  canvasHeight: number // 画布高度
  canvasWidth: number // 画布宽度
  dir: number // 方向 先往左仍是先往右
  deg: number // 移动视点总和
  delDeg: number // 移动视点
  constructor(
    ctx,
    x,
    y,
    radiusX,
    radiusY,
    speedX,
    speedY,
    color,
    deg,
    rotate,
    globalAlpha,
    canvasWidth,
    canvasHeight,
  ) {
    this.ctx = ctx;
    this.x = x;
    this.y = y;
    this.radiusX = radiusX;
    this.radiusY = radiusY;
    this.speedX = 1;
    this.speedY = speedY;
    this.color = color;
    this.rotate = rotate;
    this.globalAlpha = globalAlpha;
    this.canvasHeight = canvasHeight;
    this.canvasWidth = canvasWidth;
    this.dir = Math.random() - 0.5 > 0 ? 1 : -1;
    this.deg = Math.random();
    this.delDeg = Math.random() * 0.1;
  }
}

处理draw制作办法

主要是运用canvas的ellipse办法

CanvasRenderingContext2D.ellipse() 是 Canvas 2D API 添加椭圆途径的办法。椭圆的圆心在(x,y)方位,半径分别是radiusXradiusY,按照anticlockwise(默许顺时针)指定的方向,从startAngle开端制作,到endAngle结束。

语法

void ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);

链接 developer.mozilla.org/zh-CN/docs/…

class Leaf {
    ...
  draw() {
    this.ctx.beginPath();
    // 设置透明度
    this.ctx.globalAlpha = this.globalAlpha;
    // 设置填充色彩
    this.ctx.fillStyle = this.color;
    // 制作椭圆
    this.ctx.ellipse(
      this.x,
      this.y,
      this.radiusX,
      this.radiusY,
      this.rotate,
      0,
      2 * Math.PI,
      false,
    );
    // ctx.rotate(this.deg * Math.PI / 180)
    this.ctx.fill();
    this.ctx.closePath();
  }
}

这样咱们就制作了一个椭圆,哦不 叶子 (变绿了 嘿嘿)

Vue3+不正规ts完结不正规的落叶飘落作用

处理update办法

针对 叶子的移动办法咱们需求考虑一些点

  • 鸿沟状况
  • 过渡状况
  • 假如做飘落作用

我或许考虑不周,现在只想到这些

针对鸿沟的考虑,遵从以下准则

  • 假如 y大于画布高度,就重置其所有 random 参数,当成是一个新的叶子来处理,当然色彩是不变的,没必要变对吧
  • 假如 x大于小于画布宽度,都进行取反处理

针对过渡的状况,现在是让滚动到底下的元素添加透明度,透明度核算规矩

  • 1 – 当时的y坐标 / 画布总的高度

假如做飘落作用

  • 之前有想过用贝塞尔曲线(不太娴熟)
  • 退而求其次,根据 Math.sin 函数 让其x方位进行幅度化的更改
class Leaf {
  ...
  update() {
    // 假如y的坐标大于画布高度,说明移动到画布外了
    if (this.y > this.canvasHeight) {
      this.y = Math.random() * -100; // 重置坐标y的值
      this.x = Math.random() * this.canvasWidth; // 重置x的值
      this.deg = Math.random(); // 移动视点总和
      this.delDeg = Math.random() * 0.1; // 移动视点
    }
    // 假如x坐标超出画布,赋相反方向
    if (this.x >= this.canvasWidth) {
      this.speedX = -Math.abs(this.speedX);
    }
    if (this.x <= 0) {
      this.speedX = Math.abs(this.speedX);
    }
    // 添加透明度
    this.globalAlpha = 1 - this.y / this.canvasHeight;
    // 移动y
    this.y  = this.speedY;
    // 移动x做圆弧飘落作用
    this.x = this.dir
      ? this.x   Math.sin(this.deg)
      : this.x - Math.sin(this.deg);
    this.deg  = this.delDeg;
    // 制作
    this.draw();
  }
}

这样你就拥有了一个能够下落的树叶,可是现在 vue内容需求调整下

...
// 开端动画
const startAnimate = () => {
	canvasCtx.clearRect(0, 0, canvasRef.width, canvasRef.height)
	leafList.forEach((leaf) => {
		leaf.update()
		leaf.draw()
	})
	requestAnimationFrame(startAnimate)
}

页面作用

处理resize办法

class Leaf {
  ...
  resize(canvasWidth: number, canvasHeight: number) {
    this.canvasHeight = canvasHeight;
    this.canvasWidth = canvasWidth;
  }
}

咱们把创立叶子的办法抽离到Leaf文件中

const colorList = [
  "#1abc9c",
  "#2ecc71",
  "#3498db",
  "#9b59b6",
  "#f1c40f",
  "#e67e22",
  "#e74c3c",
  "#34495e",
];
export const createRandomLeaf = (num: number, ctx: any, canvasWidth: number, canvasHeight: number) => {
  const temp = [];
  for (let i = 0; i < num; i  ) {
    const x = Math.random() * canvasWidth;
    const y = -Math.random() * 100;
    const speedX = Math.random() - 0.5;
    const speedY = Math.random() * 2   1;
    const radiusX = Math.random() * 5   5;
    const radiusY = Math.random()   5;
    const rotation = (Math.random() * 30) / Math.PI;
    const color = colorList[Math.floor(Math.random() * 9)];
    const leaf = new Leaf(
      ctx,
      x,
      y,
      radiusX,
      radiusY,
      speedX,
      speedY,
      color,
      0,
      rotation,
      1,
      canvasWidth,
      canvasHeight,
    );
    leaf.draw();
    temp.push(leaf);
  }
  return temp;
};

修正vue文件代码,并做一些相关优化,终究vue代码如下

<template>
	<div ref="dropLeafContainer" class="drop-leaf-contaier">
		<canvas ref="canvasRef"></canvas>
	</div>
</template>
<script lang="ts" setup>
import { ref, getCurrentInstance, onMounted, onUnmounted } from 'vue'
import { createRandomLeaf } from './Leaf'
import { debounce } from '@/utils'
interface IProps {
	width?: number
	height?: number
	full?: Boolean
}
const props = withDefaults(defineProps<IProps>(), {
	height: 400,
	width: document.body.clientWidth,
})
let canvasRef: HTMLCanvasElement | null = null
let canvasCtx = null
let dropLeafContainer: HTMLDivElement | null = null
let dropLeafContainerObserve: any = null
let leafList: Array<object> = []
const startAnimate = () => {
	canvasCtx.clearRect(0, 0, canvasRef.width, canvasRef.height)
	leafList.forEach((leaf) => {
		leaf.update()
		leaf.draw()
	})
	requestAnimationFrame(startAnimate)
}
const initCanvas = () => {
	canvasRef.height = props.height
	canvasRef.width = props.width
	canvasCtx = canvasRef?.getContext('2d')
	leafList = createRandomLeaf(50, canvasCtx, props.width, props.height)
	startAnimate()
}
const reszieChange = ([{ contentRect }]) => {
	if (leafList.length && contentRect.width) {
		canvasRef.width = contentRect.width
		leafList.forEach((leaf) => {
			leaf.resize(contentRect.width, props.height)
		})
	}
}
const listenerResize = () => {
	dropLeafContainerObserve = new ResizeObserver(debounce(reszieChange, 200))
	dropLeafContainerObserve.observe(dropLeafContainer)
}
const unListenerResize = () => {
	if (dropLeafContainerObserve && dropLeafContainer) {
		dropLeafContainerObserve.unobserve(dropLeafContainer)
		dropLeafContainerObserve = null
		dropLeafContainer = null
		leafList = []
	}
}
onMounted(() => {
	const { ctx } = getCurrentInstance()
	canvasRef = ctx.$refs.canvasRef
	dropLeafContainer = ctx.$refs.dropLeafContainer
	listenerResize()
	initCanvas()
})
onUnmounted(() => {
	unListenerResize()
})
</script>
<style lang="scss" scoped>
.drop-leaf-contaier {
	position: absolute;
	top: 0px;
	z-index: -1;
	width: 100%;
}
</style>

完整 Leaf.js代码

interface ILeaf {
  ctx: any;
}
export class Leaf {
  ctx: any // canvas ctx实例
  x: number // 叶子的x坐标
  y: number // 叶子的y坐标
  radiusX: number // 叶子的x圆角
  radiusY: number // 叶子的y圆角
  speedX: number // 叶子的x速度
  speedY: number // 叶子的y速度
  color: string // 色彩
  rotate: number // 旋转视点
  globalAlpha: number // 透明度
  canvasHeight: number // 画布高度
  canvasWidth: number // 画布宽度
  dir: number // 方向 先往左仍是先往右
  deg: number // 移动视点总和
  delDeg: number // 移动视点
  constructor(
    ctx,
    x,
    y,
    radiusX,
    radiusY,
    speedX,
    speedY,
    color,
    deg,
    rotate,
    globalAlpha,
    canvasWidth,
    canvasHeight,
  ) {
    this.ctx = ctx;
    this.x = x;
    this.y = y;
    this.radiusX = radiusX;
    this.radiusY = radiusY;
    this.speedX = 1;
    this.speedY = speedY;
    this.color = color;
    this.rotate = rotate;
    this.globalAlpha = globalAlpha;
    this.canvasHeight = canvasHeight;
    this.canvasWidth = canvasWidth;
    this.dir = Math.random() - 0.5 > 0 ? 1 : -1;
    this.deg = Math.random();
    this.delDeg = Math.random() * 0.1;
  }
  draw() {
    this.ctx.beginPath();
    this.ctx.globalAlpha = this.globalAlpha;
    this.ctx.fillStyle = this.color;
    // 制作椭圆
    this.ctx.ellipse(
      this.x,
      this.y,
      this.radiusX,
      this.radiusY,
      this.rotate,
      0,
      2 * Math.PI,
      false,
    );
    // ctx.rotate(this.deg * Math.PI / 180)
    this.ctx.fill();
    this.ctx.closePath();
  }
  resize(canvasWidth: number, canvasHeight: number) {
    this.canvasHeight = canvasHeight;
    this.canvasWidth = canvasWidth;
  }
  update() {
    // 假如y的坐标大于画布高度,说明移动到画布外了
    if (this.y > this.canvasHeight) {
      this.y = Math.random() * -100; // 重置坐标y的值
      this.x = Math.random() * this.canvasWidth; // 重置x的值
      this.deg = Math.random(); // 移动视点总和
      this.delDeg = Math.random() * 0.1; // 移动视点
    }
    // 假如x坐标超出画布,赋相反方向
    if (this.x >= this.canvasWidth) {
      this.speedX = -Math.abs(this.speedX);
    }
    if (this.x <= 0) {
      this.speedX = Math.abs(this.speedX);
    }
    // 添加透明度
    this.globalAlpha = 1 - this.y / this.canvasHeight;
    // 移动y
    this.y  = this.speedY;
    // 移动x坐圆弧飘落作用
    this.x = this.dir
      ? this.x   Math.sin(this.deg)
      : this.x - Math.sin(this.deg);
    this.deg  = this.delDeg;
    // 制作
    this.draw();
  }
}
const colorList = [
  "#1abc9c",
  "#2ecc71",
  "#3498db",
  "#9b59b6",
  "#f1c40f",
  "#e67e22",
  "#e74c3c",
  "#34495e",
];
export const createRandomLeaf = (num: number, ctx: any, canvasWidth: number, canvasHeight: number) => {
  const temp = [];
  for (let i = 0; i < num; i  ) {
    const x = Math.random() * canvasWidth;
    const y = -Math.random() * 100;
    const speedX = Math.random() - 0.5;
    const speedY = Math.random() * 2   1;
    const radiusX = Math.random() * 5   5;
    const radiusY = Math.random()   5;
    const rotation = (Math.random() * 30) / Math.PI;
    const color = colorList[Math.floor(Math.random() * 9)];
    const leaf = new Leaf(
      ctx,
      x,
      y,
      radiusX,
      radiusY,
      speedX,
      speedY,
      color,
      0,
      rotation,
      1,
      canvasWidth,
      canvasHeight,
    );
    leaf.draw();
    temp.push(leaf);
  }
  return temp;
};

总结

以上便是完结落叶作用的过程,有些小细点没有细扣(例如 sin函数等),详细能够百度看看,我怕我讲不清楚,误导了大家。

终究作用

Vue3+不正规ts完结不正规的落叶飘落作用

致谢

谢谢各位看官老爷,有啥别致的想法或许有啥意见,能够谈论区评论

Vue3+不正规ts完结不正规的落叶飘落作用