给页面添加粒子光影特效。欢迎访问个人的简历网站预览效果
本章涉及修改与新增的文件:main.ts
、App.vue
、utils
vue3-particles
tsparticles
插件 详细文档查看 tsParticles 官网npm i vue3-particles
npm i tsparticles
options.ts
// 在 utils 文件夹中 创建options.ts
export const options = {
"fullScreen": {
"zIndex": 1
},
"particles": {
"bounce": {
"horizontal": {
"random": {
"enable": false,
"minimumValue": 0.1
},
"value": 1
},
"vertical": {
"random": {
"enable": false,
"minimumValue": 0.1
},
"value": 1
}
},
"collisions": {
"absorb": {
"speed": 2
},
"bounce": {
"horizontal": {
"random": {
"enable": false,
"minimumValue": 0.1
},
"value": 1
},
"vertical": {
"random": {
"enable": false,
"minimumValue": 0.1
},
"value": 1
}
},
"enable": false,
"maxSpeed": 50,
"mode": "bounce",
"overlap": {
"enable": false,
"retries": 0
}
},
"color": {
"value": "#F29927",
},
"groups": {},
"move": {
"angle": {
"offset": 0,
"value": 50
},
"attract": {
"distance": 200,
"enable": false,
"rotate": {
"x": 3000,
"y": 3000
}
},
"center": {
"x": 50,
"y": 50,
"mode": "percent",
"radius": 0
},
"decay": 0,
"distance": {},
"direction": "none",
"drift": 0,
"enable": true,
"gravity": {
"acceleration": 9.81,
"enable": false,
"inverse": false,
"maxSpeed": 50
},
"path": {
"clamp": true,
"delay": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": 0
},
"enable": false,
"options": {}
},
"outModes": {
"default": "out",
"bottom": "out",
"left": "out",
"right": "out",
"top": "out"
},
"random": false,
"size": false,
"speed": 2,
"spin": {
"acceleration": 0,
"enable": false
},
"straight": false,
"trail": {
"enable": false,
"length": 10,
"fill": {}
},
"vibrate": false,
"warp": false
},
"number": {
"density": {
"enable": true,
"width": 1920,
"height": 1080
},
"limit": 0,
"value": 50
},
"opacity": {
"random": {
"enable": true,
"minimumValue": 0.5
},
"value": {
"min": 0.3,
"max": 1
},
"animation": {
"count": 0,
"enable": true,
"speed": 0.5,
"decay": 0,
"delay": 0,
"sync": false,
"mode": "auto",
"startValue": "random",
"destroy": "none",
"minimumValue": 0.3
}
},
"shape": {
"close": true,
"fill": true,
"options": {},
"type": "circle"
},
"size": {
"random": {
"enable": true,
"minimumValue": 1
},
"value": {
"min": 1,
"max": 3
},
"animation": {
"count": 0,
"enable": true,
"speed": 3,
"decay": 0,
"delay": 0,
"sync": false,
"mode": "auto",
"startValue": "random",
"destroy": "none",
"minimumValue": 1
}
},
"stroke": {
"width": 0
},
"zIndex": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": 0,
"opacityRate": 1,
"sizeRate": 1,
"velocityRate": 1
},
"destroy": {
"bounds": {},
"mode": "none",
"split": {
"count": 1,
"factor": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": 3
},
"rate": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": {
"min": 4,
"max": 9
}
},
"sizeOffset": true,
"particles": {}
}
},
"roll": {
"darken": {
"enable": false,
"value": 0
},
"enable": false,
"enlighten": {
"enable": false,
"value": 0
},
"mode": "vertical",
"speed": 25
},
"tilt": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": 0,
"animation": {
"enable": false,
"speed": 0,
"decay": 0,
"sync": false
},
"direction": "clockwise",
"enable": false
},
"twinkle": {
"lines": {
"enable": false,
"frequency": 0.05,
"opacity": 1
},
"particles": {
"enable": false,
"frequency": 0.05,
"opacity": 1
}
},
"wobble": {
"distance": 5,
"enable": false,
"speed": {
"angle": 50,
"move": 10
}
},
"life": {
"count": 0,
"delay": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": 0,
"sync": false
},
"duration": {
"random": {
"enable": false,
"minimumValue": 0.0001
},
"value": 0,
"sync": false
}
},
"rotate": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": 0,
"animation": {
"enable": false,
"speed": 0,
"decay": 0,
"sync": false
},
"direction": "clockwise",
"path": false
},
"orbit": {
"animation": {
"count": 0,
"enable": false,
"speed": 1,
"decay": 0,
"delay": 0,
"sync": false
},
"enable": false,
"opacity": 1,
"rotation": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": 45
},
"width": 1
},
"links": {
"blink": false,
"color": {
"value": "random"
},
"consent": false,
"distance": 100,
"enable": false,
"frequency": 1,
"opacity": 1,
"shadow": {
"blur": 5,
"color": {
"value": "#000"
},
"enable": false
},
"triangles": {
"enable": false,
"frequency": 1
},
"width": 1,
"warp": false
},
"repulse": {
"random": {
"enable": false,
"minimumValue": 0
},
"value": 0,
"enabled": false,
"distance": 1,
"duration": 1,
"factor": 1,
"speed": 1
}
},
}
main.ts
中引入插件import { createApp } from 'vue'
import './style.css'
import 'animate.css';
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import Particles from "vue3-particles";// 引入插件
import App from './App.vue'
const app = createApp(App)
app.use(Particles)
app.use(ElementPlus, { size: 'small', zIndex: 3000 })
app.mount('#app')
App.vue
中使用插件<template>
<div class="app-background" @wheel="handleWheel">
<!-- 上一页信息 -->
<div class="absolute" @click="prev" style="top: 50px;">
<img src="/up.svg" class="logo" alt="Email" />
</div>
<el-carousel ref="carousel" height="100vh" trigger="click" direction="vertical" :autoplay="false"
@change="changeCarousel">
<el-carousel-item>
<first :count="count" ref="firstPage" />
</el-carousel-item>
<el-carousel-item>
<second :count="count" />
</el-carousel-item>
<el-carousel-item>
<third :count="count" ref="thirdPage" />
</el-carousel-item>
<el-carousel-item>
<fourth :count="count" ref="fourthPage" />
</el-carousel-item>
<el-carousel-item>
<fifth :count="count" ref="fifthPage" @showProject="showProject" />
</el-carousel-item>
</el-carousel>
<!-- 下一页信息 -->
<div class="absolute" @click="next" style="bottom: 50px;">
<img src="/down.svg" class="logo" alt="Email" />
</div>
<!-- 项目经验详情 -->
<el-dialog v-model="showDialog" center>
<div class="family">
<div class="text18" style="text-align: center;font-weight: bold;">{{ projectInfo.projectName }}</div>
<div class="margin-top" style="text-align: center;">
{{ projectInfo.projectStartTime }} - {{ projectInfo.projectEndTime }}
</div>
<div class="flex margin-top">
<div style="min-width: 100px;">项目描述:</div>
<div>{{ projectInfo.projectDescription }}</div>
</div>
<div class="flex margin-top">
<div style="min-width: 100px;">项目职责:</div>
<div>{{ projectInfo.projectDuty }}</div>
</div>
<div class="flex margin-top">
<div style="min-width: 100px;">技术栈:</div>
<div>{{ projectInfo.projectStack }}</div>
</div>
<div class="flex margin-top" v-if="projectInfo.projectOnline">
<div style="min-width: 100px;">线上地址:</div>
<el-link :href="projectInfo.projectOnline" target="_blank">
{{ projectInfo.projectOnline }}
</el-link>
</div>
</div>
</el-dialog>
// 使用粒子特效组件
<vue-particles id="tsparticles" :particlesInit="particlesInit" :clickEffect="true" :options="options" />
</div>
</template>
<script setup lang="ts">
import first from './components/First.vue'
import second from './components/Second.vue'
import third from './components/Third.vue'
import fourth from './components/Fourth.vue'
import fifth from './components/Fifth.vue'
import project from './utils/Project.ts'
import { options } from "./utils/options";
import { loadFull } from 'tsparticles'
import { ref } from 'vue'
const showDialog = ref(false)
const projectInfo = ref(project['center'])
const count = ref(0)
const firstPage = ref()
const thirdPage = ref()
const fourthPage = ref()
const fifthPage = ref()
const carousel = ref()
// 获取默认项目经验数据
const showProject = (value: any) => {
if (project[value]) {
projectInfo.value = project[value]
showDialog.value = true
}
}
// 上一页
const prev = () => {
carousel.value.prev()
}
// 下一页
const next = () => {
carousel.value.next()
}
let timer: any = null
const handleWheel = (e: any) => {
if (showDialog.value) return
let lock: Boolean = !timer;
if (lock) {
if (e.deltaY > 0) {
next()
} else if (e.deltaY < 0) {
prev()
}
timer = setTimeout(() => {
timer = null;
}, 500);
}
}
const handleKeyDown = (event: any) => {
if (showDialog.value) return
let lock: Boolean = !timer;
if (lock) {
if (event.key === 'ArrowUp') { prev() }
else if (event.key === 'ArrowDown') { next() }
timer = setTimeout(() => {
timer = null;
}, 500);
}
}
// 监听键盘触发事件
document.addEventListener('keydown', handleKeyDown);
// 监听切换事件,重置和触发动态效果
const changeCarousel = (value: any) => {
count.value = value
if (value === 0) {
firstPage.value.resetArr()
} else if (value === 2) {
thirdPage.value.resetArr()
} else if (value === 3) {
fourthPage.value.resetArr()
} else if (value === 4) {
fifthPage.value.resetArr()
}
}
const particlesInit = async (engine: any) => {
await loadFull(engine)
}
</script>
<style scoped>
.app-background {
position: relative;
width: 100%;
height: 100vh;
background-image: url('./assets/bgBig.png');
background-repeat: no-repeat;
background-position: center 0;
background-size: cover;
}
.el-carousel__item {
min-height: 100vh;
background-color: rgba(10, 10, 10, 0.3);
}
::v-deep(.el-dialog) {
background-color: rgb(250, 235, 215);
animation: jackInTheBox;
animation-duration: 1.5s;
}
.absolute {
position: absolute;
z-index: 10;
left: calc(50% - 14px);
opacity: .25;
transition: all .4s linear 0s;
}
.absolute:hover {
transform: scale(1.18);
opacity: .85;
}
.logo {
width: 28px;
height: 28px;
}
</style>
效果如下: