大家好,今日我向大家共享一个根据 Vue3 ts canvas 完结的落叶飘过作用。当然这儿并没有做一些根底的知识的科普,咱们考究完结的逻辑。(多多包容)
能够用在看不见的当地,因为看得见的当地不太好意思用。
首要,咱们先创立根底的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 更更新叶子的方位
接下来,咱们一步一步来,从声明一个目标开端
声明叶子目标
首要上图,让咱们看看叶子长啥样
能够五颜六色,可是不要绿色就行
咱们看到首要一个叶子,他需求是椭圆的(也不肯定,大佬们弄个奇形怪状的都行),一个这种形状的叶子,需求什么参数捏?
需求 圆角,巨细,色彩,透明度等,所谓咱们底下就先定义了一个目标,并声明晰一些特点,以便于后边运用,
// 创立叶子目标,并声明一些必要参数
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)方位,半径分别是radiusX和radiusY,按照anticlockwise(默许顺时针)指定的方向,从startAngle开端制作,到endAngle结束。
语法
void ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
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();
}
}
这样咱们就制作了一个椭圆,哦不 叶子 (变绿了 嘿嘿)
处理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函数等),详细能够百度看看,我怕我讲不清楚,误导了大家。
终究作用
致谢
谢谢各位看官老爷,有啥别致的想法或许有啥意见,能够谈论区评论